diff --git a/.gitattributes b/.gitattributes index 379a6bbdb..2a92ef017 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,8 @@ .github/workflows/*.lock.yml linguist-generated=true merge=ours +# Cross-platform tools rewrite these files, so keep their output deterministic. +java/**/*.java text eol=lf + # Generated files — keep LF line endings so codegen output is deterministic across platforms. nodejs/src/generated/* eol=lf linguist-generated=true dotnet/src/Generated/* eol=lf linguist-generated=true diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 389bcda90..eb77167ce 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -1,14 +1,14 @@ #!/bin/sh # # Pre-commit hook that runs Spotless check on the Java SDK when Java source -# files are staged. Only triggers if changes exist under java/src/. +# files are staged. Only triggers if changes exist under java/sdk/src/. # # To install this hook, run from the repository root: # git config core.hooksPath .githooks # -# Only run Spotless if staged changes include Java source files under java/src/ -if ! git diff --cached --name-only | grep -q '^java/src/'; then +# Only run Spotless if staged changes include Java source files under java/sdk/src/ +if ! git diff --cached --name-only | grep -q '^java/sdk/src/'; then exit 0 fi diff --git a/.github/actions/java-test-report/action.yml b/.github/actions/java-test-report/action.yml index e826628a0..eedf05372 100644 --- a/.github/actions/java-test-report/action.yml +++ b/.github/actions/java-test-report/action.yml @@ -4,15 +4,15 @@ inputs: report-path: description: "Path to the test report XML files (glob pattern)" required: false - default: "java/target/{surefire-reports*,failsafe-reports}/TEST-*.xml" + default: "java/sdk/target/{surefire-reports*,failsafe-reports}/TEST-*.xml" jacoco-path: description: "Path to the JaCoCo XML report" required: false - default: "java/target/site/jacoco-coverage/jacoco.xml" + default: "java/sdk/target/site/jacoco-coverage/jacoco.xml" jacoco-csv-path: description: "Path to the JaCoCo CSV report" required: false - default: "java/target/site/jacoco-coverage/jacoco.csv" + default: "java/sdk/target/site/jacoco-coverage/jacoco.csv" check-name: description: "Name for the check run" required: false diff --git a/.github/aw/actions-lock.json b/.github/aw/actions-lock.json index 528dbe85c..4be7dfbd5 100644 --- a/.github/aw/actions-lock.json +++ b/.github/aw/actions-lock.json @@ -20,15 +20,15 @@ "version": "v7.0.1", "sha": "043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" }, - "github/gh-aw-actions/setup-cli@v0.82.10": { + "github/gh-aw-actions/setup-cli@v0.83.1": { "repo": "github/gh-aw-actions/setup-cli", - "version": "v0.82.10", - "sha": "05205436a78512d71a2d842e46586ed05f4fa058" + "version": "v0.83.1", + "sha": "8bdba8075360648fe6802302a5b4e016361dc6ac" }, - "github/gh-aw-actions/setup@v0.82.10": { + "github/gh-aw-actions/setup@v0.83.1": { "repo": "github/gh-aw-actions/setup", - "version": "v0.82.10", - "sha": "05205436a78512d71a2d842e46586ed05f4fa058" + "version": "v0.83.1", + "sha": "8bdba8075360648fe6802302a5b4e016361dc6ac" } } } diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 79fa9522c..a9bc22d0e 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -11,7 +11,7 @@ - Top-level: `README.md` (architecture + quick start) - Language entry points: `nodejs/src/client.ts`, `python/README.md`, `go/README.md`, `dotnet/README.md` -- Java: `java/README.md`, `java/pom.xml` +- Java: `java/README.md`, `java/pom.xml`, `java/sdk/pom.xml`, `java/copilot-native/pom.xml` - Test harness & E2E: `test/harness/*`, Python harness wrapper `python/e2e/testharness/proxy.py` - Schemas & type generation: `nodejs/scripts/generate-session-types.ts` - Session snapshots used by E2E: `test/snapshots/` (used by the replay proxy) @@ -39,7 +39,7 @@ - E2E runs against a local **replaying CAPI proxy** (see `test/harness/server.ts`). Most language E2E harnesses spawn that server automatically (see `python/e2e/testharness/proxy.py`). - Tests rely on YAML snapshot exchanges under `test/snapshots/` — to add test scenarios, add or edit the appropriate YAML files and update tests. - The harness prints `Listening: http://...` — tests parse this URL to configure CLI or proxy. -- Java E2E tests use `E2ETestContext` which manages a `CapiProxy` (Node.js replaying proxy). The harness is cloned during Maven's `generate-test-resources` phase to `java/target/copilot-sdk/`. +- Java E2E tests use `E2ETestContext` which manages a `CapiProxy` (Node.js replaying proxy). The harness is cloned during Maven's `generate-test-resources` phase to `java/sdk/target/copilot-sdk/`. - Java test method names are converted to lowercase snake_case for snapshot filenames (avoids case collisions on macOS/Windows). ## Project-specific conventions & patterns ✅ @@ -61,13 +61,13 @@ ## Where to add new code or tests 🧭 -- SDK code: `nodejs/src`, `python/copilot`, `go`, `dotnet/src`, `rust/src`, `java/src/main/java` -- Unit tests: `nodejs/test`, `python/*`, `go/*`, `dotnet/test`, `rust/tests`, `java/src/test/java` -- E2E tests: `*/e2e/` folders that use the shared replay proxy and `test/snapshots/`, `java/src/test/java/**/e2e/` -- Generated types: update schema in `@github/copilot` then run `cd nodejs && npm run generate:session-types` and commit generated files in `src/generated` or language generated location. Java generated types: `java/src/generated/java` +- SDK code: `nodejs/src`, `python/copilot`, `go`, `dotnet/src`, `rust/src`, `java/sdk/src/main/java` +- Unit tests: `nodejs/test`, `python/*`, `go/*`, `dotnet/test`, `rust/tests`, `java/sdk/src/test/java` +- E2E tests: `*/e2e/` folders that use the shared replay proxy and `test/snapshots/`, `java/sdk/src/test/java/**/e2e/` +- Generated types: update schema in `@github/copilot` then run `cd nodejs && npm run generate:session-types` and commit generated files in `src/generated` or language generated location. Java generated types: `java/sdk/src/generated/java` ## Boundaries — files you must NOT hand-edit ⛔ -- `java/src/generated/java/` — auto-generated by `scripts/codegen/java.ts`; regenerate with `cd java && mvn generate-sources -Pcodegen`. +- `java/sdk/src/generated/java/` — auto-generated by `scripts/codegen/java.ts`; regenerate with `cd java && mvn generate-sources -Pcodegen`. - `nodejs/src/generated/` — auto-generated by `npm run generate:session-types`. - `test/snapshots/` — authoritative test fixtures; add/edit YAML here to change E2E behavior, but don't delete without understanding downstream impact. diff --git a/.github/instructions/docs-style.instructions.md b/.github/instructions/docs-style.instructions.md index 32ea46fbf..16dbe2709 100644 --- a/.github/instructions/docs-style.instructions.md +++ b/.github/instructions/docs-style.instructions.md @@ -10,7 +10,7 @@ This style guide applies to all documentation in the `docs/` directory. These do Use **sentence case** for all headings. Capitalize only the first word and proper nouns. -* `## Quick start: Azure AI Foundry` — not `## Quick Start: Azure AI Foundry` +* `## Quick start: Microsoft Foundry` — not `## Quick Start: Microsoft Foundry` * `# Custom agents and sub-agent orchestration` — not `# Custom Agents & Sub-Agent Orchestration` Use `and` instead of `&` in headings. @@ -49,7 +49,7 @@ When a callout applies to a specific language, put the qualifier as bold text in ```markdown > [!TIP] -> **(Python / Go)** These SDKs use a single `Data` class/struct with all fields optional. +> **(Python / Go)** These SDKs use separate, per-event data types. ``` ## Lists diff --git a/.github/skills/new-java-e2e-test-yaml-and-test/SKILL.md b/.github/skills/new-java-e2e-test-yaml-and-test/SKILL.md index 060228d55..d034b2037 100644 --- a/.github/skills/new-java-e2e-test-yaml-and-test/SKILL.md +++ b/.github/skills/new-java-e2e-test-yaml-and-test/SKILL.md @@ -54,7 +54,7 @@ conversations: ### Step 3: Create the Java IT test class -Place it in `java/src/test/java/com/github/copilot/` with an `IT` suffix +Place it in `java/sdk/src/test/java/com/github/copilot/` with an `IT` suffix (e.g., `MyFeatureIT.java`). The failsafe plugin picks up `*IT.java` files. **Template:** @@ -141,12 +141,12 @@ mvn clean verify | What | Where | |------|-------| -| Test context (manages proxy, workDir, CLI) | `java/src/test/java/com/github/copilot/E2ETestContext.java` | -| Java proxy wrapper | `java/src/test/java/com/github/copilot/CapiProxy.java` | +| Test context (manages proxy, workDir, CLI) | `java/sdk/src/test/java/com/github/copilot/E2ETestContext.java` | +| Java proxy wrapper | `java/sdk/src/test/java/com/github/copilot/CapiProxy.java` | | Replay proxy (TypeScript) | `test/harness/replayingCapiProxy.ts` | | Proxy server entry point | `test/harness/server.ts` | | Snapshot files | `test/snapshots//.yaml` | -| Existing IT tests for reference | `java/src/test/java/com/github/copilot/*IT.java` | +| Existing IT tests for reference | `java/sdk/src/test/java/com/github/copilot/*IT.java` | ## How the Proxy Matches Requests diff --git a/.github/workflows/agentics-maintenance.yml b/.github/workflows/agentics-maintenance.yml index 45d836922..28c4e67ca 100644 --- a/.github/workflows/agentics-maintenance.yml +++ b/.github/workflows/agentics-maintenance.yml @@ -1,4 +1,4 @@ -# This file was automatically generated by pkg/workflow/maintenance_workflow.go (v0.82.10). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# This file was automatically generated by pkg/workflow/maintenance_workflow.go (v0.83.1). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ # / _ \ | | (_) @@ -94,7 +94,7 @@ jobs: discussions: write steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -113,7 +113,7 @@ jobs: issues: write steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -132,7 +132,7 @@ jobs: pull-requests: write steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -152,7 +152,7 @@ jobs: actions: write steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -181,7 +181,7 @@ jobs: persist-credentials: false - name: Setup Scripts - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -196,9 +196,9 @@ jobs: await main(); - name: Install gh-aw - uses: github/gh-aw-actions/setup-cli@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup-cli@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: - version: v0.82.10 + version: v0.83.1 - name: Run operation uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -228,7 +228,7 @@ jobs: pull-requests: write steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -275,7 +275,7 @@ jobs: persist-credentials: false - name: Setup Scripts - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -321,7 +321,7 @@ jobs: persist-credentials: false - name: Setup Scripts - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -336,9 +336,9 @@ jobs: await main(); - name: Install gh-aw - uses: github/gh-aw-actions/setup-cli@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup-cli@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: - version: v0.82.10 + version: v0.83.1 - name: Create missing labels uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -367,7 +367,7 @@ jobs: persist-credentials: false - name: Setup Scripts - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -382,9 +382,9 @@ jobs: await main(); - name: Install gh-aw - uses: github/gh-aw-actions/setup-cli@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup-cli@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: - version: v0.82.10 + version: v0.83.1 - name: Restore activity report logs cache id: activity_report_logs_cache @@ -472,7 +472,7 @@ jobs: persist-credentials: false - name: Setup Scripts - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -487,9 +487,9 @@ jobs: await main(); - name: Install gh-aw - uses: github/gh-aw-actions/setup-cli@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup-cli@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: - version: v0.82.10 + version: v0.83.1 - name: Restore forecast report logs cache id: forecast_report_logs_cache @@ -564,7 +564,7 @@ jobs: issues: write steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -601,7 +601,7 @@ jobs: persist-credentials: false - name: Setup Scripts - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -616,9 +616,9 @@ jobs: await main(); - name: Install gh-aw - uses: github/gh-aw-actions/setup-cli@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup-cli@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: - version: v0.82.10 + version: v0.83.1 - name: Validate workflows and file issue on findings uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 diff --git a/.github/workflows/codegen-check.yml b/.github/workflows/codegen-check.yml index 78927f160..f37a71e45 100644 --- a/.github/workflows/codegen-check.yml +++ b/.github/workflows/codegen-check.yml @@ -15,7 +15,7 @@ on: - 'go/rpc/**' - 'rust/src/generated/**' - 'sdk-protocol-version.json' - - 'java/src/main/java/com/github/copilot/SdkProtocolVersion.java' + - 'java/sdk/src/main/java/com/github/copilot/SdkProtocolVersion.java' - '.github/workflows/codegen-check.yml' workflow_dispatch: @@ -84,7 +84,7 @@ jobs: - name: Verify Java protocol version matches run: | EXPECTED=$(jq -r '.version' sdk-protocol-version.json) - ACTUAL=$(grep -oP 'LATEST\(\K[0-9]+' java/src/main/java/com/github/copilot/SdkProtocolVersion.java) + ACTUAL=$(grep -oP 'LATEST\(\K[0-9]+' java/sdk/src/main/java/com/github/copilot/SdkProtocolVersion.java) if [ "$EXPECTED" != "$ACTUAL" ]; then echo "::error::Java SDK protocol version ($ACTUAL) does not match sdk-protocol-version.json ($EXPECTED). Java manages its own SdkProtocolVersion.java via java/scripts/codegen/. Update it to match." exit 1 diff --git a/.github/workflows/cross-repo-issue-analysis.lock.yml b/.github/workflows/cross-repo-issue-analysis.lock.yml index dc117400b..510618f04 100644 --- a/.github/workflows/cross-repo-issue-analysis.lock.yml +++ b/.github/workflows/cross-repo-issue-analysis.lock.yml @@ -1,6 +1,6 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"16de319b1db6be0d409d3055ca8fa9f619f2c0120dad678248a45dce07b880b4","body_hash":"653dfb46c89df98eca22ddfb802149d6ade32e9a7ad40dbdc51bfb6b0ba1c4a3","compiler_version":"v0.82.10","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.70"}} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN","RUNTIME_TRIAGE_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"05205436a78512d71a2d842e46586ed05f4fa058","version":"v0.82.10"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35","digest":"sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35","digest":"sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35","digest":"sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.1","digest":"sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.5.0","digest":"sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4","pinned_image":"ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4"}]} -# This file was automatically generated by gh-aw (v0.82.10). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"16de319b1db6be0d409d3055ca8fa9f619f2c0120dad678248a45dce07b880b4","body_hash":"653dfb46c89df98eca22ddfb802149d6ade32e9a7ad40dbdc51bfb6b0ba1c4a3","compiler_version":"v0.83.1","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.73"}} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN","RUNTIME_TRIAGE_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"8bdba8075360648fe6802302a5b4e016361dc6ac","version":"v0.83.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38","digest":"sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38","digest":"sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38","digest":"sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.3","digest":"sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]} +# This file was automatically generated by gh-aw (v0.83.1). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ # / _ \ | | (_) @@ -40,17 +40,17 @@ # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 # - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 -# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 +# - actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 +# - github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04 -# - ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3 -# - ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32 +# - ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c +# - ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 +# - ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83 # - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b -# - ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 +# - ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3 name: "SDK Runtime Triage" on: @@ -108,7 +108,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -118,8 +118,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "SDK Runtime Triage" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/cross-repo-issue-analysis.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Generate agentic run info id: generate_aw_info @@ -127,16 +127,16 @@ jobs: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AGENT_VERSION: "1.0.70" - GH_AW_INFO_CLI_VERSION: "v0.82.10" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AGENT_VERSION: "1.0.73" + GH_AW_INFO_CLI_VERSION: "v0.83.1" GH_AW_INFO_WORKFLOW_NAME: "SDK Runtime Triage" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -234,7 +234,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.82.10" + GH_AW_COMPILED_VERSION: "v0.83.1" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -353,7 +353,7 @@ jobs: GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} GH_AW_INPUTS_ISSUE_NUMBER: ${{ inputs.issue_number }} - GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' + GH_AW_MCP_CLI_SERVERS_LIST: "- `github` — run `github --help` to see available tools\n- `safeoutputs` — run `safeoutputs --help` to see available tools" GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} with: script: | @@ -437,6 +437,7 @@ jobs: has_patch: ${{ steps.collect_output.outputs.has_patch }} http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }} inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }} + invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }} mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }} model: ${{ needs.activation.outputs.model }} model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }} @@ -449,7 +450,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -458,8 +459,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "SDK Runtime Triage" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/cross-repo-issue-analysis.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Set runtime paths id: set-runtime-paths @@ -511,11 +512,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.70 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.35 --rootless + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38 --rootless - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 @@ -542,7 +543,7 @@ jobs: GH_AW_SKILL_DIR: ".github/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04 ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3 ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3 - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" @@ -729,17 +730,18 @@ jobs: MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') source "${RUNNER_TEMP}/gh-aw/actions/resolve_docker_socket_gid.sh" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.1' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.3' mkdir -p "$HOME/.copilot" GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_d97c92af15acf38e_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_4d6f106ca28cda70_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.5.0", + "container": "ghcr.io/github/github-mcp-server:v1.6.0", "env": { + "GITHUB_FEATURES": "fields_param", "GITHUB_HOST": "${GITHUB_SERVER_URL}", "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_MCP_SERVER_TOKEN}", "GITHUB_READ_ONLY": "1", @@ -790,10 +792,11 @@ jobs: "port": $MCP_GATEWAY_PORT, "domain": "${MCP_GATEWAY_DOMAIN}", "apiKey": "${MCP_GATEWAY_API_KEY}", - "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}", + "startupTimeout": 120 } } - GH_AW_MCP_CONFIG_d97c92af15acf38e_EOF + GH_AW_MCP_CONFIG_4d6f106ca28cda70_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -825,6 +828,7 @@ jobs: # --allow-tool shell(date) # --allow-tool shell(echo) # --allow-tool shell(find:*) + # --allow-tool shell(github:*) # --allow-tool shell(grep) # --allow-tool shell(grep:*) # --allow-tool shell(head) @@ -846,7 +850,7 @@ jobs: run: | set -o pipefail printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt - trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT mkdir -p "$HOME/.copilot" printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" export XDG_CONFIG_HOME="$HOME" @@ -857,7 +861,7 @@ jobs: export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.35/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.35,squid=sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3,agent=sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed,agent-act=sha256:b00340a7b09c917c522cb806af6da1d12f2146e25a4a6198f1589b0116aee992,api-proxy=sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04,cli-proxy=sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST="" @@ -876,7 +880,7 @@ jobs: fi # shellcheck disable=SC1003,SC2016,SC2086 awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --skip-pull \ - -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(cat:*)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(find:*)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(grep:*)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(head:*)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(ls:*)'\'' --allow-tool '\''shell(printf)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(safeoutputs:*)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(tail:*)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(wc:*)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(cat:*)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(find:*)'\'' --allow-tool '\''shell(github:*)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(grep:*)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(head:*)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(ls:*)'\'' --allow-tool '\''shell(printf)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(safeoutputs:*)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(tail:*)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(wc:*)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE @@ -890,7 +894,7 @@ jobs: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_TIMEOUT_MINUTES: 20 - GH_AW_VERSION: v0.82.10 + GH_AW_VERSION: v0.83.1 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1081,7 +1085,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1090,8 +1094,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "SDK Runtime Triage" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/cross-repo-issue-analysis.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output @@ -1332,7 +1336,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1341,8 +1345,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "SDK Runtime Triage" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/cross-repo-issue-analysis.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output @@ -1369,7 +1373,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04 ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 - name: Check if detection needed id: detection_guard if: always() @@ -1427,16 +1431,16 @@ jobs: mkdir -p /tmp/gh-aw/threat-detection touch /tmp/gh-aw/threat-detection/detection.log - name: Setup Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.70 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.35 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true @@ -1446,7 +1450,7 @@ jobs: run: | set -o pipefail printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt - trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT mkdir -p "$HOME/.copilot" printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" export XDG_CONFIG_HOME="$HOME" @@ -1456,7 +1460,7 @@ jobs: export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.35/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.35,squid=sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3,agent=sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed,agent-act=sha256:b00340a7b09c917c522cb806af6da1d12f2146e25a4a6198f1589b0116aee992,api-proxy=sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04,cli-proxy=sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST="" @@ -1490,7 +1494,7 @@ jobs: GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_TIMEOUT_MINUTES: 20 - GH_AW_VERSION: v0.82.10 + GH_AW_VERSION: v0.83.1 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1573,15 +1577,15 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} env: GH_AW_SETUP_WORKFLOW_NAME: "SDK Runtime Triage" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/cross-repo-issue-analysis.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Check team membership for workflow id: check_membership @@ -1618,7 +1622,7 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.70" + GH_AW_ENGINE_VERSION: "1.0.73" GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} GH_AW_WORKFLOW_ID: "cross-repo-issue-analysis" @@ -1636,7 +1640,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1645,8 +1649,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "SDK Runtime Triage" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/cross-repo-issue-analysis.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output diff --git a/.github/workflows/docs-validation.yml b/.github/workflows/docs-validation.yml index 4f53b71e4..dff02f0d2 100644 --- a/.github/workflows/docs-validation.yml +++ b/.github/workflows/docs-validation.yml @@ -9,8 +9,9 @@ on: - 'python/copilot/**' - 'go/**/*.go' - 'dotnet/src/**' - - 'java/src/**' + - 'java/sdk/src/**' - 'java/pom.xml' + - 'java/sdk/pom.xml' - 'scripts/docs-validation/**' - '.github/workflows/docs-validation.yml' workflow_dispatch: diff --git a/.github/workflows/dotnet-sdk-tests.yml b/.github/workflows/dotnet-sdk-tests.yml index ecd5dcd09..fa2e2dc75 100644 --- a/.github/workflows/dotnet-sdk-tests.yml +++ b/.github/workflows/dotnet-sdk-tests.yml @@ -4,31 +4,15 @@ on: push: branches: - main - pull_request: - types: [opened, synchronize, reopened, ready_for_review] - paths: - - 'dotnet/**' - - 'test/**' - - 'nodejs/package.json' - - '.github/workflows/dotnet-sdk-tests.yml' - - '!**/*.md' - - '!**/LICENSE*' - - '!**/.gitignore' - - '!**/.editorconfig' - - '!**/*.png' - - '!**/*.jpg' - - '!**/*.jpeg' - - '!**/*.gif' - - '!**/*.svg' workflow_dispatch: - merge_group: + workflow_call: permissions: contents: read jobs: test: - name: ".NET SDK Tests (${{ matrix.os }}, ${{ matrix.transport }}, ${{ matrix.backend }})" + name: ".NET SDK Tests (${{ matrix.os }}, ${{ matrix.transport }}, ${{ matrix.backend }}, ${{ matrix.shard }})" if: github.event.repository.fork == false env: POWERSHELL_UPDATECHECK: Off @@ -40,22 +24,41 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] transport: ["default", "inprocess"] backend: [capi] + shard: [full] # TODO: Re-enable after fixing in-process sqlite file locking on shutdown on Windows. exclude: - os: windows-latest transport: "inprocess" + - os: windows-latest + transport: default + shard: full include: + # Keep xUnit serial within each process, but split the slow Windows + # default-transport suite across two isolated test hosts. Keep both + # target frameworks in each shard: separate framework jobs did not + # shorten the critical path and doubled the Windows job count. + - os: windows-latest + transport: default + backend: capi + shard: "1" + - os: windows-latest + transport: default + backend: capi + shard: "2" - os: ubuntu-latest transport: inprocess backend: anthropic-messages + shard: full test-filter: "FullyQualifiedName~GitHub.Copilot.Test.E2E&E2EBackend!=SelfConfiguredBackend&E2EBackend!=CapiOnly" - os: ubuntu-latest transport: inprocess backend: openai-responses + shard: full test-filter: "FullyQualifiedName~GitHub.Copilot.Test.E2E&E2EBackend!=SelfConfiguredBackend&E2EBackend!=CapiOnly" - os: ubuntu-latest transport: inprocess backend: openai-completions + shard: full test-filter: "FullyQualifiedName~GitHub.Copilot.Test.E2E&E2EBackend!=SelfConfiguredBackend&E2EBackend!=CapiOnly" runs-on: ${{ matrix.os }} defaults: @@ -108,9 +111,30 @@ jobs: - name: Run .NET SDK tests env: COPILOT_HMAC_KEY: ${{ secrets.COPILOT_DEVELOPER_CLI_INTEGRATION_HMAC_KEY }} + DOTNET_TEST_SHARD: ${{ matrix.shard }} run: | args=(--no-build -v n) - if [[ -n "$DOTNET_TEST_FILTER" ]]; then - args+=(--filter "$DOTNET_TEST_FILTER") + + filter="$DOTNET_TEST_FILTER" + if [[ "$DOTNET_TEST_SHARD" != "full" ]]; then + if [[ "$DOTNET_TEST_SHARD" == "1" ]]; then + initials=(A C D H I J K L N Q S U W Y) + shard_filter="FullyQualifiedName~GitHub.Copilot.Test.ConnectionToken" + else + initials=(B E F G M O P R T V X Z) + shard_filter="" + fi + + for namespace in E2E Unit; do + for initial in "${initials[@]}"; do + clause="FullyQualifiedName~GitHub.Copilot.Test.${namespace}.${initial}" + shard_filter="${shard_filter:+${shard_filter}|}${clause}" + done + done + filter="${filter:+(${filter})&}(${shard_filter})" + fi + + if [[ -n "$filter" ]]; then + args+=(--filter "$filter") fi dotnet test "${args[@]}" diff --git a/.github/workflows/go-sdk-tests.yml b/.github/workflows/go-sdk-tests.yml index 365e60373..61d74d257 100644 --- a/.github/workflows/go-sdk-tests.yml +++ b/.github/workflows/go-sdk-tests.yml @@ -4,25 +4,8 @@ on: push: branches: - main - pull_request: - types: [opened, synchronize, reopened, ready_for_review] - paths: - - 'go/**' - - 'test/**' - - 'nodejs/package.json' - - '.github/workflows/go-sdk-tests.yml' - - '.github/actions/setup-copilot/**' - - '!**/*.md' - - '!**/LICENSE*' - - '!**/.gitignore' - - '!**/.editorconfig' - - '!**/*.png' - - '!**/*.jpg' - - '!**/*.jpeg' - - '!**/*.gif' - - '!**/*.svg' workflow_dispatch: - merge_group: + workflow_call: permissions: contents: read diff --git a/.github/workflows/handle-bug.lock.yml b/.github/workflows/handle-bug.lock.yml index 4c8844361..153e882c4 100644 --- a/.github/workflows/handle-bug.lock.yml +++ b/.github/workflows/handle-bug.lock.yml @@ -1,6 +1,6 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"df4e7ec5b346a28c7de5353cbfb15d329d03eb7092f2ded784ee6602108461e6","body_hash":"376c982b907760113954510ef1aff70d22dcb172c7bb851b2fa3d82121bdbc1c","compiler_version":"v0.82.10","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.70"}} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"05205436a78512d71a2d842e46586ed05f4fa058","version":"v0.82.10"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35","digest":"sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35","digest":"sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35","digest":"sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.1","digest":"sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.5.0","digest":"sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4","pinned_image":"ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4"}]} -# This file was automatically generated by gh-aw (v0.82.10). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"df4e7ec5b346a28c7de5353cbfb15d329d03eb7092f2ded784ee6602108461e6","body_hash":"376c982b907760113954510ef1aff70d22dcb172c7bb851b2fa3d82121bdbc1c","compiler_version":"v0.83.1","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.73"}} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"8bdba8075360648fe6802302a5b4e016361dc6ac","version":"v0.83.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38","digest":"sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38","digest":"sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38","digest":"sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.3","digest":"sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]} +# This file was automatically generated by gh-aw (v0.83.1). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ # / _ \ | | (_) @@ -39,17 +39,17 @@ # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 # - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 -# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 +# - actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 +# - github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04 -# - ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3 -# - ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32 +# - ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c +# - ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 +# - ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83 # - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b -# - ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 +# - ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3 name: "Bug Handler" on: @@ -119,7 +119,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -127,8 +127,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Bug Handler" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-bug.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} - name: Resolve host repo for activation checkout @@ -156,16 +156,16 @@ jobs: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AGENT_VERSION: "1.0.70" - GH_AW_INFO_CLI_VERSION: "v0.82.10" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AGENT_VERSION: "1.0.73" + GH_AW_INFO_CLI_VERSION: "v0.83.1" GH_AW_INFO_WORKFLOW_NAME: "Bug Handler" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -273,7 +273,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.82.10" + GH_AW_COMPILED_VERSION: "v0.83.1" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -374,7 +374,7 @@ jobs: GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} GH_AW_INPUTS_ISSUE_NUMBER: ${{ inputs.issue_number }} - GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' + GH_AW_MCP_CLI_SERVERS_LIST: "- `github` — run `github --help` to see available tools\n- `safeoutputs` — run `safeoutputs --help` to see available tools" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -458,6 +458,7 @@ jobs: has_patch: ${{ steps.collect_output.outputs.has_patch }} http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }} inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }} + invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }} mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }} model: ${{ needs.activation.outputs.model }} model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }} @@ -470,7 +471,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -479,8 +480,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Bug Handler" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-bug.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} - name: Set runtime paths @@ -527,11 +528,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.70 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.35 --rootless + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38 --rootless - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 @@ -566,7 +567,7 @@ jobs: GH_AW_SKILL_DIR: ".github/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04 ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3 ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3 - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" @@ -736,17 +737,18 @@ jobs: MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') source "${RUNNER_TEMP}/gh-aw/actions/resolve_docker_socket_gid.sh" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.1' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.3' mkdir -p "$HOME/.copilot" GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_e85305aae15b8db5_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_9424afbf8aa8e8ef_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.5.0", + "container": "ghcr.io/github/github-mcp-server:v1.6.0", "env": { + "GITHUB_FEATURES": "fields_param", "GITHUB_HOST": "${GITHUB_SERVER_URL}", "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_MCP_SERVER_TOKEN}", "GITHUB_READ_ONLY": "1", @@ -800,10 +802,11 @@ jobs: "port": $MCP_GATEWAY_PORT, "domain": "${MCP_GATEWAY_DOMAIN}", "apiKey": "${MCP_GATEWAY_API_KEY}", - "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}", + "startupTimeout": 120 } } - GH_AW_MCP_CONFIG_e85305aae15b8db5_EOF + GH_AW_MCP_CONFIG_9424afbf8aa8e8ef_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -832,7 +835,7 @@ jobs: run: | set -o pipefail printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt - trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT mkdir -p "$HOME/.copilot" printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" export XDG_CONFIG_HOME="$HOME" @@ -843,7 +846,7 @@ jobs: export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.35/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.35,squid=sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3,agent=sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed,agent-act=sha256:b00340a7b09c917c522cb806af6da1d12f2146e25a4a6198f1589b0116aee992,api-proxy=sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04,cli-proxy=sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST="" @@ -876,7 +879,7 @@ jobs: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_TIMEOUT_MINUTES: 20 - GH_AW_VERSION: v0.82.10 + GH_AW_VERSION: v0.83.1 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1067,7 +1070,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1076,8 +1079,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Bug Handler" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-bug.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} - name: Download agent output artifact @@ -1319,7 +1322,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1328,8 +1331,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Bug Handler" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-bug.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} - name: Download agent output artifact @@ -1357,7 +1360,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04 ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 - name: Check if detection needed id: detection_guard if: always() @@ -1415,16 +1418,16 @@ jobs: mkdir -p /tmp/gh-aw/threat-detection touch /tmp/gh-aw/threat-detection/detection.log - name: Setup Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.70 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.35 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true @@ -1434,7 +1437,7 @@ jobs: run: | set -o pipefail printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt - trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT mkdir -p "$HOME/.copilot" printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" export XDG_CONFIG_HOME="$HOME" @@ -1444,7 +1447,7 @@ jobs: export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.35/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.35,squid=sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3,agent=sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed,agent-act=sha256:b00340a7b09c917c522cb806af6da1d12f2146e25a4a6198f1589b0116aee992,api-proxy=sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04,cli-proxy=sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST="" @@ -1478,7 +1481,7 @@ jobs: GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_TIMEOUT_MINUTES: 20 - GH_AW_VERSION: v0.82.10 + GH_AW_VERSION: v0.83.1 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1569,7 +1572,7 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.70" + GH_AW_ENGINE_VERSION: "1.0.73" GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} GH_AW_WORKFLOW_ID: "handle-bug" @@ -1587,7 +1590,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1596,8 +1599,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Bug Handler" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-bug.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} - name: Download agent output artifact diff --git a/.github/workflows/handle-documentation.lock.yml b/.github/workflows/handle-documentation.lock.yml index 71d73c337..0a5e68efe 100644 --- a/.github/workflows/handle-documentation.lock.yml +++ b/.github/workflows/handle-documentation.lock.yml @@ -1,6 +1,6 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"6c3b9dc8d0f7b54d44175db209cda34e37ea8c635d01ee0a5cba13675053f6cd","body_hash":"81c8287f5691cdc10ae8f60c004bb671d9b4942740d73fcc9646e28fbcd8790e","compiler_version":"v0.82.10","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.70"}} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"05205436a78512d71a2d842e46586ed05f4fa058","version":"v0.82.10"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35","digest":"sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35","digest":"sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35","digest":"sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.1","digest":"sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.5.0","digest":"sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4","pinned_image":"ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4"}]} -# This file was automatically generated by gh-aw (v0.82.10). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"6c3b9dc8d0f7b54d44175db209cda34e37ea8c635d01ee0a5cba13675053f6cd","body_hash":"81c8287f5691cdc10ae8f60c004bb671d9b4942740d73fcc9646e28fbcd8790e","compiler_version":"v0.83.1","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.73"}} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"8bdba8075360648fe6802302a5b4e016361dc6ac","version":"v0.83.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38","digest":"sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38","digest":"sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38","digest":"sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.3","digest":"sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]} +# This file was automatically generated by gh-aw (v0.83.1). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ # / _ \ | | (_) @@ -39,17 +39,17 @@ # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 # - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 -# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 +# - actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 +# - github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04 -# - ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3 -# - ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32 +# - ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c +# - ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 +# - ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83 # - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b -# - ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 +# - ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3 name: "Documentation Handler" on: @@ -119,7 +119,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -127,8 +127,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Documentation Handler" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-documentation.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} - name: Resolve host repo for activation checkout @@ -156,16 +156,16 @@ jobs: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AGENT_VERSION: "1.0.70" - GH_AW_INFO_CLI_VERSION: "v0.82.10" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AGENT_VERSION: "1.0.73" + GH_AW_INFO_CLI_VERSION: "v0.83.1" GH_AW_INFO_WORKFLOW_NAME: "Documentation Handler" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -273,7 +273,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.82.10" + GH_AW_COMPILED_VERSION: "v0.83.1" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -374,7 +374,7 @@ jobs: GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} GH_AW_INPUTS_ISSUE_NUMBER: ${{ inputs.issue_number }} - GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' + GH_AW_MCP_CLI_SERVERS_LIST: "- `github` — run `github --help` to see available tools\n- `safeoutputs` — run `safeoutputs --help` to see available tools" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -458,6 +458,7 @@ jobs: has_patch: ${{ steps.collect_output.outputs.has_patch }} http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }} inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }} + invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }} mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }} model: ${{ needs.activation.outputs.model }} model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }} @@ -470,7 +471,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -479,8 +480,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Documentation Handler" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-documentation.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} - name: Set runtime paths @@ -527,11 +528,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.70 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.35 --rootless + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38 --rootless - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 @@ -566,7 +567,7 @@ jobs: GH_AW_SKILL_DIR: ".github/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04 ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3 ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3 - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" @@ -736,17 +737,18 @@ jobs: MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') source "${RUNNER_TEMP}/gh-aw/actions/resolve_docker_socket_gid.sh" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.1' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.3' mkdir -p "$HOME/.copilot" GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_e85305aae15b8db5_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_9424afbf8aa8e8ef_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.5.0", + "container": "ghcr.io/github/github-mcp-server:v1.6.0", "env": { + "GITHUB_FEATURES": "fields_param", "GITHUB_HOST": "${GITHUB_SERVER_URL}", "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_MCP_SERVER_TOKEN}", "GITHUB_READ_ONLY": "1", @@ -800,10 +802,11 @@ jobs: "port": $MCP_GATEWAY_PORT, "domain": "${MCP_GATEWAY_DOMAIN}", "apiKey": "${MCP_GATEWAY_API_KEY}", - "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}", + "startupTimeout": 120 } } - GH_AW_MCP_CONFIG_e85305aae15b8db5_EOF + GH_AW_MCP_CONFIG_9424afbf8aa8e8ef_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -832,7 +835,7 @@ jobs: run: | set -o pipefail printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt - trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT mkdir -p "$HOME/.copilot" printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" export XDG_CONFIG_HOME="$HOME" @@ -843,7 +846,7 @@ jobs: export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.35/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.35,squid=sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3,agent=sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed,agent-act=sha256:b00340a7b09c917c522cb806af6da1d12f2146e25a4a6198f1589b0116aee992,api-proxy=sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04,cli-proxy=sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST="" @@ -876,7 +879,7 @@ jobs: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_TIMEOUT_MINUTES: 5 - GH_AW_VERSION: v0.82.10 + GH_AW_VERSION: v0.83.1 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1067,7 +1070,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1076,8 +1079,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Documentation Handler" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-documentation.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} - name: Download agent output artifact @@ -1319,7 +1322,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1328,8 +1331,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Documentation Handler" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-documentation.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} - name: Download agent output artifact @@ -1357,7 +1360,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04 ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 - name: Check if detection needed id: detection_guard if: always() @@ -1415,16 +1418,16 @@ jobs: mkdir -p /tmp/gh-aw/threat-detection touch /tmp/gh-aw/threat-detection/detection.log - name: Setup Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.70 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.35 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true @@ -1434,7 +1437,7 @@ jobs: run: | set -o pipefail printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt - trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT mkdir -p "$HOME/.copilot" printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" export XDG_CONFIG_HOME="$HOME" @@ -1444,7 +1447,7 @@ jobs: export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.35/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.35,squid=sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3,agent=sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed,agent-act=sha256:b00340a7b09c917c522cb806af6da1d12f2146e25a4a6198f1589b0116aee992,api-proxy=sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04,cli-proxy=sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST="" @@ -1478,7 +1481,7 @@ jobs: GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_TIMEOUT_MINUTES: 20 - GH_AW_VERSION: v0.82.10 + GH_AW_VERSION: v0.83.1 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1569,7 +1572,7 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.70" + GH_AW_ENGINE_VERSION: "1.0.73" GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} GH_AW_WORKFLOW_ID: "handle-documentation" @@ -1587,7 +1590,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1596,8 +1599,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Documentation Handler" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-documentation.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} - name: Download agent output artifact diff --git a/.github/workflows/handle-enhancement.lock.yml b/.github/workflows/handle-enhancement.lock.yml index ac4b65a73..594320387 100644 --- a/.github/workflows/handle-enhancement.lock.yml +++ b/.github/workflows/handle-enhancement.lock.yml @@ -1,6 +1,6 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"f194730258943dec72121a119dba066ff5fee588d79f69fddddd4ca29b56ffd4","body_hash":"624219976b9b7078c6bb11c4177925478cfd8316fe8de535a581bdd176eda825","compiler_version":"v0.82.10","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.70"}} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"05205436a78512d71a2d842e46586ed05f4fa058","version":"v0.82.10"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35","digest":"sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35","digest":"sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35","digest":"sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.1","digest":"sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.5.0","digest":"sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4","pinned_image":"ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4"}]} -# This file was automatically generated by gh-aw (v0.82.10). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"f194730258943dec72121a119dba066ff5fee588d79f69fddddd4ca29b56ffd4","body_hash":"624219976b9b7078c6bb11c4177925478cfd8316fe8de535a581bdd176eda825","compiler_version":"v0.83.1","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.73"}} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"8bdba8075360648fe6802302a5b4e016361dc6ac","version":"v0.83.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38","digest":"sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38","digest":"sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38","digest":"sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.3","digest":"sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]} +# This file was automatically generated by gh-aw (v0.83.1). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ # / _ \ | | (_) @@ -39,17 +39,17 @@ # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 # - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 -# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 +# - actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 +# - github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04 -# - ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3 -# - ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32 +# - ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c +# - ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 +# - ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83 # - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b -# - ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 +# - ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3 name: "Enhancement Handler" on: @@ -119,7 +119,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -127,8 +127,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Enhancement Handler" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-enhancement.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} - name: Resolve host repo for activation checkout @@ -156,16 +156,16 @@ jobs: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AGENT_VERSION: "1.0.70" - GH_AW_INFO_CLI_VERSION: "v0.82.10" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AGENT_VERSION: "1.0.73" + GH_AW_INFO_CLI_VERSION: "v0.83.1" GH_AW_INFO_WORKFLOW_NAME: "Enhancement Handler" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -273,7 +273,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.82.10" + GH_AW_COMPILED_VERSION: "v0.83.1" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -374,7 +374,7 @@ jobs: GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} GH_AW_INPUTS_ISSUE_NUMBER: ${{ inputs.issue_number }} - GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' + GH_AW_MCP_CLI_SERVERS_LIST: "- `github` — run `github --help` to see available tools\n- `safeoutputs` — run `safeoutputs --help` to see available tools" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -458,6 +458,7 @@ jobs: has_patch: ${{ steps.collect_output.outputs.has_patch }} http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }} inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }} + invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }} mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }} model: ${{ needs.activation.outputs.model }} model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }} @@ -470,7 +471,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -479,8 +480,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Enhancement Handler" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-enhancement.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} - name: Set runtime paths @@ -527,11 +528,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.70 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.35 --rootless + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38 --rootless - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 @@ -566,7 +567,7 @@ jobs: GH_AW_SKILL_DIR: ".github/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04 ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3 ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3 - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" @@ -736,17 +737,18 @@ jobs: MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') source "${RUNNER_TEMP}/gh-aw/actions/resolve_docker_socket_gid.sh" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.1' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.3' mkdir -p "$HOME/.copilot" GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_e85305aae15b8db5_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_9424afbf8aa8e8ef_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.5.0", + "container": "ghcr.io/github/github-mcp-server:v1.6.0", "env": { + "GITHUB_FEATURES": "fields_param", "GITHUB_HOST": "${GITHUB_SERVER_URL}", "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_MCP_SERVER_TOKEN}", "GITHUB_READ_ONLY": "1", @@ -800,10 +802,11 @@ jobs: "port": $MCP_GATEWAY_PORT, "domain": "${MCP_GATEWAY_DOMAIN}", "apiKey": "${MCP_GATEWAY_API_KEY}", - "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}", + "startupTimeout": 120 } } - GH_AW_MCP_CONFIG_e85305aae15b8db5_EOF + GH_AW_MCP_CONFIG_9424afbf8aa8e8ef_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -832,7 +835,7 @@ jobs: run: | set -o pipefail printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt - trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT mkdir -p "$HOME/.copilot" printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" export XDG_CONFIG_HOME="$HOME" @@ -843,7 +846,7 @@ jobs: export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.35/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.35,squid=sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3,agent=sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed,agent-act=sha256:b00340a7b09c917c522cb806af6da1d12f2146e25a4a6198f1589b0116aee992,api-proxy=sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04,cli-proxy=sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST="" @@ -876,7 +879,7 @@ jobs: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_TIMEOUT_MINUTES: 5 - GH_AW_VERSION: v0.82.10 + GH_AW_VERSION: v0.83.1 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1067,7 +1070,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1076,8 +1079,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Enhancement Handler" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-enhancement.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} - name: Download agent output artifact @@ -1319,7 +1322,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1328,8 +1331,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Enhancement Handler" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-enhancement.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} - name: Download agent output artifact @@ -1357,7 +1360,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04 ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 - name: Check if detection needed id: detection_guard if: always() @@ -1415,16 +1418,16 @@ jobs: mkdir -p /tmp/gh-aw/threat-detection touch /tmp/gh-aw/threat-detection/detection.log - name: Setup Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.70 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.35 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true @@ -1434,7 +1437,7 @@ jobs: run: | set -o pipefail printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt - trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT mkdir -p "$HOME/.copilot" printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" export XDG_CONFIG_HOME="$HOME" @@ -1444,7 +1447,7 @@ jobs: export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.35/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.35,squid=sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3,agent=sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed,agent-act=sha256:b00340a7b09c917c522cb806af6da1d12f2146e25a4a6198f1589b0116aee992,api-proxy=sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04,cli-proxy=sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST="" @@ -1478,7 +1481,7 @@ jobs: GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_TIMEOUT_MINUTES: 20 - GH_AW_VERSION: v0.82.10 + GH_AW_VERSION: v0.83.1 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1569,7 +1572,7 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.70" + GH_AW_ENGINE_VERSION: "1.0.73" GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} GH_AW_WORKFLOW_ID: "handle-enhancement" @@ -1587,7 +1590,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1596,8 +1599,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Enhancement Handler" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-enhancement.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} - name: Download agent output artifact diff --git a/.github/workflows/handle-question.lock.yml b/.github/workflows/handle-question.lock.yml index 5c2a4b849..0093edce0 100644 --- a/.github/workflows/handle-question.lock.yml +++ b/.github/workflows/handle-question.lock.yml @@ -1,6 +1,6 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"92158ba4f7e373cb65457b060c7645569b32c756c13c025837491f6809cf694f","body_hash":"1bdd19aae2095beb6e3fcf7af755cd102d424de3a8727ef6e4674815950c7e8b","compiler_version":"v0.82.10","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.70"}} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"05205436a78512d71a2d842e46586ed05f4fa058","version":"v0.82.10"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35","digest":"sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35","digest":"sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35","digest":"sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.1","digest":"sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.5.0","digest":"sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4","pinned_image":"ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4"}]} -# This file was automatically generated by gh-aw (v0.82.10). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"92158ba4f7e373cb65457b060c7645569b32c756c13c025837491f6809cf694f","body_hash":"1bdd19aae2095beb6e3fcf7af755cd102d424de3a8727ef6e4674815950c7e8b","compiler_version":"v0.83.1","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.73"}} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"8bdba8075360648fe6802302a5b4e016361dc6ac","version":"v0.83.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38","digest":"sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38","digest":"sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38","digest":"sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.3","digest":"sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]} +# This file was automatically generated by gh-aw (v0.83.1). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ # / _ \ | | (_) @@ -39,17 +39,17 @@ # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 # - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 -# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 +# - actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 +# - github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04 -# - ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3 -# - ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32 +# - ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c +# - ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 +# - ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83 # - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b -# - ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 +# - ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3 name: "Question Handler" on: @@ -119,7 +119,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -127,8 +127,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Question Handler" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-question.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} - name: Resolve host repo for activation checkout @@ -156,16 +156,16 @@ jobs: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AGENT_VERSION: "1.0.70" - GH_AW_INFO_CLI_VERSION: "v0.82.10" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AGENT_VERSION: "1.0.73" + GH_AW_INFO_CLI_VERSION: "v0.83.1" GH_AW_INFO_WORKFLOW_NAME: "Question Handler" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -273,7 +273,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.82.10" + GH_AW_COMPILED_VERSION: "v0.83.1" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -374,7 +374,7 @@ jobs: GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} GH_AW_INPUTS_ISSUE_NUMBER: ${{ inputs.issue_number }} - GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' + GH_AW_MCP_CLI_SERVERS_LIST: "- `github` — run `github --help` to see available tools\n- `safeoutputs` — run `safeoutputs --help` to see available tools" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -458,6 +458,7 @@ jobs: has_patch: ${{ steps.collect_output.outputs.has_patch }} http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }} inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }} + invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }} mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }} model: ${{ needs.activation.outputs.model }} model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }} @@ -470,7 +471,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -479,8 +480,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Question Handler" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-question.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} - name: Set runtime paths @@ -527,11 +528,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.70 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.35 --rootless + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38 --rootless - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 @@ -566,7 +567,7 @@ jobs: GH_AW_SKILL_DIR: ".github/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04 ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3 ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3 - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" @@ -736,17 +737,18 @@ jobs: MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') source "${RUNNER_TEMP}/gh-aw/actions/resolve_docker_socket_gid.sh" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.1' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.3' mkdir -p "$HOME/.copilot" GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_e85305aae15b8db5_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_9424afbf8aa8e8ef_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.5.0", + "container": "ghcr.io/github/github-mcp-server:v1.6.0", "env": { + "GITHUB_FEATURES": "fields_param", "GITHUB_HOST": "${GITHUB_SERVER_URL}", "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_MCP_SERVER_TOKEN}", "GITHUB_READ_ONLY": "1", @@ -800,10 +802,11 @@ jobs: "port": $MCP_GATEWAY_PORT, "domain": "${MCP_GATEWAY_DOMAIN}", "apiKey": "${MCP_GATEWAY_API_KEY}", - "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}", + "startupTimeout": 120 } } - GH_AW_MCP_CONFIG_e85305aae15b8db5_EOF + GH_AW_MCP_CONFIG_9424afbf8aa8e8ef_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -832,7 +835,7 @@ jobs: run: | set -o pipefail printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt - trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT mkdir -p "$HOME/.copilot" printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" export XDG_CONFIG_HOME="$HOME" @@ -843,7 +846,7 @@ jobs: export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.35/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.35,squid=sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3,agent=sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed,agent-act=sha256:b00340a7b09c917c522cb806af6da1d12f2146e25a4a6198f1589b0116aee992,api-proxy=sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04,cli-proxy=sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST="" @@ -876,7 +879,7 @@ jobs: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_TIMEOUT_MINUTES: 5 - GH_AW_VERSION: v0.82.10 + GH_AW_VERSION: v0.83.1 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1067,7 +1070,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1076,8 +1079,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Question Handler" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-question.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} - name: Download agent output artifact @@ -1319,7 +1322,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1328,8 +1331,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Question Handler" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-question.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} - name: Download agent output artifact @@ -1357,7 +1360,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04 ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 - name: Check if detection needed id: detection_guard if: always() @@ -1415,16 +1418,16 @@ jobs: mkdir -p /tmp/gh-aw/threat-detection touch /tmp/gh-aw/threat-detection/detection.log - name: Setup Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.70 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.35 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true @@ -1434,7 +1437,7 @@ jobs: run: | set -o pipefail printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt - trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT mkdir -p "$HOME/.copilot" printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" export XDG_CONFIG_HOME="$HOME" @@ -1444,7 +1447,7 @@ jobs: export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.35/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.35,squid=sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3,agent=sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed,agent-act=sha256:b00340a7b09c917c522cb806af6da1d12f2146e25a4a6198f1589b0116aee992,api-proxy=sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04,cli-proxy=sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST="" @@ -1478,7 +1481,7 @@ jobs: GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_TIMEOUT_MINUTES: 20 - GH_AW_VERSION: v0.82.10 + GH_AW_VERSION: v0.83.1 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1569,7 +1572,7 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.70" + GH_AW_ENGINE_VERSION: "1.0.73" GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} GH_AW_WORKFLOW_ID: "handle-question" @@ -1587,7 +1590,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1596,8 +1599,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Question Handler" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/handle-question.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} - name: Download agent output artifact diff --git a/.github/workflows/issue-classification.lock.yml b/.github/workflows/issue-classification.lock.yml index e06af3631..041fb94b7 100644 --- a/.github/workflows/issue-classification.lock.yml +++ b/.github/workflows/issue-classification.lock.yml @@ -1,6 +1,6 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"797f7487a67c2fa4465cb3fd31e17c9f0620bb232b2a4fb693c62cb76d5d5a36","body_hash":"8e7ac9b7bb6ab07630a10a4a016108ba59f70feadf82a7391ca0ba5504e14bff","compiler_version":"v0.82.10","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.70"}} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"05205436a78512d71a2d842e46586ed05f4fa058","version":"v0.82.10"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35","digest":"sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35","digest":"sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35","digest":"sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.1","digest":"sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.5.0","digest":"sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4","pinned_image":"ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4"}]} -# This file was automatically generated by gh-aw (v0.82.10). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"797f7487a67c2fa4465cb3fd31e17c9f0620bb232b2a4fb693c62cb76d5d5a36","body_hash":"8e7ac9b7bb6ab07630a10a4a016108ba59f70feadf82a7391ca0ba5504e14bff","compiler_version":"v0.83.1","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.73"}} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"8bdba8075360648fe6802302a5b4e016361dc6ac","version":"v0.83.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38","digest":"sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38","digest":"sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38","digest":"sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.3","digest":"sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]} +# This file was automatically generated by gh-aw (v0.83.1). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ # / _ \ | | (_) @@ -39,17 +39,17 @@ # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 # - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 -# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 +# - actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 +# - github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04 -# - ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3 -# - ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32 +# - ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c +# - ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 +# - ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83 # - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b -# - ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 +# - ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3 name: "Issue Classification Agent" on: @@ -105,7 +105,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -113,8 +113,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Issue Classification Agent" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-classification.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Generate agentic run info id: generate_aw_info @@ -122,16 +122,16 @@ jobs: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AGENT_VERSION: "1.0.70" - GH_AW_INFO_CLI_VERSION: "v0.82.10" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AGENT_VERSION: "1.0.73" + GH_AW_INFO_CLI_VERSION: "v0.83.1" GH_AW_INFO_WORKFLOW_NAME: "Issue Classification Agent" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -229,7 +229,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.82.10" + GH_AW_COMPILED_VERSION: "v0.83.1" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -348,7 +348,7 @@ jobs: GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} GH_AW_INPUTS_ISSUE_NUMBER: ${{ inputs.issue_number }} - GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' + GH_AW_MCP_CLI_SERVERS_LIST: "- `github` — run `github --help` to see available tools\n- `safeoutputs` — run `safeoutputs --help` to see available tools" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -430,6 +430,7 @@ jobs: has_patch: ${{ steps.collect_output.outputs.has_patch }} http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }} inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }} + invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }} mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }} model: ${{ needs.activation.outputs.model }} model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }} @@ -442,7 +443,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -451,8 +452,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Issue Classification Agent" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-classification.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Set runtime paths id: set-runtime-paths @@ -498,11 +499,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.70 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.35 --rootless + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38 --rootless - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 @@ -537,7 +538,7 @@ jobs: GH_AW_SKILL_DIR: ".github/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04 ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3 ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3 - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" @@ -799,17 +800,18 @@ jobs: MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') source "${RUNNER_TEMP}/gh-aw/actions/resolve_docker_socket_gid.sh" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.1' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.3' mkdir -p "$HOME/.copilot" GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_e85305aae15b8db5_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_9424afbf8aa8e8ef_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.5.0", + "container": "ghcr.io/github/github-mcp-server:v1.6.0", "env": { + "GITHUB_FEATURES": "fields_param", "GITHUB_HOST": "${GITHUB_SERVER_URL}", "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_MCP_SERVER_TOKEN}", "GITHUB_READ_ONLY": "1", @@ -863,10 +865,11 @@ jobs: "port": $MCP_GATEWAY_PORT, "domain": "${MCP_GATEWAY_DOMAIN}", "apiKey": "${MCP_GATEWAY_API_KEY}", - "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}", + "startupTimeout": 120 } } - GH_AW_MCP_CONFIG_e85305aae15b8db5_EOF + GH_AW_MCP_CONFIG_9424afbf8aa8e8ef_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -895,7 +898,7 @@ jobs: run: | set -o pipefail printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt - trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT mkdir -p "$HOME/.copilot" printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" export XDG_CONFIG_HOME="$HOME" @@ -906,7 +909,7 @@ jobs: export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.35/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.35,squid=sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3,agent=sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed,agent-act=sha256:b00340a7b09c917c522cb806af6da1d12f2146e25a4a6198f1589b0116aee992,api-proxy=sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04,cli-proxy=sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST="" @@ -939,7 +942,7 @@ jobs: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_TIMEOUT_MINUTES: 10 - GH_AW_VERSION: v0.82.10 + GH_AW_VERSION: v0.83.1 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1218,7 +1221,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1227,8 +1230,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Issue Classification Agent" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-classification.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output @@ -1469,7 +1472,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1478,8 +1481,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Issue Classification Agent" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-classification.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output @@ -1506,7 +1509,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04 ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 - name: Check if detection needed id: detection_guard if: always() @@ -1564,16 +1567,16 @@ jobs: mkdir -p /tmp/gh-aw/threat-detection touch /tmp/gh-aw/threat-detection/detection.log - name: Setup Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.70 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.35 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true @@ -1583,7 +1586,7 @@ jobs: run: | set -o pipefail printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt - trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT mkdir -p "$HOME/.copilot" printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" export XDG_CONFIG_HOME="$HOME" @@ -1593,7 +1596,7 @@ jobs: export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.35/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.35,squid=sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3,agent=sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed,agent-act=sha256:b00340a7b09c917c522cb806af6da1d12f2146e25a4a6198f1589b0116aee992,api-proxy=sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04,cli-proxy=sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST="" @@ -1627,7 +1630,7 @@ jobs: GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_TIMEOUT_MINUTES: 20 - GH_AW_VERSION: v0.82.10 + GH_AW_VERSION: v0.83.1 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1718,7 +1721,7 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.70" + GH_AW_ENGINE_VERSION: "1.0.73" GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} GH_AW_WORKFLOW_ID: "issue-classification" @@ -1738,7 +1741,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1747,8 +1750,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Issue Classification Agent" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-classification.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output diff --git a/.github/workflows/issue-triage.lock.yml b/.github/workflows/issue-triage.lock.yml index 4f4ef28f1..e24584f26 100644 --- a/.github/workflows/issue-triage.lock.yml +++ b/.github/workflows/issue-triage.lock.yml @@ -1,6 +1,6 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"596392856be0229eb9fd3a16e2de146d2c9f5f8484809a28e50c861f386652af","body_hash":"30994be7c5c23b102c12a56a325ac313e413a2507dff11d0dc695899379bfbd0","compiler_version":"v0.82.10","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.70"}} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"05205436a78512d71a2d842e46586ed05f4fa058","version":"v0.82.10"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35","digest":"sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35","digest":"sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35","digest":"sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.1","digest":"sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.5.0","digest":"sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4","pinned_image":"ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4"}]} -# This file was automatically generated by gh-aw (v0.82.10). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"b343e48e59d56a2461bafa8c08f4f37d4242a56fa662b83c2f0cad16262682e5","body_hash":"30994be7c5c23b102c12a56a325ac313e413a2507dff11d0dc695899379bfbd0","compiler_version":"v0.83.1","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.73"}} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"8bdba8075360648fe6802302a5b4e016361dc6ac","version":"v0.83.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38","digest":"sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38","digest":"sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38","digest":"sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.3","digest":"sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]} +# This file was automatically generated by gh-aw (v0.83.1). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ # / _ \ | | (_) @@ -39,17 +39,17 @@ # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 # - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 -# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 +# - actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 +# - github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04 -# - ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3 -# - ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32 +# - ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c +# - ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 +# - ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83 # - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b -# - ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 +# - ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3 name: "Issue Triage Agent" on: @@ -105,7 +105,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -113,8 +113,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Issue Triage Agent" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-triage.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Generate agentic run info id: generate_aw_info @@ -122,16 +122,16 @@ jobs: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AGENT_VERSION: "1.0.70" - GH_AW_INFO_CLI_VERSION: "v0.82.10" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AGENT_VERSION: "1.0.73" + GH_AW_INFO_CLI_VERSION: "v0.83.1" GH_AW_INFO_WORKFLOW_NAME: "Issue Triage Agent" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -229,7 +229,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.82.10" + GH_AW_COMPILED_VERSION: "v0.83.1" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -348,7 +348,7 @@ jobs: GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} GH_AW_INPUTS_ISSUE_NUMBER: ${{ inputs.issue_number }} - GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' + GH_AW_MCP_CLI_SERVERS_LIST: "- `github` — run `github --help` to see available tools\n- `safeoutputs` — run `safeoutputs --help` to see available tools" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -430,6 +430,7 @@ jobs: has_patch: ${{ steps.collect_output.outputs.has_patch }} http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }} inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }} + invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }} mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }} model: ${{ needs.activation.outputs.model }} model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }} @@ -442,7 +443,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -451,8 +452,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Issue Triage Agent" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-triage.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Set runtime paths id: set-runtime-paths @@ -498,11 +499,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.70 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.35 --rootless + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38 --rootless - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 @@ -529,15 +530,15 @@ jobs: GH_AW_SKILL_DIR: ".github/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04 ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3 ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3 - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_bd9befd4ae64abbb_EOF' - {"add_comment":{"max":2},"add_labels":{"allowed":["bug","enhancement","question","documentation","sdk/dotnet","sdk/go","sdk/java","sdk/nodejs","sdk/python","priority/high","priority/low","testing","security","needs-info","duplicate"],"max":10,"target":"triggering"},"close_issue":{"max":1,"target":"triggering"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"update_issue":{"allow_body":true,"max":1,"target":"triggering"}} - GH_AW_SAFE_OUTPUTS_CONFIG_bd9befd4ae64abbb_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_ee19492f88e4cc0b_EOF' + {"add_comment":{"max":2},"add_labels":{"allowed":["bug","enhancement","question","documentation","sdk/dotnet","sdk/go","sdk/java","sdk/nodejs","sdk/python","priority/high","priority/low","testing","security","needs-info","duplicate"],"issue_intent":true,"max":10,"target":"triggering"},"close_issue":{"issue_intent":true,"max":1,"target":"triggering"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"update_issue":{"allow_body":true,"max":1,"target":"triggering"}} + GH_AW_SAFE_OUTPUTS_CONFIG_ee19492f88e4cc0b_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -549,7 +550,26 @@ jobs: "update_issue": " CONSTRAINTS: Maximum 1 issue(s) can be updated. Target: triggering." }, "repo_params": {}, - "dynamic_tools": [] + "dynamic_tools": [], + "required_field_additions": { + "close_issue": [ + "rationale", + "confidence" + ] + }, + "property_injections": { + "close_issue": { + "state_reason": { + "description": "Optional closing state reason. Omit to use the configured default. Select 'duplicate' together with 'duplicate_of' to mark a native duplicate relationship.", + "enum": [ + "completed", + "not_planned", + "duplicate" + ], + "type": "string" + } + } + } } GH_AW_VALIDATION_JSON: | { @@ -599,12 +619,30 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "confidence": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ], + "x-strip-on-error": true + }, "issue_number": { "optionalPositiveInteger": true }, + "rationale": { + "type": "string", + "sanitize": true, + "maxLength": 280, + "x-strip-on-error": true + }, "repo": { "type": "string", "maxLength": 256 + }, + "suggest": { + "type": "boolean" } } }, @@ -771,17 +809,18 @@ jobs: MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') source "${RUNNER_TEMP}/gh-aw/actions/resolve_docker_socket_gid.sh" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.1' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.3' mkdir -p "$HOME/.copilot" GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_d97c92af15acf38e_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_4d6f106ca28cda70_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.5.0", + "container": "ghcr.io/github/github-mcp-server:v1.6.0", "env": { + "GITHUB_FEATURES": "fields_param", "GITHUB_HOST": "${GITHUB_SERVER_URL}", "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_MCP_SERVER_TOKEN}", "GITHUB_READ_ONLY": "1", @@ -832,10 +871,11 @@ jobs: "port": $MCP_GATEWAY_PORT, "domain": "${MCP_GATEWAY_DOMAIN}", "apiKey": "${MCP_GATEWAY_API_KEY}", - "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}", + "startupTimeout": 120 } } - GH_AW_MCP_CONFIG_d97c92af15acf38e_EOF + GH_AW_MCP_CONFIG_4d6f106ca28cda70_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -864,7 +904,7 @@ jobs: run: | set -o pipefail printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt - trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT mkdir -p "$HOME/.copilot" printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" export XDG_CONFIG_HOME="$HOME" @@ -875,7 +915,7 @@ jobs: export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.35/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.35,squid=sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3,agent=sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed,agent-act=sha256:b00340a7b09c917c522cb806af6da1d12f2146e25a4a6198f1589b0116aee992,api-proxy=sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04,cli-proxy=sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST="" @@ -908,7 +948,7 @@ jobs: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_TIMEOUT_MINUTES: 10 - GH_AW_VERSION: v0.82.10 + GH_AW_VERSION: v0.83.1 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1097,7 +1137,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1106,8 +1146,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Issue Triage Agent" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-triage.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output @@ -1348,7 +1388,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1357,8 +1397,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Issue Triage Agent" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-triage.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output @@ -1385,7 +1425,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04 ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 - name: Check if detection needed id: detection_guard if: always() @@ -1443,16 +1483,16 @@ jobs: mkdir -p /tmp/gh-aw/threat-detection touch /tmp/gh-aw/threat-detection/detection.log - name: Setup Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.70 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.35 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true @@ -1462,7 +1502,7 @@ jobs: run: | set -o pipefail printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt - trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT mkdir -p "$HOME/.copilot" printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" export XDG_CONFIG_HOME="$HOME" @@ -1472,7 +1512,7 @@ jobs: export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.35/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.35,squid=sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3,agent=sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed,agent-act=sha256:b00340a7b09c917c522cb806af6da1d12f2146e25a4a6198f1589b0116aee992,api-proxy=sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04,cli-proxy=sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST="" @@ -1506,7 +1546,7 @@ jobs: GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_TIMEOUT_MINUTES: 20 - GH_AW_VERSION: v0.82.10 + GH_AW_VERSION: v0.83.1 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1597,7 +1637,7 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.70" + GH_AW_ENGINE_VERSION: "1.0.73" GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} GH_AW_WORKFLOW_ID: "issue-triage" @@ -1615,7 +1655,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1624,8 +1664,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Issue Triage Agent" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-triage.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output @@ -1659,7 +1699,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":2},\"add_labels\":{\"allowed\":[\"bug\",\"enhancement\",\"question\",\"documentation\",\"sdk/dotnet\",\"sdk/go\",\"sdk/java\",\"sdk/nodejs\",\"sdk/python\",\"priority/high\",\"priority/low\",\"testing\",\"security\",\"needs-info\",\"duplicate\"],\"max\":10,\"target\":\"triggering\"},\"close_issue\":{\"max\":1,\"target\":\"triggering\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{},\"update_issue\":{\"allow_body\":true,\"max\":1,\"target\":\"triggering\"}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":2},\"add_labels\":{\"allowed\":[\"bug\",\"enhancement\",\"question\",\"documentation\",\"sdk/dotnet\",\"sdk/go\",\"sdk/java\",\"sdk/nodejs\",\"sdk/python\",\"priority/high\",\"priority/low\",\"testing\",\"security\",\"needs-info\",\"duplicate\"],\"issue_intent\":true,\"max\":10,\"target\":\"triggering\"},\"close_issue\":{\"issue_intent\":true,\"max\":1,\"target\":\"triggering\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{},\"update_issue\":{\"allow_body\":true,\"max\":1,\"target\":\"triggering\"}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/issue-triage.md b/.github/workflows/issue-triage.md index c4f774c0d..3f5803b56 100644 --- a/.github/workflows/issue-triage.md +++ b/.github/workflows/issue-triage.md @@ -25,10 +25,12 @@ safe-outputs: allowed: [bug, enhancement, question, documentation, sdk/dotnet, sdk/go, sdk/java, sdk/nodejs, sdk/python, priority/high, priority/low, testing, security, needs-info, duplicate] max: 10 target: triggering + issue-intent: true update-issue: target: triggering close-issue: target: triggering + issue-intent: true timeout-minutes: 10 --- diff --git a/.github/workflows/java-adapt-handwritten-code-to-accept-upgrade-changes.lock.yml b/.github/workflows/java-adapt-handwritten-code-to-accept-upgrade-changes.lock.yml index 3a7335922..e94e0775c 100644 --- a/.github/workflows/java-adapt-handwritten-code-to-accept-upgrade-changes.lock.yml +++ b/.github/workflows/java-adapt-handwritten-code-to-accept-upgrade-changes.lock.yml @@ -1,6 +1,6 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"a5f19a89f89b0693f86ca89ea90e3a633fe19c17bf4d27214fa9124429cdc156","body_hash":"41bc10df4ac9179064417476fbb39fef7423d0998dc0beb7bad42e9eb7ab0494","compiler_version":"v0.82.10","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.70"}} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"05205436a78512d71a2d842e46586ed05f4fa058","version":"v0.82.10"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35","digest":"sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35","digest":"sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35","digest":"sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.1","digest":"sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.5.0","digest":"sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4","pinned_image":"ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4"}]} -# This file was automatically generated by gh-aw (v0.82.10). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"a5f19a89f89b0693f86ca89ea90e3a633fe19c17bf4d27214fa9124429cdc156","body_hash":"8db09798070cbcba22c42c50a316ae45c8e8c650eeb23c556b44fde8d519550a","compiler_version":"v0.83.1","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.73"}} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"8bdba8075360648fe6802302a5b4e016361dc6ac","version":"v0.83.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38","digest":"sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38","digest":"sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38","digest":"sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.3","digest":"sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]} +# This file was automatically generated by gh-aw (v0.83.1). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ # / _ \ | | (_) @@ -42,17 +42,17 @@ # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 # - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 -# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 +# - actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 +# - github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04 -# - ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3 -# - ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32 +# - ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c +# - ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 +# - ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83 # - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b -# - ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 +# - ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3 name: "Java Handwritten Code Adaptation After CLI Upgrade" on: @@ -105,7 +105,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -113,8 +113,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Java Handwritten Code Adaptation After CLI Upgrade" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/java-adapt-handwritten-code-to-accept-upgrade-changes.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Generate agentic run info id: generate_aw_info @@ -122,16 +122,16 @@ jobs: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AGENT_VERSION: "1.0.70" - GH_AW_INFO_CLI_VERSION: "v0.82.10" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AGENT_VERSION: "1.0.73" + GH_AW_INFO_CLI_VERSION: "v0.83.1" GH_AW_INFO_WORKFLOW_NAME: "Java Handwritten Code Adaptation After CLI Upgrade" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","github"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -229,7 +229,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.82.10" + GH_AW_COMPILED_VERSION: "v0.83.1" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -336,7 +336,7 @@ jobs: GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} GH_AW_INPUTS_BRANCH: ${{ inputs.branch }} GH_AW_INPUTS_PR_NUMBER: ${{ inputs.pr_number }} - GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' + GH_AW_MCP_CLI_SERVERS_LIST: "- `github` — run `github --help` to see available tools\n- `safeoutputs` — run `safeoutputs --help` to see available tools" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -416,6 +416,7 @@ jobs: has_patch: ${{ steps.collect_output.outputs.has_patch }} http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }} inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }} + invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }} mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }} model: ${{ needs.activation.outputs.model }} model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }} @@ -428,7 +429,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -437,8 +438,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Java Handwritten Code Adaptation After CLI Upgrade" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/java-adapt-handwritten-code-to-accept-upgrade-changes.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Set runtime paths id: set-runtime-paths @@ -484,11 +485,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.70 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.35 --rootless + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38 --rootless - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 @@ -515,7 +516,7 @@ jobs: GH_AW_SKILL_DIR: ".github/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04 ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3 ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3 - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" @@ -689,17 +690,18 @@ jobs: MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') source "${RUNNER_TEMP}/gh-aw/actions/resolve_docker_socket_gid.sh" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.1' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.3' mkdir -p "$HOME/.copilot" GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_89bf9ba8e0ab7f74_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_58d53a00b5a25078_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.5.0", + "container": "ghcr.io/github/github-mcp-server:v1.6.0", "env": { + "GITHUB_FEATURES": "fields_param", "GITHUB_HOST": "${GITHUB_SERVER_URL}", "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_MCP_SERVER_TOKEN}", "GITHUB_READ_ONLY": "1", @@ -750,10 +752,11 @@ jobs: "port": $MCP_GATEWAY_PORT, "domain": "${MCP_GATEWAY_DOMAIN}", "apiKey": "${MCP_GATEWAY_API_KEY}", - "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}", + "startupTimeout": 120 } } - GH_AW_MCP_CONFIG_89bf9ba8e0ab7f74_EOF + GH_AW_MCP_CONFIG_58d53a00b5a25078_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -782,7 +785,7 @@ jobs: run: | set -o pipefail printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt - trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT mkdir -p "$HOME/.copilot" printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" export XDG_CONFIG_HOME="$HOME" @@ -793,7 +796,7 @@ jobs: export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.35/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"*.githubusercontent.com\",\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"codeload.github.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"docs.github.com\",\"github-cloud.githubusercontent.com\",\"github-cloud.s3.amazonaws.com\",\"github.blog\",\"github.com\",\"github.githubassets.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"lfs.github.com\",\"objects.githubusercontent.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"patch-diff.githubusercontent.com\",\"patchdiff.githubusercontent.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.35,squid=sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3,agent=sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed,agent-act=sha256:b00340a7b09c917c522cb806af6da1d12f2146e25a4a6198f1589b0116aee992,api-proxy=sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04,cli-proxy=sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"*.githubusercontent.com\",\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"codeload.github.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"docs.github.com\",\"github-cloud.githubusercontent.com\",\"github-cloud.s3.amazonaws.com\",\"github.blog\",\"github.com\",\"github.githubassets.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"lfs.github.com\",\"objects.githubusercontent.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"patch-diff.githubusercontent.com\",\"patchdiff.githubusercontent.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST="" @@ -826,7 +829,7 @@ jobs: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_TIMEOUT_MINUTES: 60 - GH_AW_VERSION: v0.82.10 + GH_AW_VERSION: v0.83.1 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1015,7 +1018,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1024,8 +1027,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Java Handwritten Code Adaptation After CLI Upgrade" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/java-adapt-handwritten-code-to-accept-upgrade-changes.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output @@ -1268,7 +1271,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1277,8 +1280,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Java Handwritten Code Adaptation After CLI Upgrade" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/java-adapt-handwritten-code-to-accept-upgrade-changes.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output @@ -1305,7 +1308,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04 ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 - name: Check if detection needed id: detection_guard if: always() @@ -1363,16 +1366,16 @@ jobs: mkdir -p /tmp/gh-aw/threat-detection touch /tmp/gh-aw/threat-detection/detection.log - name: Setup Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.70 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.35 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true @@ -1382,7 +1385,7 @@ jobs: run: | set -o pipefail printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt - trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT mkdir -p "$HOME/.copilot" printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" export XDG_CONFIG_HOME="$HOME" @@ -1392,7 +1395,7 @@ jobs: export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.35/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.35,squid=sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3,agent=sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed,agent-act=sha256:b00340a7b09c917c522cb806af6da1d12f2146e25a4a6198f1589b0116aee992,api-proxy=sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04,cli-proxy=sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST="" @@ -1426,7 +1429,7 @@ jobs: GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_TIMEOUT_MINUTES: 20 - GH_AW_VERSION: v0.82.10 + GH_AW_VERSION: v0.83.1 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1517,7 +1520,7 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.70" + GH_AW_ENGINE_VERSION: "1.0.73" GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} GH_AW_WORKFLOW_ID: "java-adapt-handwritten-code-to-accept-upgrade-changes" @@ -1537,7 +1540,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1546,8 +1549,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Java Handwritten Code Adaptation After CLI Upgrade" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/java-adapt-handwritten-code-to-accept-upgrade-changes.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output diff --git a/.github/workflows/java-adapt-handwritten-code-to-accept-upgrade-changes.md b/.github/workflows/java-adapt-handwritten-code-to-accept-upgrade-changes.md index b1623ff78..dd1bfe2bb 100644 --- a/.github/workflows/java-adapt-handwritten-code-to-accept-upgrade-changes.md +++ b/.github/workflows/java-adapt-handwritten-code-to-accept-upgrade-changes.md @@ -51,17 +51,17 @@ You are an automation agent that fixes handwritten Java SDK source and test code - The branch `${{ inputs.branch }}` already has: - Updated `java/scripts/codegen/package.json` with the new version - - Regenerated `java/src/generated/java/` code that compiles successfully + - Regenerated `java/sdk/src/generated/java/` code that compiles successfully - Updated the Java POM CLI/version pin property - Your job is ONLY to fix **handwritten** code, NOT generated code. ## Boundaries -- ❌ Do NOT edit anything under `java/src/generated/java/` +- ❌ Do NOT edit anything under `java/sdk/src/generated/java/` - ❌ Do NOT edit `java/scripts/codegen/java.ts` -- ❌ Do NOT create or modify tests in the `com.github.copilot.generated` test package (`java/src/test/java/com/github/copilot/sdk/generated/`) -- ✅ DO edit `java/src/main/java/com/github/copilot/sdk/**` -- ✅ DO edit `java/src/test/java/com/github/copilot/sdk/**` (excluding the `generated` subpackage) +- ❌ Do NOT create or modify tests in the `com.github.copilot.generated` test package (`java/sdk/src/test/java/com/github/copilot/sdk/generated/`) +- ✅ DO edit `java/sdk/src/main/java/com/github/copilot/sdk/**` +- ✅ DO edit `java/sdk/src/test/java/com/github/copilot/sdk/**` (excluding the `generated` subpackage) - ✅ DO add new test methods or test classes if new user-facing API surface is introduced ## Instructions @@ -146,7 +146,7 @@ mvn verify -Dskip.test.harness=true If this passes, commit and push: ```bash -git add java/src/main/java java/src/test/java +git add java/sdk/src/main/java java/sdk/src/test/java git commit -m "Fix handwritten Java code for @github/copilot schema changes Adapt constructor calls, enum references, and test assertions to match diff --git a/.github/workflows/java-codegen-check.yml b/.github/workflows/java-codegen-check.yml index e1c11cd6d..f2f452796 100644 --- a/.github/workflows/java-codegen-check.yml +++ b/.github/workflows/java-codegen-check.yml @@ -6,12 +6,12 @@ on: - main paths: - 'java/scripts/codegen/**' - - 'java/src/generated/**' + - 'java/sdk/src/generated/**' - '.github/workflows/java-codegen-check.yml' pull_request: paths: - 'java/scripts/codegen/**' - - 'java/src/generated/**' + - 'java/sdk/src/generated/**' - '.github/workflows/java-codegen-check.yml' workflow_dispatch: diff --git a/.github/workflows/java-codegen-fix.lock.yml b/.github/workflows/java-codegen-fix.lock.yml index 11e1b9184..1d8d28c43 100644 --- a/.github/workflows/java-codegen-fix.lock.yml +++ b/.github/workflows/java-codegen-fix.lock.yml @@ -1,6 +1,6 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"b0390c9ab9beb0d7e106314299e89e486269ab7f64d8489d5021132d79aa6b9b","body_hash":"fe41f8fe1c12cb585cfaefdd755f58d2a84410d9d819cd706a3192ce052e2545","compiler_version":"v0.82.10","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.70"}} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"05205436a78512d71a2d842e46586ed05f4fa058","version":"v0.82.10"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35","digest":"sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35","digest":"sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35","digest":"sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.1","digest":"sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.5.0","digest":"sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4","pinned_image":"ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4"}]} -# This file was automatically generated by gh-aw (v0.82.10). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"b0390c9ab9beb0d7e106314299e89e486269ab7f64d8489d5021132d79aa6b9b","body_hash":"63d6ce13a5131b158ddffb10a469aa59e0fdc2278eec4d8de7f6763e0b6f2ea2","compiler_version":"v0.83.1","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.73"}} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"8bdba8075360648fe6802302a5b4e016361dc6ac","version":"v0.83.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38","digest":"sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38","digest":"sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38","digest":"sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.3","digest":"sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]} +# This file was automatically generated by gh-aw (v0.83.1). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ # / _ \ | | (_) @@ -41,17 +41,17 @@ # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 # - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 -# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 +# - actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 +# - github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04 -# - ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3 -# - ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32 +# - ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c +# - ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 +# - ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83 # - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b -# - ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 +# - ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3 name: "Java Codegen Agentic Fix" on: @@ -108,7 +108,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -116,8 +116,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Java Codegen Agentic Fix" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/java-codegen-fix.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Generate agentic run info id: generate_aw_info @@ -125,16 +125,16 @@ jobs: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AGENT_VERSION: "1.0.70" - GH_AW_INFO_CLI_VERSION: "v0.82.10" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AGENT_VERSION: "1.0.73" + GH_AW_INFO_CLI_VERSION: "v0.83.1" GH_AW_INFO_WORKFLOW_NAME: "Java Codegen Agentic Fix" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","github"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -232,7 +232,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.82.10" + GH_AW_COMPILED_VERSION: "v0.83.1" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -342,7 +342,7 @@ jobs: GH_AW_INPUTS_BRANCH: ${{ inputs.branch }} GH_AW_INPUTS_ERROR_SUMMARY: ${{ inputs.error_summary }} GH_AW_INPUTS_PR_NUMBER: ${{ inputs.pr_number }} - GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' + GH_AW_MCP_CLI_SERVERS_LIST: "- `github` — run `github --help` to see available tools\n- `safeoutputs` — run `safeoutputs --help` to see available tools" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -423,6 +423,7 @@ jobs: has_patch: ${{ steps.collect_output.outputs.has_patch }} http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }} inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }} + invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }} mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }} model: ${{ needs.activation.outputs.model }} model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }} @@ -435,7 +436,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -444,8 +445,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Java Codegen Agentic Fix" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/java-codegen-fix.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Set runtime paths id: set-runtime-paths @@ -491,11 +492,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.70 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.35 --rootless + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38 --rootless - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 @@ -522,7 +523,7 @@ jobs: GH_AW_SKILL_DIR: ".github/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04 ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3 ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3 - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" @@ -696,17 +697,18 @@ jobs: MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') source "${RUNNER_TEMP}/gh-aw/actions/resolve_docker_socket_gid.sh" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.1' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.3' mkdir -p "$HOME/.copilot" GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_89bf9ba8e0ab7f74_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_58d53a00b5a25078_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.5.0", + "container": "ghcr.io/github/github-mcp-server:v1.6.0", "env": { + "GITHUB_FEATURES": "fields_param", "GITHUB_HOST": "${GITHUB_SERVER_URL}", "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_MCP_SERVER_TOKEN}", "GITHUB_READ_ONLY": "1", @@ -757,10 +759,11 @@ jobs: "port": $MCP_GATEWAY_PORT, "domain": "${MCP_GATEWAY_DOMAIN}", "apiKey": "${MCP_GATEWAY_API_KEY}", - "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}", + "startupTimeout": 120 } } - GH_AW_MCP_CONFIG_89bf9ba8e0ab7f74_EOF + GH_AW_MCP_CONFIG_58d53a00b5a25078_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -789,7 +792,7 @@ jobs: run: | set -o pipefail printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt - trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT mkdir -p "$HOME/.copilot" printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" export XDG_CONFIG_HOME="$HOME" @@ -800,7 +803,7 @@ jobs: export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.35/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"*.githubusercontent.com\",\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"codeload.github.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"docs.github.com\",\"github-cloud.githubusercontent.com\",\"github-cloud.s3.amazonaws.com\",\"github.blog\",\"github.com\",\"github.githubassets.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"lfs.github.com\",\"objects.githubusercontent.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"patch-diff.githubusercontent.com\",\"patchdiff.githubusercontent.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.35,squid=sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3,agent=sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed,agent-act=sha256:b00340a7b09c917c522cb806af6da1d12f2146e25a4a6198f1589b0116aee992,api-proxy=sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04,cli-proxy=sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"*.githubusercontent.com\",\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"codeload.github.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"docs.github.com\",\"github-cloud.githubusercontent.com\",\"github-cloud.s3.amazonaws.com\",\"github.blog\",\"github.com\",\"github.githubassets.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"lfs.github.com\",\"objects.githubusercontent.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"patch-diff.githubusercontent.com\",\"patchdiff.githubusercontent.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST="" @@ -833,7 +836,7 @@ jobs: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_TIMEOUT_MINUTES: 60 - GH_AW_VERSION: v0.82.10 + GH_AW_VERSION: v0.83.1 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1022,7 +1025,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1031,8 +1034,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Java Codegen Agentic Fix" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/java-codegen-fix.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output @@ -1275,7 +1278,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1284,8 +1287,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Java Codegen Agentic Fix" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/java-codegen-fix.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output @@ -1312,7 +1315,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04 ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 - name: Check if detection needed id: detection_guard if: always() @@ -1370,16 +1373,16 @@ jobs: mkdir -p /tmp/gh-aw/threat-detection touch /tmp/gh-aw/threat-detection/detection.log - name: Setup Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.70 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.35 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true @@ -1389,7 +1392,7 @@ jobs: run: | set -o pipefail printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt - trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT mkdir -p "$HOME/.copilot" printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" export XDG_CONFIG_HOME="$HOME" @@ -1399,7 +1402,7 @@ jobs: export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.35/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.35,squid=sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3,agent=sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed,agent-act=sha256:b00340a7b09c917c522cb806af6da1d12f2146e25a4a6198f1589b0116aee992,api-proxy=sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04,cli-proxy=sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST="" @@ -1433,7 +1436,7 @@ jobs: GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_TIMEOUT_MINUTES: 20 - GH_AW_VERSION: v0.82.10 + GH_AW_VERSION: v0.83.1 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1524,7 +1527,7 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.70" + GH_AW_ENGINE_VERSION: "1.0.73" GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} GH_AW_WORKFLOW_ID: "java-codegen-fix" @@ -1544,7 +1547,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1553,8 +1556,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Java Codegen Agentic Fix" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/java-codegen-fix.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output diff --git a/.github/workflows/java-codegen-fix.md b/.github/workflows/java-codegen-fix.md index 1fe465f4c..b1dcb1f63 100644 --- a/.github/workflows/java-codegen-fix.md +++ b/.github/workflows/java-codegen-fix.md @@ -54,7 +54,7 @@ You are an automation agent that fixes Java compilation and test failures caused A Dependabot PR bumped the `@github/copilot` npm dependency in `java/scripts/codegen/package.json`. The `java-codegen-check` workflow ran the code generator (`java/scripts/codegen/java.ts`) against the new schemas and `mvn verify` subsequently failed. Your job is to fix **both** the code generator script (if needed) and the handwritten SDK/test source code so the build passes. -**❌❌❌ YOU MUST NEVER EDIT any of the java source code in `java/src/generated/` directly.** ✅✅Rather, the way to affect changes in these files is to change the code generator script and re-generate the classes in `java/src/generated`. +**❌❌❌ YOU MUST NEVER EDIT any of the java source code in `java/sdk/src/generated/` directly.** ✅✅Rather, the way to affect changes in these files is to change the code generator script and re-generate the classes in `java/sdk/src/generated`. The branch to fix is: `${{ inputs.branch }}` The PR number is: `${{ inputs.pr_number }}` @@ -66,7 +66,7 @@ ${{ inputs.error_summary }} ## Architecture overview -The code generator (`java/scripts/codegen/java.ts`) reads JSON schemas from `node_modules/@github/copilot/schemas/` and produces Java source files under `java/src/generated/java/`. These generated types are consumed by handwritten code in `java/src/main/java/` (primarily `CopilotSession.java`) and tested by handwritten tests in `java/src/test/java/`. +The code generator (`java/scripts/codegen/java.ts`) reads JSON schemas from `node_modules/@github/copilot/schemas/` and produces Java source files under `java/sdk/src/generated/java/`. These generated types are consumed by handwritten code in `java/sdk/src/main/java/` (primarily `CopilotSession.java`) and tested by handwritten tests in `java/sdk/src/test/java/`. When `@github/copilot` is bumped, the schemas may change in ways the code generator does not yet handle. Common schema changes include: @@ -126,7 +126,7 @@ Before making fixes, determine whether the failure is caused by: - New schemas exist but no corresponding Java types were generated **(B) Handwritten code referencing old generated type names/shapes.** Signs: -- Compilation errors in `java/src/main/java/` or `java/src/test/java/` referencing types that no longer exist +- Compilation errors in `java/sdk/src/main/java/` or `java/sdk/src/test/java/` referencing types that no longer exist - Test data using old JSON field names Often **both** (A) and (B) apply: the codegen needs fixing first, then handwritten code needs updating. @@ -162,7 +162,7 @@ If the diagnosis shows the code generator does not handle the new schema format: 4. **Verify the generated output** looks reasonable: ```bash - git diff --stat java/src/generated/java/ + git diff --stat java/sdk/src/generated/java/ ``` **You may ONLY modify `java/scripts/codegen/java.ts`.** Do not modify `package.json`, `package-lock.json`, or any other file under `java/scripts/codegen/`. @@ -179,12 +179,12 @@ For each attempt: 2. **Read the generated types** to understand what changed. Check the generated files that the handwritten code references: ```bash # Example: check what a generated type looks like now - cat java/src/generated/java/com/github/copilot/generated/rpc/.java + cat java/sdk/src/generated/java/com/github/copilot/generated/rpc/.java ``` 3. **Fix the affected source files.** You may modify files under: - - `java/src/main/java/` — handwritten SDK source code - - `java/src/test/java/` — handwritten test code + - `java/sdk/src/main/java/` — handwritten SDK source code + - `java/sdk/src/test/java/` — handwritten test code Common fixes: - Update type references from old nested types to new standalone types (e.g. `SessionMcpListResultServersItem` → `McpServer`) @@ -236,12 +236,12 @@ Do **NOT** push broken code. ## Important constraints -- **NEVER** hand-edit files under `java/src/generated/java/` — these are auto-generated. They are updated by running `cd java/scripts/codegen && npx tsx java.ts`. -- **NEVER** modify `java/pom.xml` — build config is not in scope +- **NEVER** hand-edit files under `java/sdk/src/generated/java/` — these are auto-generated. They are updated by running `cd java/scripts/codegen && npx tsx java.ts`. +- **NEVER** modify `java/sdk/pom.xml` — build config is not in scope - **NEVER** modify `java/scripts/codegen/package.json` or `java/scripts/codegen/package-lock.json` — dependency versions are not in scope - **NEVER** modify files under `.github/` — workflow files are not in scope - You **MAY** modify `java/scripts/codegen/java.ts` to fix the code generator -- You **MAY** modify files under `java/src/main/java/` and `java/src/test/java/` to fix handwritten code +- You **MAY** modify files under `java/sdk/src/main/java/` and `java/sdk/src/test/java/` to fix handwritten code - Always run `cd java && mvn spotless:apply` before committing to ensure code formatting - Maximum 3 fix attempts before reporting failure via `noop` - Only push if `mvn verify` passes \ No newline at end of file diff --git a/.github/workflows/java-publish-maven.yml b/.github/workflows/java-publish-maven.yml index 944a0ee38..1744a697e 100644 --- a/.github/workflows/java-publish-maven.yml +++ b/.github/workflows/java-publish-maven.yml @@ -37,6 +37,10 @@ on: type: boolean required: false default: false + outputs: + mavenPublished: + description: "Whether the Java package was published to Maven Central" + value: ${{ jobs.publish-maven.outputs.published }} secrets: JAVA_RELEASE_TOKEN: required: true @@ -80,39 +84,6 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.JAVA_RELEASE_TOKEN }} - - name: Verify JAVA_RELEASE_GITHUB_TOKEN can trigger workflows - run: | - # JAVA_RELEASE_GITHUB_TOKEN is used for: - # - gh workflow run release-changelog.lock.yml (requires actions:write) - # Check the token's OAuth scopes for 'workflow' (classic PAT) or - # attempt a workflow dispatch with a non-existent ref to verify write access - # (fine-grained PAT — these don't expose scopes via X-OAuth-Scopes). - SCOPES=$(gh api -i user 2>&1 | grep -i '^x-oauth-scopes:' | tr '[:upper:]' '[:lower:]' || true) - if echo "$SCOPES" | grep -q 'workflow'; then - echo "JAVA_RELEASE_GITHUB_TOKEN has 'workflow' scope (classic PAT)" - elif [ -z "$SCOPES" ]; then - # Fine-grained PAT: no X-OAuth-Scopes header returned. - # Attempt a workflow dispatch against a non-existent ref. If the token - # has actions:write, the API returns 422 (validation failed on ref). - # If it lacks the permission, the API returns 403. - HTTP_CODE=$(gh api -X POST \ - "repos/${{ github.repository }}/actions/workflows/release-changelog.lock.yml/dispatches" \ - -f ref="preflight-check-nonexistent-ref" \ - -f 'inputs[tag]=preflight-check' \ - --silent -i 2>&1 | head -1 | grep -oE '[0-9]{3}' || echo "000") - if [ "$HTTP_CODE" = "403" ] || [ "$HTTP_CODE" = "000" ]; then - echo "::error::JAVA_RELEASE_GITHUB_TOKEN lacks actions:write permission on ${{ github.repository }}. It cannot trigger the changelog generation workflow." - exit 1 - fi - # 422 = has write access but ref doesn't exist (expected), 204 would mean it dispatched (shouldn't happen with fake ref) - echo "JAVA_RELEASE_GITHUB_TOKEN actions:write access OK (fine-grained PAT, dispatch returned HTTP ${HTTP_CODE})" - else - echo "::error::JAVA_RELEASE_GITHUB_TOKEN lacks 'workflow' scope. Found scopes: ${SCOPES}. It needs this scope to trigger changelog generation via gh workflow run." - exit 1 - fi - env: - GITHUB_TOKEN: ${{ secrets.JAVA_RELEASE_GITHUB_TOKEN }} - publish-maven: name: Publish Java SDK to Maven Central needs: preflight @@ -123,6 +94,7 @@ jobs: working-directory: ./java outputs: version: ${{ steps.versions.outputs.release_version }} + published: ${{ steps.publish-maven.outcome == 'success' }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -199,24 +171,12 @@ jobs: working-directory: ./java run: | VERSION="${{ steps.versions.outputs.release_version }}" - - # Update release version in README.md (supports any version qualifier like -java.N, -java-preview.N, -beta-java.N) - sed -i "s|[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\(-[a-z][a-z0-9-]*\.[0-9][0-9]*\)*|${VERSION}|g" README.md - sed -i "s|copilot-sdk-java:[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\(-[a-z][a-z0-9-]*\.[0-9][0-9]*\)*|copilot-sdk-java:${VERSION}|g" README.md - - # Update snapshot versions in README.md (must run AFTER release version seds - # because the release copilot-sdk-java: pattern partially matches inside snapshot - # strings — the snapshot-specific seds override with the correct DEV_VERSION) DEV_VERSION="${{ steps.versions.outputs.dev_version }}" - sed -i "s|[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\(-[a-z][a-z0-9-]*\.[0-9][0-9]*\)*-SNAPSHOT|${DEV_VERSION}|g" README.md - sed -i "s|copilot-sdk-java:[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\(-[a-z][a-z0-9-]*\.[0-9][0-9]*\)*-SNAPSHOT|copilot-sdk-java:${DEV_VERSION}|g" README.md - - # Update version in jbang-example.java - sed -i "s|copilot-sdk-java:[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\(-[a-z][a-z0-9-]*\.[0-9][0-9]*\)*|copilot-sdk-java:${VERSION}|g" jbang-example.java - sed -i 's|copilot-sdk-java:${project\.version}|copilot-sdk-java:'"${VERSION}"'|g' jbang-example.java + ./scripts/test-update-documentation-versions.sh + ./scripts/update-documentation-versions.sh "$VERSION" "$DEV_VERSION" README.md sdk/jbang-example.java # Commit the documentation changes before release:prepare (requires clean working directory) - git add README.md jbang-example.java + git add README.md sdk/jbang-example.java git commit -m "docs: update version references to ${VERSION}" # Save the commit SHA for potential rollback @@ -239,6 +199,7 @@ jobs: JAVA_GPG_PASSPHRASE: ${{ secrets.JAVA_GPG_PASSPHRASE }} - name: Perform Release and Deploy to Maven Central + id: publish-maven working-directory: ./java run: | mvn -B release:perform \ @@ -260,68 +221,9 @@ jobs: # Also run Maven release:rollback to clean up any partial release state mvn -B release:rollback || true - github-release: - name: Create GitHub Release - needs: [preflight, publish-maven] - if: github.ref == 'refs/heads/main' - runs-on: ubuntu-latest - defaults: - run: - shell: bash - working-directory: ./java - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - fetch-depth: 0 - - name: Create GitHub Release - run: | - VERSION="${{ needs.publish-maven.outputs.version }}" - GROUP_ID="com.github" - ARTIFACT_ID="copilot-sdk-java" - CURRENT_TAG="java/v${VERSION}" - - if gh release view "${CURRENT_TAG}" >/dev/null 2>&1; then - echo "Release ${CURRENT_TAG} already exists. Skipping creation." - exit 0 - fi - - # Generate release notes from template - export VERSION GROUP_ID ARTIFACT_ID - RELEASE_NOTES=$(envsubst < $GITHUB_WORKSPACE/.github/workflows/java.notes.template) - - # Get the previous tag for generating notes - # grep returns exit 1 when no lines match (first release), so - # append "|| true" to prevent pipefail from aborting the script. - PREV_TAG=$(git tag --list 'java/v*' --sort=-version:refname \ - | grep -Fxv "${CURRENT_TAG}" \ - | head -n 1 || true) - - echo "Current tag: ${CURRENT_TAG}" - echo "Previous tag: ${PREV_TAG}" - - # Build the gh release command - GH_ARGS=("${CURRENT_TAG}") - GH_ARGS+=("--title" "GitHub Copilot SDK for Java ${VERSION}") - GH_ARGS+=("--notes" "${RELEASE_NOTES}") - GH_ARGS+=("--generate-notes") - - if [ -n "$PREV_TAG" ]; then - GH_ARGS+=("--notes-start-tag" "$PREV_TAG") - fi - - ${{ inputs.prerelease == true && 'GH_ARGS+=("--prerelease")' || '' }} - - gh release create "${GH_ARGS[@]}" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Trigger changelog generation - run: gh workflow run release-changelog.lock.yml -f tag="java/v${{ needs.publish-maven.outputs.version }}" - env: - GITHUB_TOKEN: ${{ secrets.JAVA_RELEASE_GITHUB_TOKEN }} - deploy-site: name: Deploy Documentation Site - needs: [preflight, publish-maven, github-release] + needs: [preflight, publish-maven] if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest steps: diff --git a/.github/workflows/java-sdk-tests.yml b/.github/workflows/java-sdk-tests.yml index 948a986e5..bd0a34bd2 100644 --- a/.github/workflows/java-sdk-tests.yml +++ b/.github/workflows/java-sdk-tests.yml @@ -10,30 +10,58 @@ on: - ".github/workflows/java-sdk-tests.yml" - ".github/actions/setup-copilot/**" - ".github/actions/java-test-report/**" - pull_request: - types: [opened, synchronize, reopened, ready_for_review] - paths: - - "java/**" - - "test/**" - - ".github/workflows/java-sdk-tests.yml" - - ".github/actions/setup-copilot/**" - - ".github/actions/java-test-report/**" - - "!**/*.md" - - "!**/LICENSE*" - - "!**/.gitignore" - - "!**/.editorconfig" - - "!**/*.png" - - "!**/*.jpg" - - "!**/*.jpeg" - - "!**/*.gif" - - "!**/*.svg" workflow_dispatch: - merge_group: + workflow_call: permissions: contents: read jobs: + java-sdk-inprocess: + name: "Java SDK InProcess Tests" + if: github.event.repository.fork == false + runs-on: ubuntu-latest + defaults: + run: + shell: bash + working-directory: ./java + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 + with: + java-version: "25" + distribution: "microsoft" + cache: "maven" + + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 + with: + node-version: 22 + + - name: Run Java SDK tests (InProcess) + env: + CI: "true" + run: mvn clean verify -Pinprocess + + - name: Generate Test Report Summary + if: always() + uses: ./.github/actions/java-test-report + with: + title: "Copilot Java SDK :: Test Results InProcess" + + - name: Upload test results on failure + if: failure() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: java-test-results-inprocess + path: | + java/sdk/target/surefire-reports/ + java/sdk/target/surefire-reports-isolated/ + java/sdk/target/failsafe-reports/ + retention-days: 7 + java-sdk: name: "Java SDK Tests (JDK ${{ matrix.test-jdk }})" if: github.event.repository.fork == false @@ -62,6 +90,10 @@ jobs: with: node-version: 22 + - name: Test documentation version updater + if: matrix.test-jdk == '25' + run: ./scripts/test-update-documentation-versions.sh + - name: Build SDK and set up test harness run: mvn test-compile jar:jar @@ -109,7 +141,7 @@ jobs: run: | echo "Running tests against JDK 25-built classes using JDK 17 runtime..." java -version - mvn jacoco:prepare-agent@wire-up-coverage-instrumentation antrun:run@print-test-jdk-banner surefire:test failsafe:integration-test failsafe:verify jacoco:report@build-coverage-report-from-tests -Denforcer.skip=true + mvn -pl sdk jacoco:prepare-agent@wire-up-coverage-instrumentation antrun:run@print-test-jdk-banner surefire:test failsafe:integration-test failsafe:verify jacoco:report@build-coverage-report-from-tests -Denforcer.skip=true - name: Upload test results for site generation if: success() && github.ref == 'refs/heads/main' && matrix.test-jdk == '25' @@ -117,9 +149,9 @@ jobs: with: name: test-results-for-site path: | - java/target/jacoco-test-results/sdk-tests.exec - java/target/surefire-reports/ - java/target/surefire-reports-isolated/ + java/sdk/target/jacoco-test-results/sdk-tests.exec + java/sdk/target/surefire-reports/ + java/sdk/target/surefire-reports-isolated/ retention-days: 1 - name: Generate Test Report Summary @@ -134,7 +166,7 @@ jobs: with: name: java-test-results-jdk-${{ matrix.test-jdk }} path: | - java/target/surefire-reports/ - java/target/surefire-reports-isolated/ - java/target/failsafe-reports/ + java/sdk/target/surefire-reports/ + java/sdk/target/surefire-reports-isolated/ + java/sdk/target/failsafe-reports/ retention-days: 7 diff --git a/.github/workflows/java-smoke-test.yml b/.github/workflows/java-smoke-test.yml index cffef0a35..e7e9a417d 100644 --- a/.github/workflows/java-smoke-test.yml +++ b/.github/workflows/java-smoke-test.yml @@ -63,7 +63,7 @@ jobs: The SDK has already been built and installed into the local Maven repository. JDK 17 and Maven are already installed and on PATH. - Execute the prompt at `src/test/prompts/PROMPT-smoke-test.md` with the following critical overrides: + Execute the prompt at `sdk/src/test/prompts/PROMPT-smoke-test.md` with the following critical overrides: **Critical override — disable SNAPSHOT updates (but allow downloads):** The goal of this workflow is to validate the SDK SNAPSHOT that was just built and installed locally, not any newer SNAPSHOT that might exist in a remote repository. To ensure Maven does not download a newer timestamped SNAPSHOT of the SDK while still allowing it to download any missing plugins or dependencies, you must run the smoke-test Maven build without `-U` and with `--no-snapshot-updates`, so that it uses the locally installed SDK artifact. Use `mvn --no-snapshot-updates clean package` instead of `mvn -U clean package` or `mvn -o clean package`. @@ -136,7 +136,7 @@ jobs: The SDK has already been built and installed into the local Maven repository. JDK 25 and Maven are already installed and on PATH. - Execute the prompt at `src/test/prompts/PROMPT-smoke-test.md` with the following critical overrides: + Execute the prompt at `sdk/src/test/prompts/PROMPT-smoke-test.md` with the following critical overrides: **Critical override — disable SNAPSHOT updates (but allow downloads):** The goal of this workflow is to validate the SDK SNAPSHOT that was just built and installed locally, not any newer SNAPSHOT that might exist in a remote repository. To ensure Maven does not download a newer timestamped SNAPSHOT of the SDK while still allowing it to download any missing plugins or dependencies, you must run the smoke-test Maven build without `-U` and with `--no-snapshot-updates`, so that it uses the locally installed SDK artifact. Use `mvn --no-snapshot-updates clean package` instead of `mvn -U clean package` or `mvn -o clean package`. diff --git a/.github/workflows/java.notes.template b/.github/workflows/java.notes.template deleted file mode 100644 index e209a110b..000000000 --- a/.github/workflows/java.notes.template +++ /dev/null @@ -1,29 +0,0 @@ - - -# Installation - -⚠️ **Artifact versioning plan:** Releases of this implementation track releases of the reference implementation. For each release of the reference implementation, there may follow a corresponding release of this implementation with the same number as the reference implementation. Release identifiers of the reference implementation are in the form `vMaj.Min.Micro`. For example v0.1.32. The corresponding maven version for the release will be `Maj.Min.Micro-java.N`, where `Maj`, `Min` and `Micro` are the corresponding numbers for the reference implementation release, and `N` is a monotonically increasing sequence number starting with 0 for each release. See the corresponding architectural decision record for more information in the `docs/adr` directory of the source code. - -📦 [View on Maven Central](https://central.sonatype.com/artifact/${GROUP_ID}/${ARTIFACT_ID}/${VERSION}) - -📖 [Documentation](https://github.github.io/copilot-sdk-java/${VERSION}/) · [Javadoc](https://github.github.io/copilot-sdk-java/${VERSION}/apidocs/index.html) - - -## Maven -```xml - - ${GROUP_ID} - ${ARTIFACT_ID} - ${VERSION} - -``` - -## Gradle (Kotlin DSL) -```kotlin -implementation("${GROUP_ID}:${ARTIFACT_ID}:${VERSION}") -``` - -## Gradle (Groovy DSL) -```groovy -implementation '${GROUP_ID}:${ARTIFACT_ID}:${VERSION}' -``` diff --git a/.github/workflows/nodejs-sdk-tests.yml b/.github/workflows/nodejs-sdk-tests.yml index 647345e0e..4c31f79cc 100644 --- a/.github/workflows/nodejs-sdk-tests.yml +++ b/.github/workflows/nodejs-sdk-tests.yml @@ -7,24 +7,8 @@ on: push: branches: - main - pull_request: - types: [opened, synchronize, reopened, ready_for_review] - paths: - - 'nodejs/**' - - 'test/**' - - '.github/workflows/nodejs-sdk-tests.yml' - - '!nodejs/scripts/**' - - '!**/*.md' - - '!**/LICENSE*' - - '!**/.gitignore' - - '!**/.editorconfig' - - '!**/*.png' - - '!**/*.jpg' - - '!**/*.jpeg' - - '!**/*.gif' - - '!**/*.svg' workflow_dispatch: - merge_group: + workflow_call: permissions: contents: read diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b44fd582a..98bf23690 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -379,7 +379,8 @@ jobs: needs.publish-nodejs.result == 'success' && needs.publish-dotnet.result == 'success' && needs.publish-python.result == 'success' && - needs.publish-rust.result == 'success' + needs.publish-rust.result == 'success' && + needs.publish-java.outputs.mavenPublished == 'true' runs-on: ubuntu-latest permissions: actions: write @@ -434,11 +435,9 @@ jobs: fi env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Tag Rust SDK and create Rust GitHub Release - # Rust gets its own version-scoped GitHub Release with notes - # derived from PR titles since the previous Rust tag. The - # cross-language `vX.Y.Z` release above still exists; this one - # is the canonical reference for Rust users. + - name: Tag Rust SDK + # Keep a language-scoped source tag for traceability. Rust is + # included in the cross-language `vX.Y.Z` GitHub Release. if: github.event.inputs.dist-tag == 'latest' || github.event.inputs.dist-tag == 'prerelease' run: | set -e @@ -453,26 +452,5 @@ jobs: else echo "Tag $TAG_NAME already exists, skipping tag push" fi - # Find the previous Rust tag for note generation. Prefer rust/v*, - # fall back to the historical rust-v* tags from the release-plz era. - PREV_TAG=$(git tag --list 'rust/v*' --sort=-v:refname | grep -vFx "$TAG_NAME" | head -n1) - if [ -z "$PREV_TAG" ]; then - PREV_TAG=$(git tag --list 'rust-v*' --sort=-v:refname | head -n1) - fi - NOTES_FLAG="" - if [ -n "$PREV_TAG" ]; then - NOTES_FLAG="--notes-start-tag $PREV_TAG" - echo "Generating notes from $PREV_TAG..$TAG_NAME" - else - echo "No previous Rust tag found; generating notes from full history" - fi - PRERELEASE_FLAG="" - if [ "${{ github.event.inputs.dist-tag }}" = "prerelease" ]; then - PRERELEASE_FLAG="--prerelease" - fi - gh release create "$TAG_NAME" \ - --title "$TAG_NAME" \ - --generate-notes $NOTES_FLAG $PRERELEASE_FLAG \ - --target ${{ github.sha }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/python-sdk-tests.yml b/.github/workflows/python-sdk-tests.yml index 8d3ea0715..1ea973975 100644 --- a/.github/workflows/python-sdk-tests.yml +++ b/.github/workflows/python-sdk-tests.yml @@ -7,24 +7,8 @@ on: push: branches: - main - pull_request: - types: [opened, synchronize, reopened, ready_for_review] - paths: - - 'python/**' - - 'test/**' - - 'nodejs/package.json' - - '.github/workflows/python-sdk-tests.yml' - - '!**/*.md' - - '!**/LICENSE*' - - '!**/.gitignore' - - '!**/.editorconfig' - - '!**/*.png' - - '!**/*.jpg' - - '!**/*.jpeg' - - '!**/*.gif' - - '!**/*.svg' workflow_dispatch: - merge_group: + workflow_call: permissions: contents: read @@ -95,4 +79,6 @@ jobs: - name: Run Python SDK tests env: COPILOT_HMAC_KEY: ${{ secrets.COPILOT_DEVELOPER_CLI_INTEGRATION_HMAC_KEY }} - run: uv run pytest -v -s + # Keep each module's shared E2E client and proxy on one process while + # running independent modules concurrently in isolated workers. + run: uv run pytest -v -s -n 2 --dist=loadfile diff --git a/.github/workflows/release-changelog.lock.yml b/.github/workflows/release-changelog.lock.yml index f6ea43604..23b19d9b8 100644 --- a/.github/workflows/release-changelog.lock.yml +++ b/.github/workflows/release-changelog.lock.yml @@ -1,6 +1,6 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"9342b428009e6a3b47258c08b78735a89fc72714b73a44b72c4714e310d60006","body_hash":"89e26ed929f440bd6af57d1da92b06dbf1739b4a1d34b9923286919d00f272d1","compiler_version":"v0.82.10","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.70"}} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"05205436a78512d71a2d842e46586ed05f4fa058","version":"v0.82.10"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35","digest":"sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35","digest":"sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35","digest":"sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.1","digest":"sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.5.0","digest":"sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4","pinned_image":"ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4"}]} -# This file was automatically generated by gh-aw (v0.82.10). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"a4a0859e0103be270433c7fe1926a346c46271b4b530b2c08fa5d606d0ab75c4","body_hash":"490b25b529910b1b087df624fd59eaef52e142e84b9503ca1cf87631f4c36b53","compiler_version":"v0.83.1","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.73"}} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"8bdba8075360648fe6802302a5b4e016361dc6ac","version":"v0.83.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38","digest":"sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38","digest":"sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38","digest":"sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.3","digest":"sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]} +# This file was automatically generated by gh-aw (v0.83.1). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ # / _ \ | | (_) @@ -40,17 +40,17 @@ # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 # - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 -# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 +# - actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 +# - github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04 -# - ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3 -# - ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32 +# - ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c +# - ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 +# - ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83 # - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b -# - ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 +# - ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3 name: "Release Changelog Generator" on: @@ -62,7 +62,7 @@ on: required: false type: string tag: - description: Release tag to generate changelog for (e.g., v0.1.30, /v1.0.0) + description: Release tag to generate changelog for (e.g., v1.0.0) required: true type: string @@ -99,7 +99,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -107,8 +107,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Release Changelog Generator" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/release-changelog.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Generate agentic run info id: generate_aw_info @@ -116,16 +116,16 @@ jobs: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AGENT_VERSION: "1.0.70" - GH_AW_INFO_CLI_VERSION: "v0.82.10" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AGENT_VERSION: "1.0.73" + GH_AW_INFO_CLI_VERSION: "v0.83.1" GH_AW_INFO_WORKFLOW_NAME: "Release Changelog Generator" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -223,7 +223,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.82.10" + GH_AW_COMPILED_VERSION: "v0.83.1" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -328,7 +328,7 @@ jobs: GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} - GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' + GH_AW_MCP_CLI_SERVERS_LIST: "- `github` — run `github --help` to see available tools\n- `safeoutputs` — run `safeoutputs --help` to see available tools" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -409,6 +409,7 @@ jobs: has_patch: ${{ steps.collect_output.outputs.has_patch }} http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }} inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }} + invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }} mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }} model: ${{ needs.activation.outputs.model }} model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }} @@ -421,7 +422,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -430,8 +431,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Release Changelog Generator" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/release-changelog.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Set runtime paths id: set-runtime-paths @@ -477,11 +478,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.70 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.35 --rootless + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38 --rootless - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 @@ -508,7 +509,7 @@ jobs: GH_AW_SKILL_DIR: ".github/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04 ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3 ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3 - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" @@ -709,17 +710,18 @@ jobs: MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') source "${RUNNER_TEMP}/gh-aw/actions/resolve_docker_socket_gid.sh" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.1' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.3' mkdir -p "$HOME/.copilot" GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_d97c92af15acf38e_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_4d6f106ca28cda70_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.5.0", + "container": "ghcr.io/github/github-mcp-server:v1.6.0", "env": { + "GITHUB_FEATURES": "fields_param", "GITHUB_HOST": "${GITHUB_SERVER_URL}", "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_MCP_SERVER_TOKEN}", "GITHUB_READ_ONLY": "1", @@ -770,10 +772,11 @@ jobs: "port": $MCP_GATEWAY_PORT, "domain": "${MCP_GATEWAY_DOMAIN}", "apiKey": "${MCP_GATEWAY_API_KEY}", - "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}", + "startupTimeout": 120 } } - GH_AW_MCP_CONFIG_d97c92af15acf38e_EOF + GH_AW_MCP_CONFIG_4d6f106ca28cda70_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -802,7 +805,7 @@ jobs: run: | set -o pipefail printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt - trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT mkdir -p "$HOME/.copilot" printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" export XDG_CONFIG_HOME="$HOME" @@ -813,7 +816,7 @@ jobs: export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.35/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.35,squid=sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3,agent=sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed,agent-act=sha256:b00340a7b09c917c522cb806af6da1d12f2146e25a4a6198f1589b0116aee992,api-proxy=sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04,cli-proxy=sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST="" @@ -846,7 +849,7 @@ jobs: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_TIMEOUT_MINUTES: 15 - GH_AW_VERSION: v0.82.10 + GH_AW_VERSION: v0.83.1 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1035,7 +1038,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1044,8 +1047,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Release Changelog Generator" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/release-changelog.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output @@ -1288,7 +1291,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1297,8 +1300,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Release Changelog Generator" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/release-changelog.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output @@ -1325,7 +1328,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04 ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 - name: Check if detection needed id: detection_guard if: always() @@ -1383,16 +1386,16 @@ jobs: mkdir -p /tmp/gh-aw/threat-detection touch /tmp/gh-aw/threat-detection/detection.log - name: Setup Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.70 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.35 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true @@ -1402,7 +1405,7 @@ jobs: run: | set -o pipefail printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt - trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT mkdir -p "$HOME/.copilot" printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" export XDG_CONFIG_HOME="$HOME" @@ -1412,7 +1415,7 @@ jobs: export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.35/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.35,squid=sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3,agent=sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed,agent-act=sha256:b00340a7b09c917c522cb806af6da1d12f2146e25a4a6198f1589b0116aee992,api-proxy=sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04,cli-proxy=sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST="" @@ -1446,7 +1449,7 @@ jobs: GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_TIMEOUT_MINUTES: 20 - GH_AW_VERSION: v0.82.10 + GH_AW_VERSION: v0.83.1 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1537,7 +1540,7 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.70" + GH_AW_ENGINE_VERSION: "1.0.73" GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} GH_AW_WORKFLOW_ID: "release-changelog" @@ -1555,7 +1558,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1564,8 +1567,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Release Changelog Generator" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/release-changelog.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output diff --git a/.github/workflows/release-changelog.md b/.github/workflows/release-changelog.md index 7a682c56d..c846b0dd3 100644 --- a/.github/workflows/release-changelog.md +++ b/.github/workflows/release-changelog.md @@ -4,7 +4,7 @@ on: workflow_dispatch: inputs: tag: - description: "Release tag to generate changelog for (e.g., v0.1.30, /v1.0.0)" + description: "Release tag to generate changelog for (e.g., v1.0.0)" required: true type: string permissions: @@ -55,9 +55,8 @@ Use the GitHub API to fetch the release corresponding to `${{ github.event.input 2. The **new version** is the release tag: `${{ github.event.inputs.tag }}` 3. Fetch the release metadata to determine if this is a **stable** or **prerelease** release. 4. Determine the **previous version** to diff against: - - **Scoped tags**: If the tag has a language prefix (e.g., `java/v1.0.0` or `rust/v0.2.0`), the previous tag must use the **same prefix**. List tags matching that prefix (e.g., `java/v*` or `rust/v*`) sorted by version and pick the one immediately before the current tag. Only compare within the same scope. - - **For stable releases**: find the previous **stable** release (skip prereleases). Check `CHANGELOG.md` for the most recent version heading matching this scope (`## [vX.Y.Z](...)` for unscoped, `## [java/vX.Y.Z](...)` for Java, `## [rust/vX.Y.Z](...)` for Rust), or fall back to listing releases via the API. This means stable changelogs include ALL changes since the last stable release, even if some were already mentioned in prerelease notes. - - **For prerelease releases**: find the most recent release of **any kind** (stable or prerelease) that precedes this one within the same tag scope. This way prerelease notes only cover what's new since the last release. + - **For stable releases**: find the previous **stable** release (skip prereleases). Check `CHANGELOG.md` for the most recent `## [vX.Y.Z](...)` heading, or fall back to listing releases via the API. This means stable changelogs include ALL changes since the last stable release, even if some were already mentioned in prerelease notes. + - **For prerelease releases**: find the most recent release of **any kind** (stable or prerelease) that precedes this one. This way prerelease notes only cover what's new since the last release. 5. If no previous release exists at all, use the first commit in the repo as the starting point. 6. After identifying the range, verify it by listing the commits in `PREVIOUS_TAG..NEW_TAG`. If the local result still looks suspiciously small or inconsistent, do **not** proceed based on local git alone — use the GitHub tools as the source of truth for the commits and PRs in the release. @@ -68,8 +67,7 @@ Use the GitHub API to fetch the release corresponding to `${{ github.event.input - PR number and title - The PR author - Which SDK(s) were affected (look for prefixes like `[C#]`, `[Python]`, `[Go]`, `[Node]`, `[Java]`, `[Rust]` in the title, or infer from changed files) -3. **For scoped tags** (e.g., `java/v*`, `rust/v*`): only include changes that touch the corresponding language directory (`java/`, `rust/`). Ignore changes to other languages unless they directly affect the scoped SDK. -4. Ignore: +3. Ignore: - Dependabot/bot PRs that only bump internal dependencies (like `Update @github/copilot to ...`) unless they bring user-facing changes - Merge commits with no meaningful content - Preview/prerelease-only changes that were already documented @@ -81,6 +79,10 @@ Separate the changes into two groups: 1. **Highlighted features**: Any interesting new feature or significant improvement that deserves its own section with a description and code snippet(s). Read the PR diff and source code to understand the feature well enough to write about it. 2. **Other changes**: Bug fixes, minor improvements, and smaller features that can be summarized in a single bullet each. +**Format for each highlighted feature** — use an `### Feature:` or `### Fix:` heading, a 1-2 sentence description explaining what it does and why it matters, and at least one short code snippet (max 3 lines). Cover all six SDKs—TypeScript, C#, Go, Python, Java, and Rust—in the combined release notes. Show code examples in the languages whose APIs best illustrate the change, and ensure every user-visible language-specific change appears either as a highlighted feature or under other changes. + +**Format for other changes** — use a single `### Other changes` section with a flat bulleted list. Each bullet has a lowercase prefix (`feature:`, `bugfix:`, `improvement:`) and a one-line description linking to the PR. **However, if there are no highlighted features above it, omit the `### Other changes` heading.** + Only include changes that are **user-visible in the published SDK packages**. Skip anything that only affects docs, CI, build tooling, GitHub workflows, test infrastructure, or other internal-only concerns. Additionally, identify **new contributors** — anyone whose first merged PR to this repo falls within this release range. You can determine this by checking whether the author has any earlier merged PRs in the repository. @@ -90,11 +92,7 @@ Additionally, identify **new contributors** — anyone whose first merged PR to **Skip this step entirely for prerelease releases.** 1. Read the current `CHANGELOG.md` file. -2. Add the new version entry **at the top** of the file, right after the title/header. Use the **full tag** as the version in the heading — e.g., `## [v0.2.3](...)` for unscoped tags, `## [java/v1.0.0](...)` for Java-scoped tags, `## [rust/v0.2.3](...)` for Rust-scoped tags. - -**Format for each highlighted feature** — use an `### Feature:` or `### Fix:` heading, a 1-2 sentence description explaining what it does and why it matters, and at least one short code snippet (max 3 lines). For unscoped releases, focus on **TypeScript** and **C#** as the primary languages; only show Go/Python when giving a list of one-liner equivalents across all languages, or when their usage pattern is meaningfully different. For **scoped releases** (e.g., `java/v*`), show code snippets in the scoped language only (e.g., Java for `java/v*`, Rust for `rust/v*`). - -**Format for other changes** — a single `### Other changes` section with a flat bulleted list. Each bullet has a lowercase prefix (`feature:`, `bugfix:`, `improvement:`) and a one-line description linking to the PR. **However, if there are no highlighted features above it, omit the `### Other changes` heading entirely** — just list the bullets directly under the version heading. +2. Add the new version entry **at the top** of the file, right after the title/header. Use the full tag as the version in the heading, for example `## [v1.0.0](...)`. 3. Use the release's publish date (from the GitHub Release metadata), not today's date. For `workflow_dispatch` runs, fetch the release by tag to get the date. 4. If there are new contributors, add a `### New contributors` section at the end listing each with a link to their first PR: @@ -118,11 +116,6 @@ Use the `create-pull-request` output to submit your changes. The PR should: Use the `update-release` output to replace the auto-generated release notes with your nicely formatted changelog. **Do not include the version heading** (`## [vX.Y.Z](...) (date)`) in the release notes — the release already has a title showing the version. Start directly with the feature sections or other changes list. -**IMPORTANT — Preserving the Installation section:** -The release body may contain an Installation section delimited by `` and `` HTML comments. In the case of Java, this section includes Maven/Gradle dependency snippets and a "View on Maven Central" link. You **MUST** preserve this entire section (from the opening comment through the closing comment, inclusive) exactly as it appears in the existing release body. Place your generated changelog content **after** the Installation section. - -**URL reconstruction:** If the Maven Central URL in the Installation section appears corrupted or contains the word "redacted", reconstruct it. Extract the version from the release tag (e.g., `java/v1.0.0` → `1.0.0`), and rebuild the URL as: `https://central.sonatype.com/artifact/com.github/copilot-sdk-java/{VERSION}`. The `` HTML comment in the section contains the intended URL pattern. - ## Example Output Here is an example of what a changelog entry should look like, based on real commits from this repo. **Follow this style exactly.** @@ -154,6 +147,8 @@ While `session.rpc.models.setModel()` already worked, there is now a convenience - C#: `session.SetModel("gpt-4o")` - Python: `session.set_model("gpt-4o")` - Go: `session.SetModel("gpt-4o")` +- Java: `session.setModel("gpt-4o").get()` +- Rust: `session.set_model("gpt-4o", None).await?` ### Other changes @@ -171,7 +166,7 @@ While `session.rpc.models.setModel()` already worked, there is now a convenience **Key rules visible in the example:** - Highlighted features get their own `### Feature:` heading, a short description, and code snippets -- Code snippets are TypeScript and C# primarily; Go/Python only when listing one-liner equivalents or when meaningfully different +- Code snippets use whichever of TypeScript, C#, Go, Python, Java, and Rust best illustrate the change; list all affected languages when showing equivalents - The `### Other changes` section is a flat bulleted list with lowercase `bugfix:` / `feature:` / `improvement:` prefixes - PR numbers are linked inline, not at the end with author attribution (keep it clean) diff --git a/.github/workflows/required-checks.yml b/.github/workflows/required-checks.yml new file mode 100644 index 000000000..dbd79fd4c --- /dev/null +++ b/.github/workflows/required-checks.yml @@ -0,0 +1,181 @@ +name: "SDK" + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + merge_group: + workflow_dispatch: + +permissions: + contents: read + pull-requests: read + +jobs: + changes: + name: Select SDK workflows + runs-on: ubuntu-latest + outputs: + nodejs: ${{ steps.select.outputs.nodejs }} + python: ${{ steps.select.outputs.python }} + go: ${{ steps.select.outputs.go }} + dotnet: ${{ steps.select.outputs.dotnet }} + java: ${{ steps.select.outputs.java }} + rust: ${{ steps.select.outputs.rust }} + steps: + - name: Detect changed paths + id: filter + if: github.event_name == 'pull_request' + uses: dorny/paths-filter@6852f92c20ea7fd3b0c25de3b5112db3a98da050 # v3 + with: + predicate-quantifier: every + filters: | + orchestrator: + - '.github/workflows/required-checks.yml' + nodejs: + - '{nodejs/**,test/**,.github/workflows/nodejs-sdk-tests.yml}' + - '!nodejs/scripts/**' + - '!**/*.md' + - '!**/LICENSE*' + - '!**/.gitignore' + - '!**/.editorconfig' + - '!**/*.{png,jpg,jpeg,gif,svg}' + python: + - '{python/**,test/**,nodejs/package.json,.github/workflows/python-sdk-tests.yml}' + - '!**/*.md' + - '!**/LICENSE*' + - '!**/.gitignore' + - '!**/.editorconfig' + - '!**/*.{png,jpg,jpeg,gif,svg}' + go: + - '{go/**,test/**,nodejs/package.json,.github/workflows/go-sdk-tests.yml,.github/actions/setup-copilot/**}' + - '!**/*.md' + - '!**/LICENSE*' + - '!**/.gitignore' + - '!**/.editorconfig' + - '!**/*.{png,jpg,jpeg,gif,svg}' + dotnet: + - '{dotnet/**,test/**,nodejs/package.json,.github/workflows/dotnet-sdk-tests.yml}' + - '!**/*.md' + - '!**/LICENSE*' + - '!**/.gitignore' + - '!**/.editorconfig' + - '!**/*.{png,jpg,jpeg,gif,svg}' + java: + - '{java/**,test/**,.github/workflows/java-sdk-tests.yml,.github/actions/setup-copilot/**,.github/actions/java-test-report/**}' + - '!**/*.md' + - '!**/LICENSE*' + - '!**/.gitignore' + - '!**/.editorconfig' + - '!**/*.{png,jpg,jpeg,gif,svg}' + rust: + - '{rust/**,test/**,nodejs/package.json,.github/workflows/rust-sdk-tests.yml,.github/actions/setup-copilot/**}' + - '!**/*.md' + - '!**/LICENSE*' + - '!**/.gitignore' + - '!**/.editorconfig' + - '!**/*.{png,jpg,jpeg,gif,svg}' + + - name: Select workflows + id: select + env: + EVENT_NAME: ${{ github.event_name }} + ORCHESTRATOR_CHANGED: ${{ steps.filter.outputs.orchestrator }} + NODEJS_CHANGED: ${{ steps.filter.outputs.nodejs }} + PYTHON_CHANGED: ${{ steps.filter.outputs.python }} + GO_CHANGED: ${{ steps.filter.outputs.go }} + DOTNET_CHANGED: ${{ steps.filter.outputs.dotnet }} + JAVA_CHANGED: ${{ steps.filter.outputs.java }} + RUST_CHANGED: ${{ steps.filter.outputs.rust }} + run: | + if [[ "$EVENT_NAME" != "pull_request" || "$ORCHESTRATOR_CHANGED" == "true" ]]; then + for workflow in nodejs python go dotnet java rust; do + echo "$workflow=true" >> "$GITHUB_OUTPUT" + done + exit 0 + fi + + echo "nodejs=${NODEJS_CHANGED:-false}" >> "$GITHUB_OUTPUT" + echo "python=${PYTHON_CHANGED:-false}" >> "$GITHUB_OUTPUT" + echo "go=${GO_CHANGED:-false}" >> "$GITHUB_OUTPUT" + echo "dotnet=${DOTNET_CHANGED:-false}" >> "$GITHUB_OUTPUT" + echo "java=${JAVA_CHANGED:-false}" >> "$GITHUB_OUTPUT" + echo "rust=${RUST_CHANGED:-false}" >> "$GITHUB_OUTPUT" + + nodejs: + needs: changes + if: needs.changes.outputs.nodejs == 'true' + uses: ./.github/workflows/nodejs-sdk-tests.yml + secrets: inherit + + python: + needs: changes + if: needs.changes.outputs.python == 'true' + uses: ./.github/workflows/python-sdk-tests.yml + secrets: inherit + + go: + needs: changes + if: needs.changes.outputs.go == 'true' + uses: ./.github/workflows/go-sdk-tests.yml + secrets: inherit + + dotnet: + needs: changes + if: needs.changes.outputs.dotnet == 'true' + uses: ./.github/workflows/dotnet-sdk-tests.yml + secrets: inherit + + java: + needs: changes + if: needs.changes.outputs.java == 'true' + uses: ./.github/workflows/java-sdk-tests.yml + + rust: + needs: changes + if: needs.changes.outputs.rust == 'true' + uses: ./.github/workflows/rust-sdk-tests.yml + secrets: inherit + + required: + name: "${{ matrix.name }} required" + if: always() + needs: [changes, nodejs, python, go, dotnet, java, rust] + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - key: nodejs + name: Node.js + - key: python + name: Python + - key: go + name: Go + - key: dotnet + name: .NET + - key: java + name: Java + - key: rust + name: Rust + steps: + - name: Verify SDK workflow + env: + KEY: ${{ matrix.key }} + SELECTIONS: ${{ toJSON(needs.changes.outputs) }} + RESULTS: ${{ toJSON(needs) }} + run: | + selected=$(jq -r --arg key "$KEY" '.[$key]' <<< "$SELECTIONS") + result=$(jq -r --arg key "$KEY" '.[$key].result' <<< "$RESULTS") + + if [[ "$selected" == "true" && "$result" == "success" ]]; then + echo "$KEY SDK checks succeeded." + exit 0 + fi + + if [[ "$selected" == "false" && "$result" == "skipped" ]]; then + echo "$KEY SDK checks were not required." + exit 0 + fi + + echo "::error::$KEY SDK checks were selected=$selected with result=$result." + exit 1 diff --git a/.github/workflows/rust-sdk-tests.yml b/.github/workflows/rust-sdk-tests.yml index 8e13e16b2..7fdac3b81 100644 --- a/.github/workflows/rust-sdk-tests.yml +++ b/.github/workflows/rust-sdk-tests.yml @@ -4,25 +4,8 @@ on: push: branches: - main - pull_request: - types: [opened, synchronize, reopened, ready_for_review] - paths: - - 'rust/**' - - 'test/**' - - 'nodejs/package.json' - - '.github/workflows/rust-sdk-tests.yml' - - '.github/actions/setup-copilot/**' - - '!**/*.md' - - '!**/LICENSE*' - - '!**/.gitignore' - - '!**/.editorconfig' - - '!**/*.png' - - '!**/*.jpg' - - '!**/*.jpeg' - - '!**/*.gif' - - '!**/*.svg' workflow_dispatch: - merge_group: + workflow_call: permissions: contents: read diff --git a/.github/workflows/sdk-consistency-review.lock.yml b/.github/workflows/sdk-consistency-review.lock.yml index 39121bc38..bc33be9ad 100644 --- a/.github/workflows/sdk-consistency-review.lock.yml +++ b/.github/workflows/sdk-consistency-review.lock.yml @@ -1,6 +1,6 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"fb73d13f101fc375308576a64180f63934cc9e8306cb6ef6303f1b9788d9df28","body_hash":"478a74e14ae52b64cd38c57a44953fad4d8003a2414912a19de8f0f4a354a92f","compiler_version":"v0.82.10","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.70"}} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"05205436a78512d71a2d842e46586ed05f4fa058","version":"v0.82.10"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35","digest":"sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35","digest":"sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35","digest":"sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.1","digest":"sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.5.0","digest":"sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4","pinned_image":"ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4"}],"has_pull_request":true} -# This file was automatically generated by gh-aw (v0.82.10). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"fb73d13f101fc375308576a64180f63934cc9e8306cb6ef6303f1b9788d9df28","body_hash":"cc60c817de34cdb662ae4c091203c67a5ef240ca0165b0cd26a842f03b22614f","compiler_version":"v0.83.1","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.73"}} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"8bdba8075360648fe6802302a5b4e016361dc6ac","version":"v0.83.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38","digest":"sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38","digest":"sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38","digest":"sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.3","digest":"sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}],"has_pull_request":true} +# This file was automatically generated by gh-aw (v0.83.1). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ # / _ \ | | (_) @@ -39,17 +39,17 @@ # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 # - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 -# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 +# - actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 +# - github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04 -# - ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3 -# - ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32 +# - ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c +# - ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 +# - ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83 # - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b -# - ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 +# - ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3 name: "SDK Consistency Review Agent" on: @@ -118,7 +118,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -126,8 +126,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "SDK Consistency Review Agent" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/sdk-consistency-review.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Generate agentic run info id: generate_aw_info @@ -135,16 +135,16 @@ jobs: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AGENT_VERSION: "1.0.70" - GH_AW_INFO_CLI_VERSION: "v0.82.10" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AGENT_VERSION: "1.0.73" + GH_AW_INFO_CLI_VERSION: "v0.83.1" GH_AW_INFO_WORKFLOW_NAME: "SDK Consistency Review Agent" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -242,7 +242,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.82.10" + GH_AW_COMPILED_VERSION: "v0.83.1" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -358,7 +358,7 @@ jobs: GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} GH_AW_INPUTS_PR_NUMBER: ${{ inputs.pr_number }} - GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' + GH_AW_MCP_CLI_SERVERS_LIST: "- `github` — run `github --help` to see available tools\n- `safeoutputs` — run `safeoutputs --help` to see available tools" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -439,6 +439,7 @@ jobs: has_patch: ${{ steps.collect_output.outputs.has_patch }} http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }} inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }} + invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }} mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }} model: ${{ needs.activation.outputs.model }} model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }} @@ -451,7 +452,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -460,8 +461,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "SDK Consistency Review Agent" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/sdk-consistency-review.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Set runtime paths id: set-runtime-paths @@ -507,11 +508,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.70 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.35 --rootless + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38 --rootless - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 @@ -538,7 +539,7 @@ jobs: GH_AW_SKILL_DIR: ".github/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04 ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3 ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 ghcr.io/github/gh-aw-mcpg:v0.4.3@sha256:3c744710ea275cd5ee65db92a1099e0d980754bd9fafda9ce67704c67004dc83 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3 - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" @@ -731,17 +732,18 @@ jobs: MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') source "${RUNNER_TEMP}/gh-aw/actions/resolve_docker_socket_gid.sh" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.1' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.3' mkdir -p "$HOME/.copilot" GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_d97c92af15acf38e_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_4d6f106ca28cda70_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.5.0", + "container": "ghcr.io/github/github-mcp-server:v1.6.0", "env": { + "GITHUB_FEATURES": "fields_param", "GITHUB_HOST": "${GITHUB_SERVER_URL}", "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_MCP_SERVER_TOKEN}", "GITHUB_READ_ONLY": "1", @@ -792,10 +794,11 @@ jobs: "port": $MCP_GATEWAY_PORT, "domain": "${MCP_GATEWAY_DOMAIN}", "apiKey": "${MCP_GATEWAY_API_KEY}", - "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}", + "startupTimeout": 120 } } - GH_AW_MCP_CONFIG_d97c92af15acf38e_EOF + GH_AW_MCP_CONFIG_4d6f106ca28cda70_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -824,7 +827,7 @@ jobs: run: | set -o pipefail printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt - trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT mkdir -p "$HOME/.copilot" printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" export XDG_CONFIG_HOME="$HOME" @@ -835,7 +838,7 @@ jobs: export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.35/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.35,squid=sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3,agent=sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed,agent-act=sha256:b00340a7b09c917c522cb806af6da1d12f2146e25a4a6198f1589b0116aee992,api-proxy=sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04,cli-proxy=sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST="" @@ -868,7 +871,7 @@ jobs: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_TIMEOUT_MINUTES: 15 - GH_AW_VERSION: v0.82.10 + GH_AW_VERSION: v0.83.1 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1057,7 +1060,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1066,8 +1069,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "SDK Consistency Review Agent" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/sdk-consistency-review.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output @@ -1313,7 +1316,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1322,8 +1325,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "SDK Consistency Review Agent" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/sdk-consistency-review.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output @@ -1350,7 +1353,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.35@sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed ghcr.io/github/gh-aw-firewall/api-proxy:0.27.35@sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04 ghcr.io/github/gh-aw-firewall/squid:0.27.35@sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.38@sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.38@sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c ghcr.io/github/gh-aw-firewall/squid:0.27.38@sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917 - name: Check if detection needed id: detection_guard if: always() @@ -1408,16 +1411,16 @@ jobs: mkdir -p /tmp/gh-aw/threat-detection touch /tmp/gh-aw/threat-detection/detection.log - name: Setup Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.70 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.73 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.35 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.38 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true @@ -1427,7 +1430,7 @@ jobs: run: | set -o pipefail printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt - trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"' EXIT mkdir -p "$HOME/.copilot" printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" export XDG_CONFIG_HOME="$HOME" @@ -1437,7 +1440,7 @@ jobs: export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.35/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.35,squid=sha256:f69282ec7b1326ba53891c399cf5b10475c0d3ccf4e1519b33d234a5427b57d3,agent=sha256:2202f63e8650b2b8b0d38033b44a05387b2b71ad3e690c4d23a34786f5462aed,agent-act=sha256:b00340a7b09c917c522cb806af6da1d12f2146e25a4a6198f1589b0116aee992,api-proxy=sha256:755b79d0dfda82bd6b43a208d68666721e504110c5d342a4eeb199802644ff04,cli-proxy=sha256:fe83cd274636efa9de3f456e2b078fae137328b9bb6ee4986ae510acaef0cec5\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.38/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"fable\",\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.38,squid=sha256:6c19094d95aad5f9f128ad5e583f0f2b894b158aa66c3b86dd9bcc90970a2917,agent=sha256:cb928eb62d9139a013c2d278dab19af232d35a2d83dca71a3d98eb431f786243,api-proxy=sha256:cd6145620d96acee46e1ede25180a13aa36002467e663db0caa453a8bc8eb60c,cli-proxy=sha256:c30c5319da37505d42f95cb3faa2cfa55e794ccb5cc805dbd9201410d1ac2a3e\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST="" @@ -1471,7 +1474,7 @@ jobs: GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_TIMEOUT_MINUTES: 20 - GH_AW_VERSION: v0.82.10 + GH_AW_VERSION: v0.83.1 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1562,7 +1565,7 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.70" + GH_AW_ENGINE_VERSION: "1.0.73" GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} GH_AW_TRACKER_ID: "sdk-consistency-review" @@ -1581,7 +1584,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 + uses: github/gh-aw-actions/setup@8bdba8075360648fe6802302a5b4e016361dc6ac # v0.83.1 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1590,8 +1593,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "SDK Consistency Review Agent" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/sdk-consistency-review.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.70" - GH_AW_INFO_AWF_VERSION: "v0.27.35" + GH_AW_INFO_VERSION: "1.0.73" + GH_AW_INFO_AWF_VERSION: "v0.27.38" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output diff --git a/.github/workflows/sdk-consistency-review.md b/.github/workflows/sdk-consistency-review.md index 8a4ff106a..550d9349d 100644 --- a/.github/workflows/sdk-consistency-review.md +++ b/.github/workflows/sdk-consistency-review.md @@ -74,7 +74,7 @@ When a pull request modifies any SDK client code, review it to ensure: - **Python**: `python/copilot/` - **Go**: `go/` - **.NET**: `dotnet/src/` -- **Java**: `java/src/main/java/` +- **Java**: `java/sdk/src/main/java/` - **Rust**: `rust/src/` ## Review Process diff --git a/.github/workflows/update-copilot-dependency.yml b/.github/workflows/update-copilot-dependency.yml index b1a668a7c..9646366ad 100644 --- a/.github/workflows/update-copilot-dependency.yml +++ b/.github/workflows/update-copilot-dependency.yml @@ -192,11 +192,11 @@ jobs: - Enum value additions/renames in generated types - New event types requiring handler registration - Removed or renamed generated types - 3. **Fix handwritten source** (`java/src/main/java/com/github/copilot/sdk/`): + 3. **Fix handwritten source** (`java/sdk/src/main/java/com/github/copilot/sdk/`): - Update call sites passing positional constructor args to include new fields (typically `null` for optional new fields). - Update switch/if-else over enum values to handle new cases. - Register handlers for new event types in `CopilotSession.java` if applicable. - 4. **Fix handwritten tests** (`java/src/test/java/com/github/copilot/sdk/`): + 4. **Fix handwritten tests** (`java/sdk/src/test/java/com/github/copilot/sdk/`): - Same constructor/enum fixes as above. - Add new test methods for new functionality if the change adds user-facing API surface. 5. **Validate**: `cd java && mvn clean test-compile jar:jar && mvn verify -Dskip.test.harness=true` diff --git a/.github/workflows/verify-compiled.yml b/.github/workflows/verify-compiled.yml index 946f6f903..1a3dbb96f 100644 --- a/.github/workflows/verify-compiled.yml +++ b/.github/workflows/verify-compiled.yml @@ -19,8 +19,9 @@ jobs: - name: Install gh-aw CLI uses: github/gh-aw-actions/setup-cli@05205436a78512d71a2d842e46586ed05f4fa058 # v0.82.10 with: - version: v0.82.10 + version: v0.83.1 - name: Recompile workflows + # Full-repository compile so the diff check below covers all workflows. run: gh aw compile - name: Check for uncommitted changes run: | diff --git a/.gitignore b/.gitignore index 4aff9be11..c1e983376 100644 --- a/.gitignore +++ b/.gitignore @@ -14,9 +14,10 @@ docs/.validation/ *.csproj.lscache # Java -java/target +java/**/target/ java/smoke-test java/.classpath java/.project java/.settings java/scripts/codegen/node_modules/ +.flattened-pom.xml diff --git a/.vscode/settings.json b/.vscode/settings.json index c4ae9c761..049330d2a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -25,6 +25,7 @@ "[go]": { "editor.defaultFormatter": "golang.go" }, + "java.autobuild.enabled": false, "java.configuration.updateBuildConfiguration": "automatic", "java.compile.nullAnalysis.mode": "automatic" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 41bf05e17..e9f22a3df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,43 @@ All notable changes to the Copilot SDK are documented in this file. This changelog is automatically generated by an AI agent when stable releases are published. See [GitHub Releases](https://github.com/github/copilot-sdk/releases) for the full list. +## [Unreleased] + +### Feature: host-injected managed settings permissions + +Session create and resume accept a new optional `managedSettings` option that injects an enterprise permissions policy at session startup, alongside the existing `enableManagedSettings` self-fetch flag. The current contract is permissions-only: `disableBypassPermissionsMode` (the literal `"disable"`), plus `deny`, `ask`, and `allow` rule lists. The layer composes restrictively with any server- or device-level managed settings (deny/ask are unioned, every present allow list must admit a tool, and `disableBypassPermissionsMode` is deny-wins). + +This layer is startup-only and is not persisted with the session, so it must be re-supplied on resume to remain in effect; omitting it on resume clears the previously injected layer. It can be combined with `enableManagedSettings`. Host injection requires Copilot CLI `1.0.79-5` or later and does not require an SDK protocol version bump. + +The generated session-event types also expose truthful injected-policy provenance: `session.managed_settings_resolved` can report `source` as `client` or `mixed`, with optional `clientManaged` metadata. + +```ts +const session = await client.createSession({ + managedSettings: { + permissions: { + disableBypassPermissionsMode: "disable", + deny: ["shell(rm*)"], + ask: ["write"], + }, + }, +}); +``` + +```cs +var session = await client.CreateSessionAsync(new SessionConfig +{ + ManagedSettings = new ManagedSettings + { + Permissions = new ManagedSettingsPermissions + { + DisableBypassPermissionsMode = DisableBypassPermissionsMode.Disable, + Deny = ["shell(rm*)"], + Ask = ["write"], + }, + }, +}); +``` + ## [v1.0.7](https://github.com/github/copilot-sdk/releases/tag/v1.0.7) (2026-07-16) ### Feature: in-process (FFI) transport diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f60625ec1..5135e596d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,63 +33,26 @@ We are generally **not** looking for: - Additional documentation - **SDKs for other languages** — if you want to create a Copilot SDK for another language, we'd love to hear from you and may offer to link to your SDK from our repo. However we do not plan to add further language-specific SDKs to this repo in the short term, since we need to retain our maintenance capacity for moving forwards quickly with the existing language set. For other languages, please consider running your own external project. -## Prerequisites for Running and Testing Code +## Developing an SDK -This is a multi-language SDK repository. Install the tools for the SDK(s) you plan to work on: +Setup, build, and test instructions are maintained with each SDK: -### All SDKs - -1. The end-to-end tests across all languages use a shared test harness written in Node.js. Before running tests in any language, `cd test/harness && npm ci`. - -### Node.js/TypeScript SDK - -1. Install [Node.js](https://nodejs.org/) (v18+) -1. Install dependencies: `cd nodejs && npm ci` - -### Python SDK - -1. Install [Python 3.8+](https://www.python.org/downloads/) -1. Install [uv](https://github.com/astral-sh/uv) -1. Install dependencies: `cd python && uv pip install -e . --group dev` - -### Go SDK - -1. Install [Go 1.24+](https://go.dev/doc/install) -1. Install [golangci-lint](https://golangci-lint.run/welcome/install/#local-installation) -1. Install dependencies: `cd go && go mod download` - -### .NET SDK - -1. Install [.NET SDK 10+](https://dotnet.microsoft.com/download) -1. Install .NET dependencies: `cd dotnet && dotnet restore` +- [Node.js/TypeScript](nodejs/README.md#development) +- [Python](python/README.md#development) +- [Go](go/README.md#development) +- [.NET](dotnet/README.md#development) +- [Rust](rust/README.md#development) +- [Java](java/README.md#development-setup) ## Submitting a Pull Request 1. Fork and clone the repository -1. Install dependencies for the SDK(s) you're modifying (see above) -1. Make sure the tests pass on your machine (see commands below) -1. Make sure linter passes on your machine (see commands below) +1. Follow the development instructions for the SDK(s) you're modifying 1. Create a new branch: `git checkout -b my-branch-name` -1. Make your change, add tests, and make sure the tests and linter still pass +1. Make your change, add tests, and run the documented checks 1. Push to your fork and [submit a pull request][pr] 1. Pat yourself on the back and wait for your pull request to be reviewed and merged. -### Running Tests and Linters - -```bash -# Node.js -cd nodejs && npm test && npm run lint - -# Python -cd python && uv run pytest && uv run ruff check . - -# Go -cd go && go test ./... && golangci-lint run ./... - -# .NET -cd dotnet && dotnet test test/GitHub.Copilot.SDK.Test.csproj -``` - Here are a few things you can do that will increase the likelihood of your pull request being accepted: - Write tests. diff --git a/README.md b/README.md index 43ff70deb..b2ef69d05 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,9 @@ The GitHub Copilot SDK exposes the same engine behind Copilot CLI: a production- | ------------------------ | ----------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ | | **Node.js / TypeScript** | [`nodejs/`](./nodejs/) | [Cookbook](https://github.com/github/awesome-copilot/blob/main/cookbook/copilot-sdk/nodejs/README.md) | `npm install @github/copilot-sdk` | | | **Python** | [`python/`](./python/) | [Cookbook](https://github.com/github/awesome-copilot/blob/main/cookbook/copilot-sdk/python/README.md) | `pip install github-copilot-sdk` | | -| **Go** | [`go/`](./go/) | [Cookbook](https://github.com/github/awesome-copilot/blob/main/cookbook/copilot-sdk/go/README.md) | `go get github.com/github/copilot-sdk/go` | [API docs](https://pkg.go.dev/github.com/github/copilot-sdk/go) | +| **Go** | [`go/`](./go/) | [Cookbook](https://github.com/github/awesome-copilot/blob/main/cookbook/copilot-sdk/go/README.md) | `go get github.com/github/copilot-sdk/go` | [API docs](https://pkg.go.dev/github.com/github/copilot-sdk/go#readme-api-reference) | | **.NET** | [`dotnet/`](./dotnet/) | [Cookbook](https://github.com/github/awesome-copilot/blob/main/cookbook/copilot-sdk/dotnet/README.md) | `dotnet add package GitHub.Copilot.SDK` | | -| **Rust** | [`rust/`](./rust/) | — | `cargo add github-copilot-sdk` | [API docs](https://docs.rs/github-copilot-sdk/latest/github_copilot_sdk/) | +| **Rust** | [`rust/`](./rust/) | — | `cargo add github-copilot-sdk` | [API docs](https://docs.rs/github-copilot-sdk/latest/github_copilot_sdk/#api-reference) | | **Java** | [`java/`](./java/) | [Cookbook](https://github.com/github/awesome-copilot/blob/main/cookbook/copilot-sdk/java/README.md) | Maven coordinates
`com.github:copilot-sdk-java`
See instructions for [Maven](./java/README.md#maven) and [Gradle](./java/README.md#gradle) | [API docs](https://javadoc.io/doc/com.github/copilot-sdk-java/latest/) | See the individual SDK READMEs for installation, usage examples, and API reference. @@ -69,7 +69,7 @@ Billing for the GitHub Copilot SDK is based on the same model as the Copilot CLI ### Does it support BYOK (Bring Your Own Key)? -Yes, the GitHub Copilot SDK supports BYOK (Bring Your Own Key). You can configure the SDK to use your own API keys from supported LLM providers (e.g. OpenAI, Azure AI Foundry, Anthropic) to access models through those providers. See the **[BYOK documentation](./docs/auth/byok.md)** for setup instructions and examples. +Yes, the GitHub Copilot SDK supports BYOK (Bring Your Own Key). You can configure the SDK to use your own API keys from supported LLM providers (e.g. OpenAI, Microsoft Foundry, Anthropic) to access models through those providers. See the **[BYOK documentation](./docs/auth/byok.md)** for setup instructions and examples. **Note:** BYOK uses key-based authentication only. Microsoft Entra ID (Azure AD), managed identities, and third-party identity providers are not supported. diff --git a/docs/README.md b/docs/README.md index 9e0d8fddf..3be019f14 100644 --- a/docs/README.md +++ b/docs/README.md @@ -8,7 +8,7 @@ Welcome to the GitHub Copilot SDK docs. Whether you're building your first Copil |---|---| | **Build my first app** | [Getting Started](./getting-started.md)—end-to-end tutorial with streaming & custom tools | | **Set up for production** | [Setup Guides](./setup/README.md)—architecture, deployment patterns, scaling | -| **Configure authentication** | [Authentication](./auth/README.md)—GitHub OAuth, environment variables, BYOK | +| **Configure authentication** | [Authentication](./auth/README.md)—GitHub OAuth, server-to-server authentication, environment variables, BYOK | | **Add features to my app** | [Features](./features/README.md)—hooks, custom agents, MCP, skills, and more | | **Debug an issue** | [Troubleshooting](./troubleshooting/debugging.md)—common problems and solutions | @@ -26,7 +26,7 @@ How to configure and deploy the SDK for your use case. * [Local CLI](./setup/local-cli.md): use your own CLI binary or running instance * [Backend Services](./setup/backend-services.md): server-side with headless CLI over TCP * [GitHub OAuth](./setup/github-oauth.md): implement the OAuth flow -* [Azure Managed Identity](./setup/azure-managed-identity.md): BYOK with Azure AI Foundry +* [Azure Managed Identity](./setup/azure-managed-identity.md): BYOK with Microsoft Foundry * [Scaling & Multi-Tenancy](./setup/scaling.md): horizontal scaling, isolation patterns * [Multi-Tenancy & Server Deployments](./setup/multi-tenancy.md): mode: "empty", session isolation, integration IDs, sessionFs @@ -35,6 +35,7 @@ How to configure and deploy the SDK for your use case. Configuring how users and services authenticate with Copilot. * [Authentication Overview](./auth/README.md): methods, priority order, and examples +* [Server-to-server authentication](./auth/server-to-server-tokens.md): use GitHub Actions or GitHub App installation tokens for organization-attributed automation * [Bring Your Own Key (BYOK)](./auth/byok.md): use your own API keys from OpenAI, Azure, Anthropic, and more ### [Features](./features/README.md) @@ -62,6 +63,7 @@ Detailed API reference for each session hook. * [Pre-Tool Use](./hooks/pre-tool-use.md): approve, deny, or modify tool calls * [Post-Tool Use](./hooks/post-tool-use.md): transform tool results * [User Prompt Submitted](./hooks/user-prompt-submitted.md): modify or filter user messages +* [User Prompt Transformed](./hooks/user-prompt-transformed.md): inspect or replace model-facing prompts * [Session Lifecycle](./hooks/session-lifecycle.md): session start and end * [Error Handling](./hooks/error-handling.md): custom error handling diff --git a/docs/auth/README.md b/docs/auth/README.md index 282bcb019..a85d6de6e 100644 --- a/docs/auth/README.md +++ b/docs/auth/README.md @@ -3,10 +3,11 @@ Choose the authentication method that best fits your deployment scenario for the GitHub Copilot SDK. * [Authenticate Copilot SDK](authenticate.md): methods, priority order, and examples +* [Server-to-server authentication](server-to-server-tokens.md): use GitHub Actions or GitHub App installation tokens for organization-attributed automation * [Bring your own key (BYOK)](./byok.md): use your own API keys from OpenAI, Azure, Anthropic, and more ## Authentication priority -When multiple credentials are configured, an explicit SDK token takes priority, followed by direct Copilot API environment authentication, environment variable GitHub tokens, stored Copilot CLI credentials, and then GitHub CLI credentials. See [Authenticate Copilot SDK](authenticate.md#authentication-priority) for details. +When multiple credentials are configured, an explicit SDK token takes priority, followed by direct Copilot API environment authentication, environment variable GitHub tokens, stored Copilot CLI credentials, and then GitHub CLI credentials. Server-to-server installation tokens use the environment variable path. See [Authenticate Copilot SDK](authenticate.md#authentication-priority) for details. For multi-user server mode, pass a per-session `gitHubToken` so each session runs with the correct GitHub identity; see [Multi-user and server deployments](../setup/multi-tenancy.md). diff --git a/docs/auth/authenticate.md b/docs/auth/authenticate.md index 1a0190186..d54c95451 100644 --- a/docs/auth/authenticate.md +++ b/docs/auth/authenticate.md @@ -9,7 +9,8 @@ The GitHub Copilot SDK supports multiple authentication methods to fit different | [GitHub Signed-in User](#github-signed-in-user) | Interactive apps where users sign in with GitHub | Yes | | [OAuth GitHub App](#oauth-github-app) | Apps acting on behalf of users via OAuth | Yes | | [Environment Variables](#environment-variables) | CI/CD, automation, server-to-server | Yes | -| [BYOK (Bring Your Own Key)](./byok.md) | Using your own API keys (Azure AI Foundry, OpenAI, and more) | No | +| [Server-to-server authentication](./server-to-server-tokens.md) | Organization-attributed automation and direct organization billing | No user subscription; organization policy required | +| [BYOK (Bring Your Own Key)](./byok.md) | Using your own API keys (Microsoft Foundry, OpenAI, and more) | No | ## GitHub signed-in user @@ -236,6 +237,8 @@ client.start().get(); For automation, CI/CD pipelines, and server-to-server scenarios, you can authenticate using environment variables. +For organization-attributed automation that should not use a user's personal access token, see [Server-to-server authentication](./server-to-server-tokens.md). + **Supported environment variables (in priority order):** 1. `COPILOT_GITHUB_TOKEN` - Recommended for explicit Copilot usage 1. `GH_TOKEN` - GitHub CLI compatible @@ -282,16 +285,16 @@ await client.start() ## BYOK (bring your own key) -BYOK allows you to use your own API keys from model providers like Azure AI Foundry, OpenAI, or Anthropic. This bypasses GitHub Copilot authentication entirely. +BYOK allows you to use your own API keys from model providers like Microsoft Foundry, OpenAI, or Anthropic. This bypasses GitHub Copilot authentication entirely. **Key benefits:** * No GitHub Copilot subscription required * Use enterprise model deployments * Direct billing with your model provider -* Support for Azure AI Foundry, OpenAI, Anthropic, and OpenAI-compatible endpoints +* Support for Microsoft Foundry, OpenAI, Anthropic, and OpenAI-compatible endpoints **See the [BYOK documentation](./byok.md) for complete details**, including: -* Azure AI Foundry setup +* Microsoft Foundry setup * Provider configuration options * Limitations and considerations * Complete code examples diff --git a/docs/auth/byok.md b/docs/auth/byok.md index d453b8113..0fbf9bd8e 100644 --- a/docs/auth/byok.md +++ b/docs/auth/byok.md @@ -7,15 +7,15 @@ BYOK allows you to use the Copilot SDK with your own API keys from model provide | Provider | Type Value | Notes | |----------|------------|-------| | OpenAI | `"openai"` | OpenAI API and OpenAI-compatible endpoints | -| Azure OpenAI / Azure AI Foundry | `"azure"` | Azure-hosted models | +| Microsoft Foundry / Azure OpenAI | `"openai"` or `"azure"` | Use `"openai"` for `/openai/v1/`; use `"azure"` for native Azure endpoints | | Anthropic | `"anthropic"` | Claude models | | Ollama | `"openai"` | Local models via OpenAI-compatible API | | Microsoft Foundry Local | `"openai"` | Run AI models locally on your device via OpenAI-compatible API | | Other OpenAI-compatible | `"openai"` | vLLM, LiteLLM, etc. | -## Quick start: Azure AI Foundry +## Quick start: Microsoft Foundry -Azure AI Foundry (formerly Azure OpenAI) is a common BYOK deployment target for enterprises. Here's a complete example: +Microsoft Foundry is a common BYOK deployment target for enterprises. Here's a complete example:
Python @@ -207,7 +207,7 @@ client.stop().get(); | `bearerToken` / `bearer_token` | string | Bearer token auth (takes precedence over apiKey) | | `bearerTokenProvider` / `bearer_token_provider` | callback | Returns a bearer token on demand (takes precedence over `apiKey` and `bearerToken`) | | `wireApi` / `wire_api` | `"completions"` \| `"responses"` | Select `"completions"` for broad model compatibility (the Chat Completions API); select `"responses"` for multi-turn state management, tool namespacing, and reasoning support (the Responses API). Anthropic models always use the Messages API regardless of this setting. | -| `azure.apiVersion` / `azure.api_version` | string | Azure API version (default: `"2024-10-21"`) | +| `azure.apiVersion` / `azure.api_version` | string | Azure API version. When set, the runtime uses the versioned deployment route; when omitted, it uses the GA versionless `v1` route. | ### Wire API format @@ -260,9 +260,9 @@ provider: { } ``` -### Azure AI Foundry (OpenAI-compatible endpoint) +### Microsoft Foundry (OpenAI-compatible endpoint) -For Azure AI Foundry deployments with `/openai/v1/` endpoints, use `type: "openai"`: +For Microsoft Foundry deployments with `/openai/v1/` endpoints, use `type: "openai"`: ```typescript provider: { @@ -493,14 +493,6 @@ Results are cached after the first call, just like the default behavior. The han ## Limitations -When using BYOK, be aware of these limitations: - -### Identity limitations - -BYOK authentication uses **static credentials only**. - -You must use an API key or static bearer token that you manage yourself. - ### Feature limitations Some Copilot features may behave differently with BYOK: @@ -514,9 +506,8 @@ Some Copilot features may behave differently with BYOK: | Provider | Limitations | |----------|-------------| -| Azure AI Foundry | No Entra ID auth; must use API keys | -| Ollama | No API key; local only; model support varies | | [Microsoft Foundry Local](https://foundrylocal.ai) | Local only; model availability depends on device hardware; no API key required | +| Ollama | No API key; local only; model support varies | | OpenAI | Subject to OpenAI rate limits and quotas | ## Troubleshooting @@ -571,7 +562,7 @@ provider: { } ``` -However, if your Azure AI Foundry deployment provides an OpenAI-compatible endpoint path (e.g., `/openai/v1/`), use `type: "openai"`: +However, if your Microsoft Foundry deployment provides an OpenAI-compatible endpoint path (for example, `/openai/v1/`), use `type: "openai"`: ```typescript @@ -589,7 +580,7 @@ const session = await client.createSession({ ```typescript -// ✅ Correct: OpenAI-compatible Azure AI Foundry endpoint +// ✅ Correct: OpenAI-compatible Microsoft Foundry endpoint provider: { type: "openai", baseUrl: "https://your-resource.openai.azure.com/openai/v1/", diff --git a/docs/auth/server-to-server-tokens.md b/docs/auth/server-to-server-tokens.md new file mode 100644 index 000000000..b7b4fcf40 --- /dev/null +++ b/docs/auth/server-to-server-tokens.md @@ -0,0 +1,205 @@ +# Server-to-server authentication + +Use a short-lived installation access token when a service needs to make Copilot requests on behalf of an organization without a user's credentials. In GitHub Actions, use the built-in `GITHUB_TOKEN` instead. + +## GitHub Actions + +For workflows in an organization-owned repository, grant the built-in token permission to make Copilot requests: + +```yaml +permissions: + contents: read + copilot-requests: write + +jobs: + copilot: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - run: your-application + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +``` + +The organization's **Allow use of Copilot CLI billed to the organization** policy must be enabled. This approach needs no GitHub App or stored authentication secret. For details, see [Using Copilot CLI in GitHub Actions with GITHUB_TOKEN](https://docs.github.com/en/copilot/how-tos/copilot-cli/use-copilot-cli-in-actions). + +## Other services and CI systems + +For services outside GitHub Actions: + +1. Create a GitHub App with the **Copilot Requests** repository permission set to **Read & write**. +1. Install it on the organization that should be billed. The current Copilot permission check requires **All repositories** access. +1. [Create an installation access token](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-an-installation-access-token-for-a-github-app) with a repository ID and the Copilot permission: + + ```json + { + "repository_ids": [123456789], + "permissions": { + "copilot_requests": "write" + } + } + ``` + +1. Pass the resulting `ghs_` token to the runtime as `COPILOT_GITHUB_TOKEN`. + +The organization must be enabled for Copilot requests from GitHub App installations. Installation tokens expire after one hour. + +> [!WARNING] +> Do not pass an installation token through the SDK's `gitHubToken`, `github_token`, or equivalent option. That option is for user tokens. Installation tokens must use the runtime environment authentication path. + +## Configure the runtime + +The following examples assume the minted token is in `INSTALLATION_TOKEN`. They pass it only to the child runtime and disable fallback to stored user credentials. + +
+TypeScript + +```typescript +import { CopilotClient, RuntimeConnection } from "@github/copilot-sdk"; + +const token = process.env.INSTALLATION_TOKEN; +if (!token) throw new Error("INSTALLATION_TOKEN is required"); + +const client = new CopilotClient({ + connection: RuntimeConnection.forStdio(), + env: { + ...process.env, + COPILOT_GITHUB_TOKEN: token, + }, + useLoggedInUser: false, +}); +``` + +
+
+Python + +```python +import os + +from copilot import CopilotClient, RuntimeConnection + +client = CopilotClient( + connection=RuntimeConnection.for_stdio(), + env={**os.environ, "COPILOT_GITHUB_TOKEN": os.environ["INSTALLATION_TOKEN"]}, + use_logged_in_user=False, +) +``` + +
+
+Go + +```go +package main + +import ( + "log" + "os" + + copilot "github.com/github/copilot-sdk/go" +) + +func main() { + token, ok := os.LookupEnv("INSTALLATION_TOKEN") + if !ok { + log.Fatal("INSTALLATION_TOKEN is required") + } + client := copilot.NewClient(&copilot.ClientOptions{ + Connection: copilot.StdioConnection{}, + Env: append(os.Environ(), "COPILOT_GITHUB_TOKEN="+token), + UseLoggedInUser: copilot.Bool(false), + }) + _ = client +} +``` + +
+
+Rust + +```rust +use github_copilot_sdk::{ClientOptions, Transport}; + +fn main() { + let token = std::env::var("INSTALLATION_TOKEN").expect("INSTALLATION_TOKEN is required"); + let options = ClientOptions::new() + .with_transport(Transport::Stdio) + .with_env([("COPILOT_GITHUB_TOKEN", token)]) + .with_use_logged_in_user(false); + drop(options); +} +``` + +
+
+.NET + +```csharp +using System.Collections; +using GitHub.Copilot; + +var token = Environment.GetEnvironmentVariable("INSTALLATION_TOKEN") + ?? throw new InvalidOperationException("INSTALLATION_TOKEN is required"); +var environment = Environment.GetEnvironmentVariables() + .Cast() + .ToDictionary(entry => (string)entry.Key, entry => entry.Value?.ToString() ?? ""); +environment["COPILOT_GITHUB_TOKEN"] = token; + +await using var client = new CopilotClient(new CopilotClientOptions +{ + Connection = RuntimeConnection.ForStdio(), + Environment = environment, + UseLoggedInUser = false, +}); +``` + +
+
+Java + +```java +import com.github.copilot.CopilotClient; +import com.github.copilot.rpc.CopilotClientOptions; +import java.util.HashMap; +import java.util.Objects; + +var environment = new HashMap<>(System.getenv()); +var token = Objects.requireNonNull( + System.getenv("INSTALLATION_TOKEN"), "INSTALLATION_TOKEN is required"); +environment.put("COPILOT_GITHUB_TOKEN", token); + +try (var client = new CopilotClient(new CopilotClientOptions() + .setEnvironment(environment) + .setUseLoggedInUser(false))) { + // Use the client. +} +``` + +
+ +For in-process FFI, set `COPILOT_GITHUB_TOKEN` in the host environment before loading the runtime; per-client environment options are not supported. For an existing runtime URI, set it on that runtime process. + +## Refresh tokens + +Mint a new installation token before the current token expires. For a child process, restart the SDK client with the new environment. For an in-process or existing runtime, restart the host runtime with the new token. + +## Billing + +Usage is attributed and billed to the account that owns the GitHub App installation. Use an organization installation for organization billing; a user-account installation attributes usage to that user. + +## Troubleshooting + +| Symptom | Check | +|---|---| +| `401 Unauthorized` | Confirm the organization supports GitHub App installation authentication for Copilot. | +| `403 Resource not accessible by integration` or an error mentioning user information | Confirm the installation token is in `COPILOT_GITHUB_TOKEN`, not the SDK's explicit token option. | +| `403 Forbidden` from the Copilot API | Confirm the token request contains `repository_ids` and `copilot_requests: write`. | +| `403 Forbidden` with the required token request | Confirm the app installation has **All repositories** access, then mint a new token. | +| Requested model is unavailable | Confirm the organization's Copilot policy allows the model and the bundled runtime supports it. | +| Wrong account billed | Confirm the installation belongs to the intended organization. | + +## Further reading + +* [Authenticate Copilot SDK](./authenticate.md): other authentication methods and priority +* [Generating an installation access token](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-an-installation-access-token-for-a-github-app): GitHub App token creation diff --git a/docs/developer-docs/secrets.md b/docs/developer-docs/secrets.md index 15788bbe5..573f4f22e 100644 --- a/docs/developer-docs/secrets.md +++ b/docs/developer-docs/secrets.md @@ -50,7 +50,7 @@ These secrets are used by the Java SDK Maven Central publishing workflow (`java- * **`JAVA_RELEASE_TOKEN`**: GitHub token with **push** permission on the repository. Used by the release workflow for `actions/checkout`, pushing release commits and tags to `main`, and running `mvn release:prepare -DpushChanges=true`. * Workflows: `java-publish-maven.yml` -* **`JAVA_RELEASE_GITHUB_TOKEN`**: GitHub token with **workflow dispatch** (actions:write) permission on this repository and `github/copilot-sdk-java`. Used to trigger the `release-changelog.lock.yml` workflow and the documentation site deployment after a release is published. +* **`JAVA_RELEASE_GITHUB_TOKEN`**: GitHub token with **workflow dispatch** (actions:write) permission on `github/copilot-sdk-java`. Used to trigger the documentation site deployment after a release is published. * Workflows: `java-publish-maven.yml` ## Rust publishing secret diff --git a/docs/features/README.md b/docs/features/README.md index b695fea6d..f97140b78 100644 --- a/docs/features/README.md +++ b/docs/features/README.md @@ -16,9 +16,12 @@ These guides cover the capabilities you can add to your Copilot SDK application. | [Skills](./skills.md) | Load reusable prompt modules from directories | | [Plugin Directories](./plugin-directories.md) | Bundle skills, hooks, MCP servers, and agents as a single loadable plugin | | [Session limits](./session-limits.md) | Set an AI Credits budget for a session and observe budget events | +| [Citations](./citations.md) | Link assistant responses back to their supporting sources | | [Image Input](./image-input.md) | Send images to sessions as attachments | | [Streaming Events](./streaming-events.md) | Subscribe to real-time session events (40+ event types) | +| [Usage and Billing](./usage-and-billing.md) | Read token counts, context-window utilization, AI credit cost, and account quota | | [Steering & Queueing](./steering-and-queueing.md) | Control message delivery—immediate steering vs. sequential queueing | +| [Context Clearing](./context-management.md) | Replace conversation context safely with terminal tools | | [Session Persistence](./session-persistence.md) | Resume sessions across restarts, manage session storage | | [Remote Sessions](./remote-sessions.md) | Share locally hosted sessions to GitHub web and mobile via Mission Control | | [Cloud Sessions](./cloud-sessions.md) | Run sessions on GitHub-hosted compute through Mission Control | diff --git a/docs/features/citations.md b/docs/features/citations.md new file mode 100644 index 000000000..b68ae292c --- /dev/null +++ b/docs/features/citations.md @@ -0,0 +1,443 @@ +# Citations + +Citations link spans of an assistant response back to the sources that support them. Turn on `enableCitations` when you create or resume a session, then read the `citations` payload on `assistant.message` events to render footnotes, source lists, or inline links. + +> [!WARNING] +> Citations are experimental. The option name, event payload, and provider coverage can change in a future release. + +## How citations work + +Citations are produced by the model provider, not by the SDK. The flow has three parts: + +1. Your application supplies citable material, such as a document attachment or a tool result that carries source content. +1. The runtime marks that material as citable on the wire when `enableCitations` is on. For Anthropic models, file attachments are sent as `document` blocks with citations enabled. +1. The model returns citation metadata, and the runtime normalizes it into a provider-agnostic `citations` object on the final `assistant.message` event. + +Provider support is limited. The `provider` field on each source records where the citation came from: + +| Provider value | Meaning | +|---|---| +| `anthropic` | Citation produced by an Anthropic (Claude) model response | +| `openai` | Citation produced by an OpenAI model response | +| `client` | Citation synthesized by the runtime from tool output | + +> [!NOTE] +> Turning on `enableCitations` does not guarantee that a response contains citations. Models emit them only when the response is grounded in citable source material. Always treat the `citations` field as optional. + +## Enable citations on a session + +Set the option on session create, and set it again on resume if you want citations after a restart. + +
+TypeScript + + + +```typescript +const session = await client.createSession({ + onPermissionRequest: approveAll, + enableCitations: true, +}); + +const resumed = await client.resumeSession(session.sessionId, { + onPermissionRequest: approveAll, + enableCitations: true, +}); +``` + +
+
+Python + + + +```python +session = await client.create_session( + on_permission_request=PermissionHandler.approve_all, + enable_citations=True, +) + +resumed = await client.resume_session( + session.session_id, + on_permission_request=PermissionHandler.approve_all, + enable_citations=True, +) +``` + +
+
+Go + + + +```go +session, err := client.CreateSession(ctx, &copilot.SessionConfig{ + OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + EnableCitations: copilot.Bool(true), +}) + +resumed, err := client.ResumeSession(ctx, session.SessionID, &copilot.ResumeSessionConfig{ + OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + EnableCitations: copilot.Bool(true), +}) +``` + +
+
+.NET + + + +```csharp +var session = await client.CreateSessionAsync(new SessionConfig +{ + OnPermissionRequest = PermissionHandler.ApproveAll, + EnableCitations = true, +}); + +var resumed = await client.ResumeSessionAsync(session.SessionId, new ResumeSessionConfig +{ + OnPermissionRequest = PermissionHandler.ApproveAll, + EnableCitations = true, +}); +``` + +
+
+Java + + + +```java +CopilotSession session = client + .createSession(new SessionConfig() + .setOnPermissionRequest(PermissionHandler.APPROVE_ALL) + .setEnableCitations(true)) + .get(); + +CopilotSession resumed = client + .resumeSession(session.getSessionId(), new ResumeSessionConfig() + .setOnPermissionRequest(PermissionHandler.APPROVE_ALL) + .setEnableCitations(true)) + .get(); +``` + +
+
+Rust + + + +```rust +let session = client + .create_session( + SessionConfig::new() + .approve_all_permissions() + .with_enable_citations(true), + ) + .await?; + +let resumed = client + .resume_session( + ResumeSessionConfig::new(session.id().clone()) + .approve_all_permissions() + .with_enable_citations(true), + ) + .await?; +``` + +
+ +## Read citations from assistant messages + +Citations arrive on the final `assistant.message` event, not on `assistant.message_delta` events. Wait for the final message before you render source markers. + +
+TypeScript + + + +```typescript +session.on((event) => { + if (event.type !== "assistant.message" || !event.data.citations) { + return; + } + + const { sources, spans } = event.data.citations; + const sourceById = new Map(sources.map((source) => [source.id, source])); + + for (const span of spans) { + const quoted = event.data.content.slice(span.startIndex, span.endIndex); + for (const reference of span.references) { + const source = sourceById.get(reference.sourceId); + const label = source?.title ?? source?.url ?? source?.path ?? source?.id; + console.log(`"${quoted}" — ${label}`); + } + } +}); +``` + +
+
+Python + + + +```python +from copilot.session_events import SessionEventType + +def utf16_slice(text: str, start: int, end: int) -> str: + """Slice by UTF-16 code units, which is how span offsets are measured.""" + units = text.encode("utf-16-le") + return units[start * 2 : end * 2].decode("utf-16-le") + +def handle(event): + if event.type != SessionEventType.ASSISTANT_MESSAGE or not event.data.citations: + return + + sources = {source.id: source for source in event.data.citations.sources} + + for span in event.data.citations.spans: + quoted = utf16_slice(event.data.content, span.start_index, span.end_index) + for reference in span.references: + source = sources[reference.source_id] + label = source.title or source.url or source.path or source.id + print(f'"{quoted}" — {label}') + +session.on(handle) +``` + +
+
+Go + + + +```go +// import "unicode/utf16" + +session.On(func(event copilot.SessionEvent) { + d, ok := event.Data.(*copilot.AssistantMessageData) + if !ok || d.Citations == nil { + return + } + + sources := map[string]copilot.CitationSource{} + for _, source := range d.Citations.Sources { + sources[source.ID] = source + } + + // Span offsets are UTF-16 code units, so index the UTF-16 view of the content. + units := utf16.Encode([]rune(d.Content)) + + for _, span := range d.Citations.Spans { + quoted := string(utf16.Decode(units[span.StartIndex:span.EndIndex])) + for _, reference := range span.References { + source := sources[reference.SourceID] + label := source.ID + switch { + case source.Title != nil: + label = *source.Title + case source.URL != nil: + label = *source.URL + case source.Path != nil: + label = *source.Path + } + fmt.Printf("%q — %s\n", quoted, label) + } + } +}) +``` + +
+
+.NET + + + +```csharp +session.On(evt => +{ + if (evt is not AssistantMessageEvent message || message.Data.Citations is null) + { + return; + } + + var sources = message.Data.Citations.Sources.ToDictionary(source => source.Id); + + foreach (var span in message.Data.Citations.Spans) + { + var quoted = message.Data.Content[(int)span.StartIndex..(int)span.EndIndex]; + foreach (var reference in span.References) + { + var source = sources[reference.SourceId]; + var label = source.Title ?? source.Url ?? source.Path ?? source.Id; + Console.WriteLine($"\"{quoted}\" — {label}"); + } + } +}); +``` + +
+
+Java + + + +```java +session.on(AssistantMessageEvent.class, event -> { + Citations citations = event.getData().citations(); + if (citations == null) { + return; + } + + Map sources = citations.sources().stream() + .collect(Collectors.toMap(CitationSource::id, source -> source)); + + for (CitationSpan span : citations.spans()) { + String quoted = event.getData().content() + .substring(span.startIndex().intValue(), span.endIndex().intValue()); + for (CitationReference reference : span.references()) { + CitationSource source = sources.get(reference.sourceId()); + String label = source.title() != null ? source.title() + : source.url() != null ? source.url() + : source.path() != null ? source.path() + : source.id(); + System.out.printf("\"%s\" — %s%n", quoted, label); + } + } +}); +``` + +
+
+Rust + + + +```rust +use github_copilot_sdk::session_events::AssistantMessageData; +use std::collections::HashMap; + +let mut events = session.subscribe(); + +while let Ok(event) = events.recv().await { + if event.event_type != "assistant.message" { + continue; + } + + let Some(data) = event.typed_data::() else { + continue; + }; + let Some(citations) = data.citations.as_ref() else { + continue; + }; + + let sources: HashMap<&str, _> = citations + .sources + .iter() + .map(|source| (source.id.as_str(), source)) + .collect(); + + // Span offsets are UTF-16 code units, so index the UTF-16 view of the content. + let units: Vec = data.content.encode_utf16().collect(); + + for span in &citations.spans { + let quoted = String::from_utf16_lossy( + &units[span.start_index as usize..span.end_index as usize], + ); + for reference in &span.references { + let Some(source) = sources.get(reference.source_id.as_str()) else { + continue; + }; + let label = source + .title + .as_deref() + .or(source.url.as_deref()) + .or(source.path.as_deref()) + .unwrap_or(source.id.as_str()); + println!("\"{quoted}\" — {label}"); + } + } +} +``` + +
+ +## Citation payload reference + +The `citations` object separates deduplicated sources from the spans that reference them, so a source cited five times appears once in `sources`. + +| Type | Field | Description | +|---|---|---| +| `Citations` | `sources` | Deduplicated set of sources referenced by the citation spans | +| `Citations` | `spans` | Spans of generated text annotated with their supporting sources | +| `CitationSource` | `id` | Stable, turn-scoped identifier referenced by `CitationReference.sourceId` | +| `CitationSource` | `provider` | System that produced the citation: `anthropic`, `openai`, or `client` | +| `CitationSource` | `title?` | Human-readable title of the source | +| `CitationSource` | `url?` | URL of the source, when it is a web resource | +| `CitationSource` | `path?` | File path relative to the agent workspace root, when the source is a file | +| `CitationSpan` | `startIndex` | Start offset in the final message content (UTF-16 code units, zero-based, inclusive) | +| `CitationSpan` | `endIndex` | End offset in the final message content (UTF-16 code units, zero-based, exclusive) | +| `CitationSpan` | `references` | The sources that support this span | +| `CitationReference` | `sourceId` | Identifier of the `CitationSource` this reference points to | +| `CitationReference` | `citedText?` | Exact text from the source that supports the span, when the model provides it | +| `CitationReference` | `location?` | Location within the source that supports the span | +| `CitationReference` | `providerMetadata?` | Provider-native correlation data, passed through opaquely | + +> [!TIP] +> Span offsets are measured in UTF-16 code units against the final `content` string. TypeScript, Java, and .NET strings are already UTF-16, so you can slice them directly. Python strings are indexed by Unicode code point and Go and Rust strings are UTF-8, so convert the content to UTF-16 code units before slicing, as the examples above do. + +### Citation locations + +`CitationReference.location` is a discriminated union keyed on `type`: + +| Location type | Fields | Use | +|---|---|---| +| `char` | `startIndex`, `endIndex` | Character range within the source text | +| `page` | `startPage`, `endPage` | Page range within a paginated document | +| `block` | `startBlock`, `endBlock` | Content-block range within a structured document | + +## Provide citable sources + +Citations need source material the model can attribute. There are two ways to supply it. + +### Attach documents to a message + +When citations are enabled and the session uses an Anthropic provider, file attachments are sent as `document` blocks with citations turned on, so the model can cite passages from them. + + + +```typescript +await session.sendAndWait({ + prompt: "Summarize the attached PDF and cite the passages you used.", + attachments: [ + { + type: "blob", + data: pdfBase64, + displayName: "quarterly-report.pdf", + mimeType: "application/pdf", + }, + ], +}); +``` + +See [Image input](./image-input.md) for the attachment API and the `file` and `blob` attachment shapes. + +### Return citable sources from a tool + +Tool results carry an experimental `citableSources` array. Each entry supplies `content` that the model can cite, along with an `id` and optional `title`, `url`, and `path`. These sources are persisted with the tool result, so they survive session resume, and citations built from them are tagged with the `client` provider. + +## Limitations + +* Citations are experimental in every SDK and are not covered by compatibility guarantees. +* Coverage depends on the model provider. A session configured for a provider without citation support emits no `citations` payload. +* Citations are only present on the final `assistant.message` event, so streaming consumers cannot render them mid-response. +* Public code and IP-duplication citations are not part of this surface. + +## Further reading + +* [Streaming events](./streaming-events.md): subscribe to session events and narrow event types +* [Image input](./image-input.md): attach files and in-memory blobs to a message +* [Session persistence](./session-persistence.md): resume sessions and re-apply session options +* [Compatibility](../troubleshooting/compatibility.md): SDK and CLI feature matrix diff --git a/docs/features/context-management.md b/docs/features/context-management.md new file mode 100644 index 000000000..6472b046a --- /dev/null +++ b/docs/features/context-management.md @@ -0,0 +1,57 @@ +# Context clearing and terminal tools + +Use `session.history.clearContext` when a host needs to replace the current conversation context without replacing the session. Typical uses include handoffs and host-managed context lifecycle policies. + +Context clearing is different from creating a new session: it preserves the session identity, system and developer messages, configuration, and event log while removing the model-facing conversation. + +> [!IMPORTANT] +> `clearContext` is a tool-handler primitive. The runtime rejects calls made without a tool call in flight, calls with an empty seed prompt, and calls on remote sessions. + +## Define a context-clearing tool + +A successful context-clearing tool should be terminal. Otherwise, the agent loop may make another model call against the newly cleared window before starting the seeded turn. + +```typescript +import { approveAll, CopilotClient, defineTool } from "@github/copilot-sdk"; +import type { CopilotSession } from "@github/copilot-sdk"; +import { z } from "zod"; + +const client = new CopilotClient(); +let session: CopilotSession; + +session = await client.createSession({ + onPermissionRequest: approveAll, + tools: [ + defineTool("clear_context", { + description: "Clear the conversation and start a fresh context window", + parameters: z.object({ prompt: z.string() }), + isTerminal: true, + defer: "never", + handler: async ({ prompt }) => { + const { messagesCleared } = + await session.rpc.history.clearContext({ prompt }); + return `Cleared ${messagesCleared} messages.`; + }, + }), + ], +}); +``` + +The required `prompt` becomes the first user message in the fresh context. A successful clear emits `session.context_cleared` with the number of removed messages and the initial message. + +## Terminal-tool behavior + +`isTerminal` ends the current agent turn only when the tool succeeds. A failure, denial, rejection, timeout, or input-validation error remains visible to the model so it can recover or retry. + +The option follows each language's naming conventions: + +| SDK | Tool option | +|---|---| +| Node.js | `isTerminal` | +| Python | `is_terminal` | +| Go | `IsTerminal` | +| .NET | `CopilotToolOptions.IsTerminal` | +| Java | `ToolDefinition.isTerminal(true)` or `@CopilotTool(isTerminal = true)` | +| Rust | `with_is_terminal(true)` | + +Use terminality only for tools whose successful completion should end the turn. Ordinary tools should leave it unset. diff --git a/docs/features/custom-agents.md b/docs/features/custom-agents.md index 0cb68b944..9e2f59768 100644 --- a/docs/features/custom-agents.md +++ b/docs/features/custom-agents.md @@ -254,12 +254,12 @@ try (var client = new CopilotClient()) { | `infer` | `boolean` | | Whether the runtime can auto-select this agent (default: `true`) | | `skills` | `string[]` | | Skill names to preload into the agent's context at startup | | `model` | `string` | | Model identifier to use while this agent runs | -| `reasoningEffort` | `string` | | Reasoning effort to use while this agent runs. When omitted, no override is sent and the backend chooses its default | +| `reasoningEffort` | `string` | | Reasoning effort to use while this agent runs. When omitted, the SDK sends no per-agent override and the runtime resolves the effort (see note below) | > [!TIP] > A good `description` helps the runtime match user intent to the right agent. Be specific about the agent's expertise and capabilities. -Set `model` and `reasoningEffort` to override the parent session's model settings while a custom agent runs. When `reasoningEffort` is omitted, the SDK sends no per-agent override and the backend chooses its default. The parent session effort is not inherited, and the SDK does not add a per-agent default. Python uses `reasoning_effort`, .NET uses `ReasoningEffort`, Go uses `ReasoningEffort`, Java uses `setReasoningEffort`, and Rust uses `with_reasoning_effort`. +Set `model` and `reasoningEffort` to override the parent session's model settings while a custom agent runs. When `reasoningEffort` is omitted, the SDK sends no per-agent override and the runtime resolves the effort from its own precedence: a per-call client option, the resolved model's default, or the agent definition all take priority; otherwise the runtime inherits the parent session's effort only when the subagent runs the same model as the parent. When the subagent resolves to a different model, it falls back to that model's default instead of inheriting the parent's effort. Python uses `reasoning_effort`, .NET uses `ReasoningEffort`, Go uses `ReasoningEffort`, Java uses `setReasoningEffort`, and Rust uses `with_reasoning_effort`. In addition to per-agent configuration above, you can set `agent` on the **session config** itself to pre-select which custom agent is active when the session starts. See [Selecting an Agent at Session Creation](#selecting-an-agent-at-session-creation) below. @@ -445,9 +445,9 @@ Sub-agent-originated session events share the parent session stream and include | Event | Emitted when | Data | |-------|-------------|------| | `subagent.selected` | Runtime selects an agent for the task | `agentName`, `agentDisplayName`, `tools` | -| `subagent.started` | Sub-agent begins execution | `toolCallId`, `agentName`, `agentDisplayName`, `agentDescription` | -| `subagent.completed` | Sub-agent finishes successfully | `toolCallId`, `agentName`, `agentDisplayName` | -| `subagent.failed` | Sub-agent encounters an error | `toolCallId`, `agentName`, `agentDisplayName`, `error` | +| `subagent.started` | Sub-agent begins execution | `toolCallId`, `agentName`, `agentDisplayName`, `agentDescription`, `model?` | +| `subagent.completed` | Sub-agent finishes successfully | `toolCallId`, `agentName`, `agentDisplayName`, `model?`, `durationMs?`, `totalTokens?`, `totalToolCalls?` | +| `subagent.failed` | Sub-agent encounters an error | `toolCallId`, `agentName`, `agentDisplayName`, `error`, `model?`, `durationMs?`, `totalTokens?`, `totalToolCalls?` | | `subagent.deselected` | Runtime switches away from the sub-agent |—| ### Subscribing to events @@ -466,11 +466,15 @@ session.on((event) => { case "subagent.completed": console.log(`✅ Sub-agent completed: ${event.data.agentDisplayName}`); + if (event.data.durationMs !== undefined) console.log(` Duration: ${event.data.durationMs}ms`); + if (event.data.totalTokens !== undefined) console.log(` Tokens: ${event.data.totalTokens}`); + if (event.data.totalToolCalls !== undefined) console.log(` Tool calls: ${event.data.totalToolCalls}`); break; case "subagent.failed": console.log(`❌ Sub-agent failed: ${event.data.agentDisplayName}`); console.log(` Error: ${event.data.error}`); + if (event.data.durationMs !== undefined) console.log(` Duration: ${event.data.durationMs}ms`); break; case "subagent.selected": diff --git a/docs/features/fleet-mode.md b/docs/features/fleet-mode.md index cbb737b78..891a8ef04 100644 --- a/docs/features/fleet-mode.md +++ b/docs/features/fleet-mode.md @@ -176,7 +176,7 @@ Native typed bindings for fleet mode were verified in Node.js/TypeScript, Python Plan-mode UIs can start fleet deployment by returning the `autopilot_fleet` exit action. The generated session event types describe it as: ```typescript -type PlanModeExitAction = +type ExitPlanModeAction = | "exit_only" | "interactive" | "autopilot" diff --git a/docs/features/hooks.md b/docs/features/hooks.md index feee55546..6a7833990 100644 --- a/docs/features/hooks.md +++ b/docs/features/hooks.md @@ -9,20 +9,22 @@ A hook is a callback you register once when creating a session. The SDK invokes ```mermaid flowchart LR A[Session starts] -->|onSessionStart| B[User sends prompt] - B -->|onUserPromptSubmitted| C[Agent picks a tool] - C -->|onPreToolUse| D[Tool executes] - D -->|onPostToolUse| E{More work?} - E -->|yes| C - E -->|no| F[Session ends] - F -->|onSessionEnd| G((Done)) - C -.->|error| H[onErrorOccurred] - D -.->|error| H + B -->|onUserPromptSubmitted| C[Runtime transforms prompt] + C -->|onUserPromptTransformed| D[Agent picks a tool] + D -->|onPreToolUse| E[Tool executes] + E -->|onPostToolUse| F{More work?} + F -->|yes| D + F -->|no| G[Session ends] + G -->|onSessionEnd| H((Done)) + D -.->|error| I[onErrorOccurred] + E -.->|error| I ``` | Hook | When it fires | What you can do | | ------------------------------------------------------------------- | ----------------------------------- | ------------------------------------------ | | [`onSessionStart`](../hooks/session-lifecycle.md#session-start) | Session begins (new or resumed) | Inject context, load preferences | | [`onUserPromptSubmitted`](../hooks/user-prompt-submitted.md) | User sends a message | Rewrite prompts, add context, filter input | +| [`onUserPromptTransformed`](../hooks/user-prompt-transformed.md) | Runtime builds the model prompt | Inspect or replace model-facing content | | [`onPreToolUse`](../hooks/pre-tool-use.md) | Before a tool executes | Allow / deny / modify the call | | [`onPostToolUse`](../hooks/post-tool-use.md) | After a tool returns (success only) | Transform results, redact secrets, audit | | [`onPostToolUseFailure`](../hooks/post-tool-use.md#failure-variant) | After a tool returns a failure | Inject retry guidance, log failures | @@ -1055,6 +1057,7 @@ For full type definitions, input/output field tables, and additional examples fo * [Pre-Tool Use](../hooks/pre-tool-use.md) * [Post-Tool Use](../hooks/post-tool-use.md) * [User Prompt Submitted](../hooks/user-prompt-submitted.md) +* [User Prompt Transformed](../hooks/user-prompt-transformed.md) * [Session Lifecycle](../hooks/session-lifecycle.md) * [Error Handling](../hooks/error-handling.md) diff --git a/docs/features/mcp.md b/docs/features/mcp.md index 6f715bd2e..caac63327 100644 --- a/docs/features/mcp.md +++ b/docs/features/mcp.md @@ -154,6 +154,35 @@ await using var session = await client.CreateSessionAsync(new SessionConfig }); ``` +## Disabling configured servers per session + +Set `disabledMcpServers` to exact MCP server names that must not run in a session. +The setting is scoped to the individual create or resume request; it does not +modify global MCP settings or the server configuration. + +```typescript +const session = await client.createSession({ + mcpServers: { + filesystem: { type: "local", command: "npx", args: ["-y", "@modelcontextprotocol/server-filesystem", "."] }, + github: { type: "http", url: "https://api.githubcopilot.com/mcp/" }, + }, + disabledMcpServers: ["github"], +}); +``` + +| SDK | Configuration property | +| --- | --- | +| Node.js | `disabledMcpServers` | +| Python | `disabled_mcp_servers` | +| Go | `DisabledMCPServers` | +| .NET | `DisabledMcpServers` | +| Java | `setDisabledMcpServers(...)` | +| Rust | `with_disabled_mcp_servers(...)` | + +On session creation and a **cold** resume, disabled servers are not started and +the runtime does not initiate their authentication. A resident resume cannot +undo a server that the runtime has already spawned. Names are matched exactly. + ## Tool configuration You can control which tools are available to an MCP server using the `tools` field. diff --git a/docs/features/plugin-directories.md b/docs/features/plugin-directories.md index ccd95df95..4af11d9a7 100644 --- a/docs/features/plugin-directories.md +++ b/docs/features/plugin-directories.md @@ -240,6 +240,41 @@ let client = Client::start( > The example above uses an stdio runtime connection — the default when the SDK bundles the CLI. If you connect to an external runtime via a URL (`forUri` / `ForUri`), pass `--plugin-dir` to the long-running CLI server when you start it; the SDK does not forward `--plugin-dir` to runtimes it didn't spawn. +## Trusted host-bundled plugin directories + +Applications that ship their own trusted plugins can register them as a client startup option. The SDK sends the complete ordered set after connecting and verifying the protocol, before `start` returns or any session can be created. Paths must be absolute; leaving the option unset or empty makes no RPC call. + + +```typescript +import { CopilotClient } from "@github/copilot-sdk"; + +async function main() { + const client = new CopilotClient({ + builtinPluginDirectories: [ + "/opt/my-app/copilot-plugins/core", + "/opt/my-app/copilot-plugins/github", + ], + }); + await client.start(); +} + +main(); +``` + + +The equivalent option in each SDK is: + +| SDK | Startup option | +|---|---| +| Node.js / TypeScript | `builtinPluginDirectories: string[]` | +| Python | `builtin_plugin_directories=[...]` | +| Go | `BuiltinPluginDirectories: []string{...}` | +| .NET | `BuiltinPluginDirectories = [...]` | +| Java | `.setBuiltinPluginDirectories(List.of(Path.of(...)))` | +| Rust | `.with_builtin_plugin_directories([...])` | + +This is a trust boundary for plugins bundled and controlled by the host application. It is distinct from `--plugin-dir`, which is a CLI process launch argument for explicitly loading ordinary plugin directories. The startup option also works when connecting to an existing runtime because it is sent over JSON-RPC rather than forwarded as a process argument. + ## What a plugin can contribute Loading a plugin directory makes its extensions visible to every session created by the client. The runtime merges plugin-provided extensions with anything you register inline: diff --git a/docs/features/remote-sessions.md b/docs/features/remote-sessions.md index 5a15ca825..f103b9cf6 100644 --- a/docs/features/remote-sessions.md +++ b/docs/features/remote-sessions.md @@ -13,7 +13,7 @@ For running sessions on GitHub-hosted compute, see [Cloud Sessions](./cloud-sess ### Always-on (client-level) -Set `remote: true` when creating the client. Every session in a GitHub repo automatically gets a remote URL. +Set `enableRemoteSessions: true` when creating the client. Every session in a GitHub repo automatically gets a remote URL. @@ -23,7 +23,7 @@ Set `remote: true` when creating the client. Every session in a GitHub repo auto ```typescript import { CopilotClient } from "@github/copilot-sdk"; -const client = new CopilotClient({ remote: true }); +const client = new CopilotClient({ enableRemoteSessions: true }); const session = await client.createSession({ workingDirectory: "/path/to/github-repo", onPermissionRequest: async () => ({ allowed: true }), @@ -59,7 +59,7 @@ session.on(on_event) ```go -client, _ := copilot.NewClient(&copilot.ClientOptions{Remote: true}) +client := copilot.NewClient(&copilot.ClientOptions{EnableRemoteSessions: true}) session, _ := client.CreateSession(ctx, &copilot.SessionConfig{ WorkingDirectory: "/path/to/github-repo", OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) { @@ -78,7 +78,7 @@ session.On(func(event copilot.SessionEvent) { ```csharp -var client = new CopilotClient(new CopilotClientOptions { Remote = true }); +var client = new CopilotClient(new CopilotClientOptions { EnableRemoteSessions = true }); var session = await client.CreateSessionAsync(new SessionConfig { WorkingDirectory = "/path/to/github-repo", @@ -201,6 +201,6 @@ The remote URL can be rendered as a QR code for easy mobile access. The SDK prov ## Notes -* The `remote` client option only applies when the SDK spawns the CLI process. It is ignored when connecting to an external server via `cliUrl`. +* The `enableRemoteSessions` client option applies when the SDK starts the runtime, either as a child process or as an in-process host. It is ignored when connecting to an already-running runtime. * If the working directory is not a GitHub repository, remote setup is silently skipped (always-on mode) or returns an error (on-demand mode). * Remote sessions require authentication. Ensure `gitHubToken` or `useLoggedInUser` is configured. diff --git a/docs/features/session-limits.md b/docs/features/session-limits.md index b2e4cfe47..e5cf624cd 100644 --- a/docs/features/session-limits.md +++ b/docs/features/session-limits.md @@ -131,7 +131,7 @@ let limits = SessionLimitsConfig { let session = client .create_session( - SessionConfig::new() + SessionConfig::default() .approve_all_permissions() .with_session_limits(limits.clone()), ) diff --git a/docs/features/streaming-events.md b/docs/features/streaming-events.md index f359dd606..10f111d9f 100644 --- a/docs/features/streaming-events.md +++ b/docs/features/streaming-events.md @@ -210,7 +210,7 @@ session.on(AssistantMessageDeltaEvent.class, event ->
> [!TIP] -> **(Python / Go)** These SDKs use a single `Data` class/struct with all possible fields as optional/nullable. Only the fields listed in the tables below are populated for each event type—the rest will be `None` / `nil`. +> **(Python / Go)** These SDKs use separate, per-event data types (for example, `AssistantMessageDeltaData`), so only the relevant fields exist on each type. > > [!TIP] > **(.NET)** The .NET SDK uses separate, strongly-typed data classes per event (e.g., `AssistantMessageDeltaData`), so only the relevant fields exist on each type. @@ -443,12 +443,20 @@ Ephemeral. Token usage and cost information for an individual API call. | `model` | `string` | ✅ | Model identifier (e.g., `"gpt-5.4"`) | | `inputTokens` | `number` | | Input tokens consumed | | `outputTokens` | `number` | | Output tokens produced | +| `reasoningTokens` | `number` | | Output tokens used for reasoning/chain-of-thought (subset of `outputTokens`) | | `cacheReadTokens` | `number` | | Tokens read from prompt cache | | `cacheWriteTokens` | `number` | | Tokens written to prompt cache | +| `cacheExpiresAt` | `string` | | ISO 8601 timestamp when the prompt cache for this model call expires | +| `contentFilterTriggered` | `boolean` | | Whether the response was blocked or truncated by content filtering (`finish_reason === 'content_filter'`) | +| `finishReason` | `string` | | Model finish reason (e.g., `"stop"`, `"length"`, `"tool_calls"`, `"content_filter"`) | | `cost` | `number` | | Model multiplier cost for billing | | `duration` | `number` | | API call duration in milliseconds | +| `timeToFirstTokenMs` | `number` | | Time from request dispatch to first token received (streaming latency) | +| `interTokenLatencyMs` | `number` | | Average latency between consecutive tokens (streaming throughput) | +| `reasoningEffort` | `string` | | Reasoning effort level used for this call (e.g., `"low"`, `"medium"`, `"high"`) | | `initiator` | `string` | | What triggered this call (e.g., `"sub-agent"`); absent for user-initiated | | `apiCallId` | `string` | | Completion ID from the provider (e.g., `chatcmpl-abc123`) | +| `serviceRequestId` | `string` | | Copilot service request ID (`x-copilot-service-request-id`) for CAPI log correlation | | `apiEndpoint` | `"/chat/completions" \| "/v1/messages" \| "/responses" \| "ws:/responses"` | | API endpoint used for the model call; useful for observability and cost attribution. `ws:/responses` is the websocket variant of the responses API | | `providerCallId` | `string` | | GitHub request tracing ID (`x-github-request-id`) | | `parentToolCallId` | `string` | | Deprecated. Use envelope-level `agentId` for sub-agent attribution | @@ -540,7 +548,7 @@ Ephemeral. The agent has finished all processing and is ready for the next messa | Data Field | Type | Required | Description | |------------|------|----------|-------------| -| `backgroundTasks` | `BackgroundTasks` | | Background agents/shells still running when the agent became idle | +| `aborted` | `boolean` | | True when the preceding turn was cancelled via abort signal | ### `session.error` @@ -653,7 +661,7 @@ These events are emitted when the agent needs approval or input from the user be ### `permission.requested` -Ephemeral. The agent needs permission to perform an action (run a command, write a file, etc.). +The agent needs permission to perform an action (run a command, write a file, etc.). | Data Field | Type | Required | Description | |------------|------|----------|-------------| @@ -676,7 +684,7 @@ All `kind` variants also include an optional `toolCallId` linking back to the to ### `permission.completed` -Ephemeral. A permission request was resolved. +A permission request was resolved. | Data Field | Type | Required | Description | |------------|------|----------|-------------| @@ -733,6 +741,7 @@ A custom agent was invoked as a sub-agent. | `agentName` | `string` | ✅ | Internal name of the sub-agent | | `agentDisplayName` | `string` | ✅ | Human-readable display name | | `agentDescription` | `string` | ✅ | Description of what the sub-agent does | +| `model` | `string` | | Model the sub-agent will run with, when known at start | ### `subagent.completed` @@ -743,6 +752,10 @@ A sub-agent finished successfully. | `toolCallId` | `string` | ✅ | Matches the corresponding `subagent.started` | | `agentName` | `string` | ✅ | Internal name | | `agentDisplayName` | `string` | ✅ | Display name | +| `model` | `string` | | Model used by the sub-agent | +| `durationMs` | `number` | | Wall-clock execution duration in milliseconds | +| `totalTokens` | `number` | | Total input and output tokens consumed | +| `totalToolCalls` | `number` | | Total tool calls made | ### `subagent.failed` @@ -754,6 +767,10 @@ A sub-agent encountered an error. | `agentName` | `string` | ✅ | Internal name | | `agentDisplayName` | `string` | ✅ | Display name | | `error` | `string` | ✅ | Error message | +| `model` | `string` | | Model selected for the sub-agent, when known | +| `durationMs` | `number` | | Wall-clock execution duration in milliseconds | +| `totalTokens` | `number` | | Total input and output tokens consumed before failure | +| `totalToolCalls` | `number` | | Total tool calls made before failure | ### `subagent.selected` @@ -818,7 +835,7 @@ A system or developer prompt was injected into the conversation. ### `external_tool.requested` -Ephemeral. The agent wants to invoke an external tool (one provided by the SDK consumer). +The agent wants to invoke an external tool (one provided by the SDK consumer). | Data Field | Type | Required | Description | |------------|------|----------|-------------| @@ -830,7 +847,7 @@ Ephemeral. The agent wants to invoke an external tool (one provided by the SDK c ### `external_tool.completed` -Ephemeral. An external tool request was resolved. +An external tool request was resolved. | Data Field | Type | Required | Description | |------------|------|----------|-------------| @@ -908,8 +925,8 @@ assistant.turn_start → Turn begins ├── assistant.usage → Token usage for this API call (ephemeral) │ ├── [If tools were requested:] -│ ├── permission.requested → Needs user approval (ephemeral) -│ ├── permission.completed → Approval result (ephemeral) +│ ├── permission.requested → Needs user approval +│ ├── permission.completed → Approval result │ ├── tool.execution_start → Tool begins │ ├── tool.execution_partial_result → Streaming tool output (ephemeral, repeated) │ ├── tool.execution_progress → Progress updates (ephemeral, repeated) @@ -941,7 +958,7 @@ This table lists key `data` payload fields. Common envelope fields are documente | `tool.execution_partial_result` | ✅ | Tool | `toolCallId`, `partialOutput` | | `tool.execution_progress` | ✅ | Tool | `toolCallId`, `progressMessage` | | `tool.execution_complete` | | Tool | `toolCallId`, `success`, `result?`, `error?` | -| `session.idle` | ✅ | Session | `backgroundTasks?` | +| `session.idle` | ✅ | Session | `aborted?` | | `session.error` | | Session | `errorType`, `message`, `statusCode?` | | `session.compaction_start` | | Session | *(empty)* | | `session.compaction_complete` | | Session | `success`, `preCompactionTokens?`, `summaryContent?` | @@ -952,23 +969,23 @@ This table lists key `data` payload fields. Common envelope fields are documente | `session.usage_checkpoint` | | Session | `totalNanoAiu`, `totalPremiumRequests?` | | `session.task_complete` | | Session | `summary?` | | `session.shutdown` | | Session | `shutdownType`, `codeChanges`, `modelMetrics` | -| `permission.requested` | ✅ | Permission | `requestId`, `permissionRequest` | -| `permission.completed` | ✅ | Permission | `requestId`, `result.kind` | +| `permission.requested` | | Permission | `requestId`, `permissionRequest` | +| `permission.completed` | | Permission | `requestId`, `result.kind` | | `user_input.requested` | ✅ | User Input | `requestId`, `question`, `choices?` | | `user_input.completed` | ✅ | User Input | `requestId` | | `elicitation.requested` | ✅ | User Input | `requestId`, `message`, `requestedSchema` | | `elicitation.completed` | ✅ | User Input | `requestId` | -| `subagent.started` | | Sub-Agent | `toolCallId`, `agentName`, `agentDisplayName` | -| `subagent.completed` | | Sub-Agent | `toolCallId`, `agentName`, `agentDisplayName` | -| `subagent.failed` | | Sub-Agent | `toolCallId`, `agentName`, `error` | +| `subagent.started` | | Sub-Agent | `toolCallId`, `agentName`, `agentDisplayName`, `model?` | +| `subagent.completed` | | Sub-Agent | `toolCallId`, `agentName`, `agentDisplayName`, `model?`, `durationMs?`, `totalTokens?`, `totalToolCalls?` | +| `subagent.failed` | | Sub-Agent | `toolCallId`, `agentName`, `error`, `model?`, `durationMs?`, `totalTokens?`, `totalToolCalls?` | | `subagent.selected` | | Sub-Agent | `agentName`, `agentDisplayName`, `tools` | | `subagent.deselected` | | Sub-Agent | *(empty)* | | `skill.invoked` | | Skill | `name`, `path`, `content`, `allowedTools?` | | `abort` | | Control | `reason` | | `user.message` | | User | `content`, `attachments?`, `agentMode?` | | `system.message` | | System | `content`, `role` | -| `external_tool.requested` | ✅ | External Tool | `requestId`, `toolName`, `arguments?` | -| `external_tool.completed` | ✅ | External Tool | `requestId` | +| `external_tool.requested` | | External Tool | `requestId`, `toolName`, `arguments?` | +| `external_tool.completed` | | External Tool | `requestId` | | `command.queued` | ✅ | Command | `requestId`, `command` | | `command.completed` | ✅ | Command | `requestId` | | `session_limits_exhausted.requested` | ✅ | Session | `requestId`, `maxAiCredits`, `usedAiCredits` | diff --git a/docs/features/usage-and-billing.md b/docs/features/usage-and-billing.md new file mode 100644 index 000000000..ec662b685 --- /dev/null +++ b/docs/features/usage-and-billing.md @@ -0,0 +1,1355 @@ +# Usage and billing metrics + +This guide shows how to read token counts, context-window utilization, AI credit cost, and account quota from a Copilot SDK application. Examples are shown for TypeScript, Python, Go, .NET, Java, and Rust. + +> [!TIP] +> Each example is functionally equivalent across languages. The TypeScript snippet is expanded by default; select your language from the collapsible blocks to see the same logic in that SDK. + +## Overview + +The SDK surfaces usage data through two complementary mechanisms: + +* **Session events**: ephemeral events the runtime emits as a turn runs. Subscribe to these for real-time, per-API-call data. +* **RPC methods**: request/response calls you make on demand. Use these to snapshot accumulated totals or look up account-level quota. + +The table below maps each signal to the API that exposes it. + +| Signal | API | Scope | Type | +|---|---|---|---| +| Per-call token counts | `assistant.usage` event | Session | Event | +| Context-window utilization | `session.usage_info` event | Session | Event | +| Context-window breakdown (on demand) | `session.metadata.contextInfo` | Session | RPC | +| Accumulated AI credit and token totals | `session.usage.getMetrics` | Session | RPC | +| Per-model AI credit pricing | `models.list` | Server | RPC | +| Account quota and premium interactions | `account.getQuota` | Server | RPC | + +> [!NOTE] +> `session.usage.getMetrics`, `session.metadata.contextInfo`, and `session.metadata.recomputeContextTokens` are marked experimental in the generated RPC surface. In .NET they raise the `GHCP001` experimental diagnostic, which you suppress with `#pragma warning disable GHCP001` or a project-level `GHCP001`. Pin both the SDK and the Copilot CLI runtime if your application depends on them. + +The field tables below list only the fields used in the examples on this page. The complete, always-current field reference is the generated SDK types plus [Streaming events](./streaming-events.md), which is regenerated from the CLI schema on every dependency bump. Treat those as the source of truth and this page as a task-oriented guide. + +## Per-call token counts + +The `assistant.usage` event is emitted once for every model API call in a turn (including calls made by sub-agents). It carries the token counts and the billing multiplier for that single call. + +The example below uses these fields. See [Streaming events](./streaming-events.md#assistantusage) for the full list, including cache, reasoning, latency, and tracing fields. + +| Field | Type | Description | +|---|---|---| +| `model` | `string` | Model identifier for this call | +| `inputTokens` | `number` | Input tokens consumed | +| `outputTokens` | `number` | Output tokens produced | +| `cost` | `number` | Premium request multiplier applied to this call | + +> [!TIP] +> `assistant.usage` is ephemeral, so it is delivered live but not replayed when you resume a session. To read accumulated totals after the fact, call `session.usage.getMetrics` (see [Accumulated AI credit and token totals](#accumulated-ai-credit-and-token-totals)). + +
+Node.js / TypeScript + + +```typescript +import { CopilotClient } from "@github/copilot-sdk"; + +const client = new CopilotClient(); +const session = await client.createSession({ streaming: true }); + +session.on("assistant.usage", (event) => { + const { model, inputTokens, outputTokens, cost } = event.data; + console.log( + `${model}: in=${inputTokens ?? 0} out=${outputTokens ?? 0} cost=${cost ?? 0}`, + ); +}); +``` + + +```typescript +session.on("assistant.usage", (event) => { + const { model, inputTokens, outputTokens, cost } = event.data; + console.log( + `${model}: in=${inputTokens ?? 0} out=${outputTokens ?? 0} cost=${cost ?? 0}`, + ); +}); +``` + +
+ +
+Python + + +```python +from copilot import CopilotClient +from copilot.session_events import SessionEventType + +client = CopilotClient() +session = await client.create_session(streaming=True) + +def on_usage(event): + if event.type == SessionEventType.ASSISTANT_USAGE: + data = event.data + print(f"{data.model}: in={data.input_tokens or 0} out={data.output_tokens or 0} cost={data.cost or 0}") + +session.on(on_usage) +``` + + +```python +def on_usage(event): + if event.type == SessionEventType.ASSISTANT_USAGE: + data = event.data + print(f"{data.model}: in={data.input_tokens or 0} out={data.output_tokens or 0} cost={data.cost or 0}") + +session.on(on_usage) +``` + +
+ +
+Go + + +```go +package main + +import ( + "context" + "fmt" + + copilot "github.com/github/copilot-sdk/go" + "github.com/github/copilot-sdk/go/rpc" +) + +func main() { + ctx := context.Background() + client := copilot.NewClient(nil) + client.Start(ctx) + + session, _ := client.CreateSession(ctx, &copilot.SessionConfig{ + Streaming: copilot.Bool(true), + OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) { + return &rpc.PermissionDecisionApproveOnce{}, nil + }, + }) + + session.On(func(event copilot.SessionEvent) { + d, ok := event.Data.(*copilot.AssistantUsageData) + if !ok { + return + } + in, out, cost := int64(0), int64(0), float64(0) + if d.InputTokens != nil { + in = *d.InputTokens + } + if d.OutputTokens != nil { + out = *d.OutputTokens + } + if d.Cost != nil { + cost = *d.Cost + } + fmt.Printf("%s: in=%d out=%d cost=%g\n", d.Model, in, out, cost) + }) + _ = session +} +``` + + +```go +session.On(func(event copilot.SessionEvent) { + d, ok := event.Data.(*copilot.AssistantUsageData) + if !ok { + return + } + in, out, cost := int64(0), int64(0), float64(0) + if d.InputTokens != nil { + in = *d.InputTokens + } + if d.OutputTokens != nil { + out = *d.OutputTokens + } + if d.Cost != nil { + cost = *d.Cost + } + fmt.Printf("%s: in=%d out=%d cost=%g\n", d.Model, in, out, cost) +}) +``` + +
+ +
+.NET + + +```csharp +using GitHub.Copilot; + +await using var client = new CopilotClient(); +await using var session = await client.CreateSessionAsync(new SessionConfig { Streaming = true }); + +session.On(evt => +{ + var data = evt.Data; + Console.WriteLine( + $"{data.Model}: in={data.InputTokens ?? 0} out={data.OutputTokens ?? 0} cost={data.Cost ?? 0}"); +}); +``` + + +```csharp +session.On(evt => +{ + var data = evt.Data; + Console.WriteLine( + $"{data.Model}: in={data.InputTokens ?? 0} out={data.OutputTokens ?? 0} cost={data.Cost ?? 0}"); +}); +``` + +
+ +
+Java + + +```java +session.on(AssistantUsageEvent.class, event -> { + var data = event.getData(); + long in = data.inputTokens() != null ? data.inputTokens() : 0; + long out = data.outputTokens() != null ? data.outputTokens() : 0; + double cost = data.cost() != null ? data.cost() : 0.0; + System.out.printf("%s: in=%d out=%d cost=%s%n", data.model(), in, out, cost); +}); +``` + +
+ +
+Rust + +```rust +use github_copilot_sdk::session_events::AssistantUsageData; + +let mut events = session.subscribe(); +while let Ok(event) = events.recv().await { + if event.event_type == "assistant.usage" { + if let Some(data) = event.typed_data::() { + println!( + "{}: in={} out={} cost={}", + data.model, + data.input_tokens.unwrap_or(0), + data.output_tokens.unwrap_or(0), + data.cost.unwrap_or(0.0), + ); + } + } +} +``` + +
+ +## Context-window utilization + +Token counts tell you what each call consumed. Context-window utilization tells you how full the model's prompt window is right now—useful for showing a progress bar or warning the user before automatic compaction kicks in. + +### Live updates with `session.usage_info` + +The runtime emits a `session.usage_info` event whenever the context-window size changes. The example uses `currentTokens` and `tokenLimit`; see [Streaming events](./streaming-events.md#sessionusage_info) for the complete payload. + +| Field | Type | Description | +|---|---|---| +| `currentTokens` | `number` | Tokens currently in the context window | +| `tokenLimit` | `number` | Maximum tokens for the model's context window | + +
+Node.js / TypeScript + + +```typescript +import { CopilotClient } from "@github/copilot-sdk"; + +const client = new CopilotClient(); +const session = await client.createSession({ streaming: true }); + +session.on("session.usage_info", (event) => { + const { currentTokens, tokenLimit } = event.data; + const pct = Math.round((currentTokens / tokenLimit) * 100); + console.log(`Context: ${currentTokens}/${tokenLimit} (${pct}%)`); +}); +``` + + +```typescript +session.on("session.usage_info", (event) => { + const { currentTokens, tokenLimit } = event.data; + const pct = Math.round((currentTokens / tokenLimit) * 100); + console.log(`Context: ${currentTokens}/${tokenLimit} (${pct}%)`); +}); +``` + +
+ +
+Python + + +```python +from copilot import CopilotClient +from copilot.session_events import SessionEventType + +client = CopilotClient() +session = await client.create_session(streaming=True) + +def on_usage_info(event): + if event.type == SessionEventType.SESSION_USAGE_INFO: + data = event.data + pct = round(data.current_tokens / data.token_limit * 100) + print(f"Context: {data.current_tokens}/{data.token_limit} ({pct}%)") + +session.on(on_usage_info) +``` + + +```python +def on_usage_info(event): + if event.type == SessionEventType.SESSION_USAGE_INFO: + data = event.data + pct = round(data.current_tokens / data.token_limit * 100) + print(f"Context: {data.current_tokens}/{data.token_limit} ({pct}%)") + +session.on(on_usage_info) +``` + +
+ +
+Go + + +```go +package main + +import ( + "context" + "fmt" + + copilot "github.com/github/copilot-sdk/go" + "github.com/github/copilot-sdk/go/rpc" +) + +func main() { + ctx := context.Background() + client := copilot.NewClient(nil) + client.Start(ctx) + + session, _ := client.CreateSession(ctx, &copilot.SessionConfig{ + Streaming: copilot.Bool(true), + OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) { + return &rpc.PermissionDecisionApproveOnce{}, nil + }, + }) + + session.On(func(event copilot.SessionEvent) { + d, ok := event.Data.(*copilot.SessionUsageInfoData) + if !ok { + return + } + pct := int(float64(d.CurrentTokens) / float64(d.TokenLimit) * 100) + fmt.Printf("Context: %d/%d (%d%%)\n", d.CurrentTokens, d.TokenLimit, pct) + }) + _ = session +} +``` + + +```go +session.On(func(event copilot.SessionEvent) { + d, ok := event.Data.(*copilot.SessionUsageInfoData) + if !ok { + return + } + pct := int(float64(d.CurrentTokens) / float64(d.TokenLimit) * 100) + fmt.Printf("Context: %d/%d (%d%%)\n", d.CurrentTokens, d.TokenLimit, pct) +}) +``` + +
+ +
+.NET + + +```csharp +using GitHub.Copilot; + +await using var client = new CopilotClient(); +await using var session = await client.CreateSessionAsync(new SessionConfig { Streaming = true }); + +session.On(evt => +{ + var pct = (int)Math.Round((double)evt.Data.CurrentTokens / evt.Data.TokenLimit * 100); + Console.WriteLine($"Context: {evt.Data.CurrentTokens}/{evt.Data.TokenLimit} ({pct}%)"); +}); +``` + + +```csharp +session.On(evt => +{ + var pct = (int)Math.Round((double)evt.Data.CurrentTokens / evt.Data.TokenLimit * 100); + Console.WriteLine($"Context: {evt.Data.CurrentTokens}/{evt.Data.TokenLimit} ({pct}%)"); +}); +``` + +
+ +
+Java + + +```java +session.on(SessionUsageInfoEvent.class, event -> { + var data = event.getData(); + long pct = Math.round((double) data.currentTokens() / data.tokenLimit() * 100); + System.out.printf("Context: %d/%d (%d%%)%n", data.currentTokens(), data.tokenLimit(), pct); +}); +``` + +
+ +
+Rust + +```rust +use github_copilot_sdk::session_events::SessionUsageInfoData; + +let mut events = session.subscribe(); +while let Ok(event) = events.recv().await { + if event.event_type == "session.usage_info" { + if let Some(data) = event.typed_data::() { + let pct = (data.current_tokens as f64 / data.token_limit as f64 * 100.0) as i64; + println!("Context: {}/{} ({}%)", data.current_tokens, data.token_limit, pct); + } + } +} +``` + +
+ +### On-demand breakdown with `session.metadata.contextInfo` + +Events only fire when the context changes. To read the current breakdown at any moment—for example, right after resuming a session—call `session.metadata.contextInfo`. Pass `0` for `promptTokenLimit` to use the runtime default; pass `0` for `outputTokenLimit` if the value is unknown. + +The result's `contextInfo` is `null` until the session has been initialized (the system prompt and tool metadata have been cached). It breaks the total down into `systemTokens`, `conversationTokens`, and `toolDefinitionsTokens`, alongside the `promptTokenLimit`. + +
+Node.js / TypeScript + + +```typescript +import { CopilotClient } from "@github/copilot-sdk"; + +const client = new CopilotClient(); +const session = await client.createSession({}); + +const { contextInfo } = await session.rpc.metadata.contextInfo({ + promptTokenLimit: 0, + outputTokenLimit: 0, +}); + +if (contextInfo) { + console.log( + `Total ${contextInfo.totalTokens}/${contextInfo.promptTokenLimit} ` + + `(system=${contextInfo.systemTokens}, conversation=${contextInfo.conversationTokens})`, + ); +} +``` + + +```typescript +const { contextInfo } = await session.rpc.metadata.contextInfo({ + promptTokenLimit: 0, + outputTokenLimit: 0, +}); + +if (contextInfo) { + console.log( + `Total ${contextInfo.totalTokens}/${contextInfo.promptTokenLimit} ` + + `(system=${contextInfo.systemTokens}, conversation=${contextInfo.conversationTokens})`, + ); +} +``` + +
+ +
+Python + + +```python +from copilot import CopilotClient +from copilot.rpc import MetadataContextInfoRequest + +client = CopilotClient() +session = await client.create_session() + +result = await session.rpc.metadata.context_info( + MetadataContextInfoRequest(prompt_token_limit=0, output_token_limit=0) +) +info = result.context_info + +if info is not None: + print( + f"Total {info.total_tokens}/{info.prompt_token_limit} " + f"(system={info.system_tokens}, conversation={info.conversation_tokens})" + ) +``` + + +```python +result = await session.rpc.metadata.context_info( + MetadataContextInfoRequest(prompt_token_limit=0, output_token_limit=0) +) +info = result.context_info + +if info is not None: + print( + f"Total {info.total_tokens}/{info.prompt_token_limit} " + f"(system={info.system_tokens}, conversation={info.conversation_tokens})" + ) +``` + +
+ +
+Go + + +```go +package main + +import ( + "context" + "fmt" + + copilot "github.com/github/copilot-sdk/go" + "github.com/github/copilot-sdk/go/rpc" +) + +func main() { + ctx := context.Background() + client := copilot.NewClient(nil) + client.Start(ctx) + + session, _ := client.CreateSession(ctx, &copilot.SessionConfig{}) + + result, _ := session.RPC.Metadata.ContextInfo(ctx, &rpc.MetadataContextInfoRequest{ + PromptTokenLimit: 0, + OutputTokenLimit: 0, + }) + + if info := result.ContextInfo; info != nil { + fmt.Printf("Total %d/%d (system=%d, conversation=%d)\n", + info.TotalTokens, info.PromptTokenLimit, info.SystemTokens, info.ConversationTokens) + } +} +``` + + +```go +result, _ := session.RPC.Metadata.ContextInfo(ctx, &rpc.MetadataContextInfoRequest{ + PromptTokenLimit: 0, + OutputTokenLimit: 0, +}) + +if info := result.ContextInfo; info != nil { + fmt.Printf("Total %d/%d (system=%d, conversation=%d)\n", + info.TotalTokens, info.PromptTokenLimit, info.SystemTokens, info.ConversationTokens) +} +``` + +
+ +
+.NET + + +```csharp +#pragma warning disable GHCP001 +using GitHub.Copilot; + +await using var client = new CopilotClient(); +await using var session = await client.CreateSessionAsync(new SessionConfig()); + +var result = await session.Rpc.Metadata.ContextInfoAsync(promptTokenLimit: 0, outputTokenLimit: 0); +var info = result.ContextInfo; + +if (info is not null) +{ + Console.WriteLine( + $"Total {info.TotalTokens}/{info.PromptTokenLimit} " + + $"(system={info.SystemTokens}, conversation={info.ConversationTokens})"); +} +#pragma warning restore GHCP001 +``` + + +```csharp +var result = await session.Rpc.Metadata.ContextInfoAsync(promptTokenLimit: 0, outputTokenLimit: 0); +var info = result.ContextInfo; + +if (info is not null) +{ + Console.WriteLine( + $"Total {info.TotalTokens}/{info.PromptTokenLimit} " + + $"(system={info.SystemTokens}, conversation={info.ConversationTokens})"); +} +``` + +
+ +
+Java + + +```java +var result = session.getRpc().metadata + .contextInfo(new SessionMetadataContextInfoParams(null, 0L, 0L, null)) + .join(); +var info = result.contextInfo(); + +if (info != null) { + System.out.printf("Total %d/%d (system=%d, conversation=%d)%n", + info.totalTokens(), info.promptTokenLimit(), info.systemTokens(), info.conversationTokens()); +} +``` + +
+ +
+Rust + +```rust +use github_copilot_sdk::rpc::MetadataContextInfoRequest; + +let result = session + .rpc() + .metadata() + .context_info(MetadataContextInfoRequest { + prompt_token_limit: 0, + output_token_limit: 0, + selected_model: None, + }) + .await?; + +if let Some(info) = result.context_info { + println!( + "Total {}/{} (system={}, conversation={})", + info.total_tokens, info.prompt_token_limit, info.system_tokens, info.conversation_tokens, + ); +} +``` + +
+ +## Accumulated AI credit and token totals + +`session.usage.getMetrics` returns the running totals for the whole session in a single call. This is the cleanest way to read AI credit cost, because it aggregates every API call (main agent and sub-agents) for you. + +The example uses the fields below. The generated `UsageGetMetricsResult` type is the full reference. + +| Field | Type | Description | +|---|---|---| +| `totalNanoAiu` | `number` | Session-wide AI credit cost, in nano-AI units | +| `totalPremiumRequestCost` | `number` | Premium request cost across all models, after multipliers | +| `modelMetrics` | `Record` | Per-model breakdown; each entry has `usage.inputTokens`, `usage.outputTokens`, and `totalNanoAiu` | + +> [!NOTE] +> Cost is reported in **nano-AI units** (the field is named `totalNanoAiu`). The exact conversion to AI credits and the precise meaning of premium request accounting are defined by GitHub Copilot billing, not by the SDK—treat [GitHub's Copilot billing documentation](https://docs.github.com/en/copilot/managing-copilot/understanding-and-managing-copilot-usage) as the source of truth and verify before surfacing currency-like values to users. The examples divide by `1e9` as a convenience, following the SI `nano` prefix; confirm this matches current billing before relying on it. The `modelMetrics` and `tokenDetails` maps are keyed by runtime strings (model IDs and token-type names) that the SDK type system does not validate. + +
+Node.js / TypeScript + + +```typescript +import { CopilotClient } from "@github/copilot-sdk"; + +const client = new CopilotClient(); +const session = await client.createSession({}); + +const metrics = await session.rpc.usage.getMetrics(); + +const aiCredits = (metrics.totalNanoAiu ?? 0) / 1e9; +console.log(`AI credits used: ${aiCredits.toFixed(6)}`); +console.log(`Premium requests: ${metrics.totalPremiumRequestCost}`); + +for (const [model, m] of Object.entries(metrics.modelMetrics)) { + if (!m) continue; + console.log( + `${model}: in=${m.usage.inputTokens} out=${m.usage.outputTokens} ` + + `nanoAiu=${m.totalNanoAiu ?? 0}`, + ); +} +``` + + +```typescript +const metrics = await session.rpc.usage.getMetrics(); + +const aiCredits = (metrics.totalNanoAiu ?? 0) / 1e9; +console.log(`AI credits used: ${aiCredits.toFixed(6)}`); +console.log(`Premium requests: ${metrics.totalPremiumRequestCost}`); + +for (const [model, m] of Object.entries(metrics.modelMetrics)) { + if (!m) continue; + console.log( + `${model}: in=${m.usage.inputTokens} out=${m.usage.outputTokens} ` + + `nanoAiu=${m.totalNanoAiu ?? 0}`, + ); +} +``` + +
+ +
+Python + + +```python +from copilot import CopilotClient + +client = CopilotClient() +session = await client.create_session() + +metrics = await session.rpc.usage.get_metrics() + +ai_credits = (metrics.total_nano_aiu or 0) / 1e9 +print(f"AI credits used: {ai_credits:.6f}") +print(f"Premium requests: {metrics.total_premium_request_cost}") + +for model, m in metrics.model_metrics.items(): + print(f"{model}: in={m.usage.input_tokens} out={m.usage.output_tokens} nanoAiu={m.total_nano_aiu or 0}") +``` + + +```python +metrics = await session.rpc.usage.get_metrics() + +ai_credits = (metrics.total_nano_aiu or 0) / 1e9 +print(f"AI credits used: {ai_credits:.6f}") +print(f"Premium requests: {metrics.total_premium_request_cost}") + +for model, m in metrics.model_metrics.items(): + print(f"{model}: in={m.usage.input_tokens} out={m.usage.output_tokens} nanoAiu={m.total_nano_aiu or 0}") +``` + +
+ +
+Go + + +```go +package main + +import ( + "context" + "fmt" + + copilot "github.com/github/copilot-sdk/go" +) + +func main() { + ctx := context.Background() + client := copilot.NewClient(nil) + client.Start(ctx) + + session, _ := client.CreateSession(ctx, &copilot.SessionConfig{}) + + metrics, _ := session.RPC.Usage.GetMetrics(ctx) + + aiCredits := float64(0) + if metrics.TotalNanoAiu != nil { + aiCredits = *metrics.TotalNanoAiu / 1e9 + } + fmt.Printf("AI credits used: %.6f\n", aiCredits) + fmt.Printf("Premium requests: %v\n", metrics.TotalPremiumRequestCost) + + for model, m := range metrics.ModelMetrics { + nanoAiu := float64(0) + if m.TotalNanoAiu != nil { + nanoAiu = *m.TotalNanoAiu + } + fmt.Printf("%s: in=%d out=%d nanoAiu=%v\n", model, m.Usage.InputTokens, m.Usage.OutputTokens, nanoAiu) + } +} +``` + + +```go +metrics, _ := session.RPC.Usage.GetMetrics(ctx) + +aiCredits := float64(0) +if metrics.TotalNanoAiu != nil { + aiCredits = *metrics.TotalNanoAiu / 1e9 +} +fmt.Printf("AI credits used: %.6f\n", aiCredits) +fmt.Printf("Premium requests: %v\n", metrics.TotalPremiumRequestCost) + +for model, m := range metrics.ModelMetrics { + nanoAiu := float64(0) + if m.TotalNanoAiu != nil { + nanoAiu = *m.TotalNanoAiu + } + fmt.Printf("%s: in=%d out=%d nanoAiu=%v\n", model, m.Usage.InputTokens, m.Usage.OutputTokens, nanoAiu) +} +``` + +
+ +
+.NET + + +```csharp +#pragma warning disable GHCP001 +using GitHub.Copilot; + +await using var client = new CopilotClient(); +await using var session = await client.CreateSessionAsync(new SessionConfig()); + +var metrics = await session.Rpc.Usage.GetMetricsAsync(); + +var aiCredits = (metrics.TotalNanoAiu ?? 0) / 1e9; +Console.WriteLine($"AI credits used: {aiCredits:F6}"); +Console.WriteLine($"Premium requests: {metrics.TotalPremiumRequestCost}"); + +foreach (var (model, m) in metrics.ModelMetrics) +{ + Console.WriteLine( + $"{model}: in={m.Usage.InputTokens} out={m.Usage.OutputTokens} nanoAiu={m.TotalNanoAiu ?? 0}"); +} +#pragma warning restore GHCP001 +``` + + +```csharp +var metrics = await session.Rpc.Usage.GetMetricsAsync(); + +var aiCredits = (metrics.TotalNanoAiu ?? 0) / 1e9; +Console.WriteLine($"AI credits used: {aiCredits:F6}"); +Console.WriteLine($"Premium requests: {metrics.TotalPremiumRequestCost}"); + +foreach (var (model, m) in metrics.ModelMetrics) +{ + Console.WriteLine( + $"{model}: in={m.Usage.InputTokens} out={m.Usage.OutputTokens} nanoAiu={m.TotalNanoAiu ?? 0}"); +} +``` + +
+ +
+Java + + +```java +var metrics = session.getRpc().usage.getMetrics().join(); + +double aiCredits = metrics.totalNanoAiu() != null ? metrics.totalNanoAiu() / 1e9 : 0; +System.out.printf("AI credits used: %.6f%n", aiCredits); +System.out.printf("Premium requests: %s%n", metrics.totalPremiumRequestCost()); + +metrics.modelMetrics().forEach((model, m) -> { + double nanoAiu = m.totalNanoAiu() != null ? m.totalNanoAiu() : 0; + System.out.printf("%s: in=%d out=%d nanoAiu=%s%n", + model, m.usage().inputTokens(), m.usage().outputTokens(), nanoAiu); +}); +``` + +
+ +
+Rust + +```rust +let metrics = session.rpc().usage().get_metrics().await?; + +let ai_credits = metrics.total_nano_aiu.unwrap_or(0.0) / 1e9; +println!("AI credits used: {ai_credits:.6}"); +println!("Premium requests: {}", metrics.total_premium_request_cost); + +for (model, m) in &metrics.model_metrics { + let nano_aiu = m.total_nano_aiu.unwrap_or(0.0); + println!( + "{model}: in={} out={} nanoAiu={nano_aiu}", + m.usage.input_tokens, m.usage.output_tokens, + ); +} +``` + +
+ +## Per-model AI credit pricing + +To estimate cost before you run a turn, read each model's token prices from `models.list`. This is a server-scoped call on the client, so it does not need a session. Prices are expressed in AI credits per billing batch of tokens. The generated `ModelBillingTokenPrices` type lists every field, including `cachePrice`. + +| Field | Type | Description | +|---|---|---| +| `billing.multiplier` | `number` | Premium request cost multiplier relative to the base rate | +| `billing.tokenPrices.inputPrice` | `number` | AI credit cost per batch of input tokens | +| `billing.tokenPrices.outputPrice` | `number` | AI credit cost per batch of output tokens | +| `billing.tokenPrices.batchSize` | `number` | Number of tokens per billing batch | + +> [!NOTE] +> Price values change as plans and models evolve. Read them at runtime as shown below; never hard-code the numbers into your application. + +
+Node.js / TypeScript + + +```typescript +import { CopilotClient } from "@github/copilot-sdk"; + +const client = new CopilotClient(); + +const { models } = await client.rpc.models.list({}); + +for (const model of models) { + const prices = model.billing?.tokenPrices; + if (!prices) continue; + console.log( + `${model.id}: input=${prices.inputPrice} output=${prices.outputPrice} ` + + `per ${prices.batchSize} tokens (x${model.billing?.multiplier ?? 1})`, + ); +} +``` + + +```typescript +const { models } = await client.rpc.models.list({}); + +for (const model of models) { + const prices = model.billing?.tokenPrices; + if (!prices) continue; + console.log( + `${model.id}: input=${prices.inputPrice} output=${prices.outputPrice} ` + + `per ${prices.batchSize} tokens (x${model.billing?.multiplier ?? 1})`, + ); +} +``` + +
+ +
+Python + + +```python +from copilot import CopilotClient +from copilot.rpc import ModelsListRequest + +client = CopilotClient() + +result = await client.rpc.models.list(ModelsListRequest()) + +for model in result.models: + prices = model.billing.token_prices if model.billing else None + if prices is None: + continue + multiplier = model.billing.multiplier if model.billing else 1 + print( + f"{model.id}: input={prices.input_price} output={prices.output_price} " + f"per {prices.batch_size} tokens (x{multiplier})" + ) +``` + + +```python +result = await client.rpc.models.list(ModelsListRequest()) + +for model in result.models: + prices = model.billing.token_prices if model.billing else None + if prices is None: + continue + multiplier = model.billing.multiplier if model.billing else 1 + print( + f"{model.id}: input={prices.input_price} output={prices.output_price} " + f"per {prices.batch_size} tokens (x{multiplier})" + ) +``` + +
+ +
+Go + + +```go +package main + +import ( + "context" + "fmt" + + copilot "github.com/github/copilot-sdk/go" + "github.com/github/copilot-sdk/go/rpc" +) + +func main() { + ctx := context.Background() + client := copilot.NewClient(nil) + client.Start(ctx) + + list, _ := client.RPC.Models.List(ctx, &rpc.ModelsListRequest{}) + + for _, model := range list.Models { + if model.Billing == nil || model.Billing.TokenPrices == nil { + continue + } + prices := model.Billing.TokenPrices + multiplier := 1.0 + if model.Billing.Multiplier != nil { + multiplier = *model.Billing.Multiplier + } + in, out := 0.0, 0.0 + if prices.InputPrice != nil { + in = *prices.InputPrice + } + if prices.OutputPrice != nil { + out = *prices.OutputPrice + } + batch := int64(0) + if prices.BatchSize != nil { + batch = *prices.BatchSize + } + fmt.Printf("%s: input=%v output=%v per %d tokens (x%v)\n", model.ID, in, out, batch, multiplier) + } +} +``` + + +```go +list, _ := client.RPC.Models.List(ctx, &rpc.ModelsListRequest{}) + +for _, model := range list.Models { + if model.Billing == nil || model.Billing.TokenPrices == nil { + continue + } + prices := model.Billing.TokenPrices + multiplier := 1.0 + if model.Billing.Multiplier != nil { + multiplier = *model.Billing.Multiplier + } + in, out := 0.0, 0.0 + if prices.InputPrice != nil { + in = *prices.InputPrice + } + if prices.OutputPrice != nil { + out = *prices.OutputPrice + } + batch := int64(0) + if prices.BatchSize != nil { + batch = *prices.BatchSize + } + fmt.Printf("%s: input=%v output=%v per %d tokens (x%v)\n", model.ID, in, out, batch, multiplier) +} +``` + +
+ +
+.NET + + +```csharp +using GitHub.Copilot; + +await using var client = new CopilotClient(); + +var list = await client.Rpc.Models.ListAsync(); + +foreach (var model in list.Models) +{ + var prices = model.Billing?.TokenPrices; + if (prices is null) continue; + Console.WriteLine( + $"{model.Id}: input={prices.InputPrice} output={prices.OutputPrice} " + + $"per {prices.BatchSize} tokens (x{model.Billing?.Multiplier ?? 1})"); +} +``` + + +```csharp +var list = await client.Rpc.Models.ListAsync(); + +foreach (var model in list.Models) +{ + var prices = model.Billing?.TokenPrices; + if (prices is null) continue; + Console.WriteLine( + $"{model.Id}: input={prices.InputPrice} output={prices.OutputPrice} " + + $"per {prices.BatchSize} tokens (x{model.Billing?.Multiplier ?? 1})"); +} +``` + +
+ +
+Java + + +```java +var list = client.getRpc().models.list().join(); + +for (var model : list.models()) { + var billing = model.billing(); + if (billing == null || billing.tokenPrices() == null) { + continue; + } + var prices = billing.tokenPrices(); + double multiplier = billing.multiplier() != null ? billing.multiplier() : 1; + System.out.printf("%s: input=%s output=%s per %d tokens (x%s)%n", + model.id(), prices.inputPrice(), prices.outputPrice(), prices.batchSize(), multiplier); +} +``` + +
+ +
+Rust + +```rust +let list = client.rpc().models().list().await?; + +for model in &list.models { + let Some(billing) = &model.billing else { continue }; + let Some(prices) = &billing.token_prices else { continue }; + let multiplier = billing.multiplier.unwrap_or(1.0); + println!( + "{}: input={} output={} per {} tokens (x{multiplier})", + model.id, + prices.input_price.unwrap_or(0.0), + prices.output_price.unwrap_or(0.0), + prices.batch_size.unwrap_or(0), + ); +} +``` + +
+ +## Account quota and premium interactions + +`account.getQuota` reports the authenticated user's remaining Copilot entitlement. The result's `quotaSnapshots` map is keyed by quota type—commonly `premium_interactions`, `chat`, and `completions`. Use it to show users how much of their monthly allowance is left, or to gate work before they hit a limit. + +The example uses the fields below; the generated `AccountQuotaSnapshot` type is the full reference. The `quotaSnapshots` keys are runtime strings that the SDK type system does not validate, so guard your lookups. + +| Field | Type | Description | +|---|---|---| +| `entitlementRequests` | `number` | Requests included in the entitlement, or `-1` for unlimited | +| `usedRequests` | `number` | Requests used so far this period | +| `remainingPercentage` | `number` | Percentage of the entitlement remaining | +| `resetDate` | `string` | ISO 8601 date when the quota resets | + +> [!TIP] +> To read quota for a specific user rather than the connection's global auth context (for example, in a multi-tenant backend), pass that user's GitHub token to `getQuota`. See [Multi-tenancy](../setup/multi-tenancy.md). + +
+Node.js / TypeScript + + +```typescript +import { CopilotClient } from "@github/copilot-sdk"; + +const client = new CopilotClient(); + +const { quotaSnapshots } = await client.rpc.account.getQuota({}); +const premium = quotaSnapshots["premium_interactions"]; + +if (premium) { + console.log( + `Premium interactions: ${premium.usedRequests}/${premium.entitlementRequests} ` + + `(${premium.remainingPercentage.toFixed(1)}% left, resets ${premium.resetDate ?? "n/a"})`, + ); +} +``` + + +```typescript +const { quotaSnapshots } = await client.rpc.account.getQuota({}); +const premium = quotaSnapshots["premium_interactions"]; + +if (premium) { + console.log( + `Premium interactions: ${premium.usedRequests}/${premium.entitlementRequests} ` + + `(${premium.remainingPercentage.toFixed(1)}% left, resets ${premium.resetDate ?? "n/a"})`, + ); +} +``` + +
+ +
+Python + + +```python +from copilot import CopilotClient +from copilot.rpc import AccountGetQuotaRequest + +client = CopilotClient() + +result = await client.rpc.account.get_quota(AccountGetQuotaRequest()) +premium = result.quota_snapshots.get("premium_interactions") + +if premium is not None: + print( + f"Premium interactions: {premium.used_requests}/{premium.entitlement_requests} " + f"({premium.remaining_percentage:.1f}% left, resets {premium.reset_date or 'n/a'})" + ) +``` + + +```python +result = await client.rpc.account.get_quota(AccountGetQuotaRequest()) +premium = result.quota_snapshots.get("premium_interactions") + +if premium is not None: + print( + f"Premium interactions: {premium.used_requests}/{premium.entitlement_requests} " + f"({premium.remaining_percentage:.1f}% left, resets {premium.reset_date or 'n/a'})" + ) +``` + +
+ +
+Go + + +```go +package main + +import ( + "context" + "fmt" + "time" + + copilot "github.com/github/copilot-sdk/go" + "github.com/github/copilot-sdk/go/rpc" +) + +func main() { + ctx := context.Background() + client := copilot.NewClient(nil) + client.Start(ctx) + + result, _ := client.RPC.Account.GetQuota(ctx, &rpc.AccountGetQuotaRequest{}) + + if premium, ok := result.QuotaSnapshots["premium_interactions"]; ok { + resets := "n/a" + if premium.ResetDate != nil { + resets = premium.ResetDate.Format(time.RFC3339) + } + fmt.Printf("Premium interactions: %d/%d (%.1f%% left, resets %s)\n", + premium.UsedRequests, premium.EntitlementRequests, premium.RemainingPercentage, resets) + } +} +``` + + +```go +result, _ := client.RPC.Account.GetQuota(ctx, &rpc.AccountGetQuotaRequest{}) + +if premium, ok := result.QuotaSnapshots["premium_interactions"]; ok { + resets := "n/a" + if premium.ResetDate != nil { + resets = premium.ResetDate.Format(time.RFC3339) + } + fmt.Printf("Premium interactions: %d/%d (%.1f%% left, resets %s)\n", + premium.UsedRequests, premium.EntitlementRequests, premium.RemainingPercentage, resets) +} +``` + +
+ +
+.NET + + +```csharp +using GitHub.Copilot; + +await using var client = new CopilotClient(); + +var result = await client.Rpc.Account.GetQuotaAsync(); + +if (result.QuotaSnapshots.TryGetValue("premium_interactions", out var premium)) +{ + Console.WriteLine( + $"Premium interactions: {premium.UsedRequests}/{premium.EntitlementRequests} " + + $"({premium.RemainingPercentage:F1}% left, resets {premium.ResetDate?.ToString("o") ?? "n/a"})"); +} +``` + + +```csharp +var result = await client.Rpc.Account.GetQuotaAsync(); + +if (result.QuotaSnapshots.TryGetValue("premium_interactions", out var premium)) +{ + Console.WriteLine( + $"Premium interactions: {premium.UsedRequests}/{premium.EntitlementRequests} " + + $"({premium.RemainingPercentage:F1}% left, resets {premium.ResetDate?.ToString("o") ?? "n/a"})"); +} +``` + +
+ +
+Java + + +```java +var result = client.getRpc().account.getQuota().join(); +var premium = result.quotaSnapshots().get("premium_interactions"); + +if (premium != null) { + System.out.printf("Premium interactions: %d/%d (%.1f%% left, resets %s)%n", + premium.usedRequests(), premium.entitlementRequests(), + premium.remainingPercentage(), premium.resetDate()); +} +``` + +
+ +
+Rust + +```rust +let result = client.rpc().account().get_quota().await?; + +if let Some(premium) = result.quota_snapshots.get("premium_interactions") { + let resets = premium.reset_date.as_deref().unwrap_or("n/a"); + println!( + "Premium interactions: {}/{} ({:.1}% left, resets {resets})", + premium.used_requests, premium.entitlement_requests, premium.remaining_percentage, + ); +} +``` + +
+ +## Choosing the right API + +Use this summary to decide which API fits your use case: + +* **Render a live cost or token meter as a turn runs**: subscribe to `assistant.usage` and `session.usage_info`. +* **Show a final cost summary after a turn or session**: call `session.usage.getMetrics`. +* **Display context-window usage on resume, before any new turn**: call `session.metadata.contextInfo`. +* **Estimate cost before running work**: read `models.list` token prices. +* **Warn users before they exhaust their plan**: call `account.getQuota`. + +## Further reading + +* [Streaming events](./streaming-events.md): full field-level reference for `assistant.usage`, `session.usage_info`, and every other session event +* [Observability](../observability/README.md): export usage data to OpenTelemetry for cost attribution +* [Multi-tenancy](../setup/multi-tenancy.md): resolve per-user quota and models with a GitHub token diff --git a/docs/getting-started.md b/docs/getting-started.md index b14fb73e5..53b6497fd 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -2053,6 +2053,7 @@ let mut options = ClientOptions::default(); options.transport = Transport::External { host: "localhost".to_string(), port: 4321, + connection_token: None, }; let client = Client::start(options).await?; @@ -2158,7 +2159,7 @@ Install with telemetry extras: `pip install copilot-sdk[telemetry]` (provides `o ```go -client, err := copilot.NewClient(copilot.ClientOptions{ +client := copilot.NewClient(&copilot.ClientOptions{ Telemetry: &copilot.TelemetryConfig{ OTLPEndpoint: "http://localhost:4318", }, @@ -2263,7 +2264,7 @@ Trace context is propagated automatically—no manual instrumentation is needed: ## Learn more * [Authentication Guide](./auth/authenticate.md) - GitHub OAuth, environment variables, and BYOK -* [BYOK (Bring Your Own Key)](./auth/byok.md) - Use your own API keys from Azure AI Foundry, OpenAI, etc. +* [BYOK (Bring Your Own Key)](./auth/byok.md) - Use your own API keys from Microsoft Foundry, OpenAI, etc. * [Node.js SDK Reference](../nodejs/README.md) * [Python SDK Reference](../python/README.md) * [Go SDK Reference](../go/README.md) diff --git a/docs/hooks/README.md b/docs/hooks/README.md index 517be9614..a6c7e1aa6 100644 --- a/docs/hooks/README.md +++ b/docs/hooks/README.md @@ -6,5 +6,6 @@ Detailed API reference for each session hook in the GitHub Copilot SDK. * [Pre-tool use](./pre-tool-use.md): approve, deny, or modify tool calls * [Post-tool use](./post-tool-use.md): transform tool results * [User prompt submitted](./user-prompt-submitted.md): modify or filter user messages +* [User prompt transformed](./user-prompt-transformed.md): inspect or replace model-facing prompts * [Session lifecycle](./session-lifecycle.md): session start and end * [Error handling](./error-handling.md): custom error handling diff --git a/docs/hooks/hooks-overview.md b/docs/hooks/hooks-overview.md index ad9a2eb52..8d5583e99 100644 --- a/docs/hooks/hooks-overview.md +++ b/docs/hooks/hooks-overview.md @@ -16,9 +16,11 @@ Hooks allow you to intercept and customize the behavior of Copilot sessions at k | [`onPostToolUse`](./post-tool-use.md) | After a tool executes (success only) | Result transformation, logging | | [`onPostToolUseFailure`](./post-tool-use.md#failure-variant) | After a tool execution whose result was a failure | Inject retry guidance, log failures | | [`onUserPromptSubmitted`](./user-prompt-submitted.md) | When user sends a message | Prompt modification, filtering | +| [`onUserPromptTransformed`](./user-prompt-transformed.md) | After runtime prompt transformation | Inspect or replace model-facing content | | [`onSessionStart`](./session-lifecycle.md#session-start) | Session begins | Add context, configure session | | [`onSessionEnd`](./session-lifecycle.md#session-end) | Session ends | Cleanup, analytics | | [`onErrorOccurred`](./error-handling.md) | Error happens | Custom error handling | +| [`onAgentStop`](./session-lifecycle.md#agent-stop) | Top-level agent naturally stops | Validate completion or request another turn | ## Quick start @@ -262,7 +264,9 @@ const session = await client.createSession({ * **[Pre-Tool Use Hook](./pre-tool-use.md)** - Control tool execution permissions * **[Post-Tool Use Hook](./post-tool-use.md)** - Transform tool results * **[User Prompt Submitted Hook](./user-prompt-submitted.md)** - Modify user prompts +* **[User Prompt Transformed Hook](./user-prompt-transformed.md)** - Replace model-facing prompts * **[Session Lifecycle Hooks](./session-lifecycle.md)** - Session start and end +* **[Agent Stop Hook](./session-lifecycle.md#agent-stop)** - Validate completion before the agent stops * **[Error Handling Hook](./error-handling.md)** - Custom error handling ## See also diff --git a/docs/hooks/session-lifecycle.md b/docs/hooks/session-lifecycle.md index 21c9bdcf6..485752601 100644 --- a/docs/hooks/session-lifecycle.md +++ b/docs/hooks/session-lifecycle.md @@ -540,6 +540,42 @@ Session Summary: }); ``` +## Agent stop hook {#agent-stop} + +The agent stop hook runs when the top-level agent naturally reaches the end of a turn. It is separate from `onSessionEnd`: the session remains active, and the hook can request another agent turn. + +| Language | Handler | +|----------|---------| +| Node.js / TypeScript | `onAgentStop` | +| Python | `on_agent_stop` | +| Go | `OnAgentStop` | +| .NET | `OnAgentStop` | +| Rust | `on_agent_stop` | +| Java | `setOnAgentStop` | + +### Input + +The public member names follow each language's casing conventions: + +| Meaning | Node.js / Python | Go / .NET | Rust | Java | +|---------|------------------|-----------|------|------| +| Why the agent stopped, such as `end_turn` | `stopReason` | `StopReason` | `stop_reason` | `getStopReason()` | +| Path to the on-disk session transcript | `transcriptPath` | `TranscriptPath` | `transcript_path` | `getTranscriptPath()` | +| Whether an earlier block decision already forced this continuation | `stopHookActive` | `StopHookActive` | `stop_hook_active` | `getStopHookActive()` | + +### Output + +Return no output to let the agent stop. Return a block decision to enqueue another user message and continue: + +```json +{ + "decision": "block", + "reason": "Run the final validation and fix any failures." +} +``` + +Use the active-stop member listed above to avoid repeatedly blocking an agent that has already continued because of this hook. The runtime also caps consecutive block decisions. + ## Best practices 1. **Keep `onSessionStart` fast** - Users are waiting for the session to be ready. diff --git a/docs/hooks/user-prompt-submitted.md b/docs/hooks/user-prompt-submitted.md index 49930ba4a..230afca96 100644 --- a/docs/hooks/user-prompt-submitted.md +++ b/docs/hooks/user-prompt-submitted.md @@ -415,11 +415,11 @@ const session = await client.createSession({ }); ``` -### Rate limiting +### Usage threshold notices ```typescript const promptTimestamps: number[] = []; -const RATE_LIMIT = 10; // prompts +const NOTICE_THRESHOLD = 10; // prompts const RATE_WINDOW = 60000; // 1 minute const session = await client.createSession({ @@ -431,15 +431,16 @@ const session = await client.createSession({ while (promptTimestamps.length > 0 && promptTimestamps[0] < now - RATE_WINDOW) { promptTimestamps.shift(); } - - if (promptTimestamps.length >= RATE_LIMIT) { + + promptTimestamps.push(now); + if (promptTimestamps.length >= NOTICE_THRESHOLD) { + // This is advisory context for the model, not an enforced rate limit. + // Enforce hard limits before calling session.send(). return { - reject: true, - rejectReason: `Rate limit exceeded. Please wait before sending more prompts.`, + additionalContext: `The user has sent ${promptTimestamps.length} prompts in the last minute. Suggest waiting before sending more.`, }; } - - promptTimestamps.push(now); + return null; }, }, @@ -490,7 +491,7 @@ const session = await client.createSession({ 1. **Use `additionalContext` over `modifiedPrompt`** - Adding context is less intrusive than rewriting the prompt. -1. **Provide clear rejection reasons** - When rejecting prompts, explain why and how to fix it. +1. **Use `additionalContext` for advisory guidance**: This hook cannot reject a prompt or enforce policy. Enforce hard limits before calling `session.send()`. 1. **Keep processing fast** - This hook runs on every user message. Avoid slow operations. diff --git a/docs/hooks/user-prompt-transformed.md b/docs/hooks/user-prompt-transformed.md new file mode 100644 index 000000000..f7791d78b --- /dev/null +++ b/docs/hooks/user-prompt-transformed.md @@ -0,0 +1,129 @@ +# User prompt transformed hook + +The `userPromptTransformed` hook runs after the runtime adds generated context to a submitted prompt, but before the resulting content is persisted to session history or sent to the model. + +Use it when you need to inspect or replace the exact model-facing prompt. The `prompt` input contains the user prompt after any `userPromptSubmitted` hooks have run, while `transformedPrompt` also contains runtime-generated context such as ``. + +## Input and output + +| Input field | Type | Description | +| --- | --- | --- | +| `sessionId` | string | Runtime session ID | +| `timestamp` | date/time | Time the hook was invoked | +| `cwd` / `workingDirectory` | string | Current working directory | +| `prompt` | string | Prompt after `userPromptSubmitted` hooks | +| `transformedPrompt` | string | Model-facing prompt after runtime transformations | + +Return no value to leave the transformed prompt unchanged. Return `modifiedTransformedPrompt` to replace the content that is stored in session history and sent to the model. + +## Examples + +
+TypeScript + + +```typescript +const session = await client.createSession({ + hooks: { + onUserPromptTransformed: async (input) => ({ + modifiedTransformedPrompt: redact(input.transformedPrompt), + }), + }, +}); +``` + +
+ +
+Python + + +```python +session = await client.create_session( + hooks={ + "on_user_prompt_transformed": lambda input_data, invocation: { + "modifiedTransformedPrompt": redact(input_data["transformedPrompt"]) + } + } +) +``` + +
+ +
+Go + + +```go +session, err := client.CreateSession(ctx, &copilot.SessionConfig{ + Hooks: &copilot.SessionHooks{ + OnUserPromptTransformed: func(input copilot.UserPromptTransformedHookInput, invocation copilot.HookInvocation) (*copilot.UserPromptTransformedHookOutput, error) { + return &copilot.UserPromptTransformedHookOutput{ + ModifiedTransformedPrompt: copilot.String(redact(input.TransformedPrompt)), + }, nil + }, + }, +}) +``` + +
+ +
+.NET + + +```csharp +var session = await client.CreateSessionAsync(new SessionConfig +{ + Hooks = new SessionHooks + { + OnUserPromptTransformed = (input, invocation) => + Task.FromResult(new() + { + ModifiedTransformedPrompt = Redact(input.TransformedPrompt), + }), + }, +}); +``` + +
+ +
+Java + + +```java +var hooks = new SessionHooks().setOnUserPromptTransformed((input, invocation) -> + CompletableFuture.completedFuture( + new UserPromptTransformedHookOutput(redact(input.transformedPrompt())))); + +var session = client.createSession(new SessionConfig().setHooks(hooks)).get(); +``` + +
+ +
+Rust + +```rust +#[async_trait] +impl SessionHooks for MyHooks { + async fn on_user_prompt_transformed( + &self, + input: UserPromptTransformedInput, + _ctx: HookContext, + ) -> Option { + Some(UserPromptTransformedOutput { + modified_transformed_prompt: Some(redact(&input.transformed_prompt)), + }) + } +} + +let session = client + .create_session(SessionConfig::default().with_hooks(Arc::new(MyHooks))) + .await?; +``` + +
+ +The replacement is persisted as the user message content, so resumed sessions replay the modified content unchanged. diff --git a/docs/integrations/microsoft-agent-framework.md b/docs/integrations/microsoft-agent-framework.md index 663a20a79..5543e6aef 100644 --- a/docs/integrations/microsoft-agent-framework.md +++ b/docs/integrations/microsoft-agent-framework.md @@ -206,13 +206,19 @@ You can also use Copilot SDK's native tool definition alongside MAF tools: Node.js / TypeScript (standalone SDK) ```typescript -import { CopilotClient, DefineTool } from "@github/copilot-sdk"; +import { CopilotClient, defineTool } from "@github/copilot-sdk"; -const getWeather = DefineTool({ - name: "GetWeather", +const getWeather = defineTool("GetWeather", { description: "Get the current weather for a given location.", - parameters: { location: { type: "string", description: "City name" } }, - execute: async ({ location }) => `The weather in ${location} is sunny, 25°C.`, + parameters: { + type: "object", + properties: { + location: { type: "string", description: "City name" }, + }, + required: ["location"], + }, + handler: async ({ location }: { location: string }) => + `The weather in ${location} is sunny, 25°C.`, }); const client = new CopilotClient(); @@ -524,7 +530,7 @@ const session = await client.createSession({ }); session.on("assistant.message_delta", (event) => { - process.stdout.write(event.data.delta ?? ""); + process.stdout.write(event.data.deltaContent ?? ""); }); await session.sendAndWait({ prompt: "Write a quicksort implementation in TypeScript" }); diff --git a/docs/observability/opentelemetry.md b/docs/observability/opentelemetry.md index 022a41c79..b3932ce66 100644 --- a/docs/observability/opentelemetry.md +++ b/docs/observability/opentelemetry.md @@ -43,7 +43,7 @@ client = CopilotClient( ```go -client, err := copilot.NewClient(copilot.ClientOptions{ +client := copilot.NewClient(&copilot.ClientOptions{ Telemetry: &copilot.TelemetryConfig{ OTLPEndpoint: "http://localhost:4318", }, @@ -152,29 +152,36 @@ When the CLI invokes a tool handler, the `traceparent` and `tracestate` from the ```typescript +import { defineTool } from "@github/copilot-sdk"; import { propagation, context, trace } from "@opentelemetry/api"; -session.registerTool(myTool, async (args, invocation) => { - // Restore the CLI's trace context as the active context - const carrier = { - traceparent: invocation.traceparent, - tracestate: invocation.tracestate, - }; - const parentCtx = propagation.extract(context.active(), carrier); - - // Create a child span under the CLI's span - const tracer = trace.getTracer("my-app"); - return context.with(parentCtx, () => - tracer.startActiveSpan("my-tool", async (span) => { - try { - const result = await doWork(args); - return result; - } finally { - span.end(); - } - }) - ); +const myTool = defineTool("my-tool", { + description: "Do work", + handler: async (args, invocation) => { + // Restore the CLI's trace context as the active context + const carrier = { + traceparent: invocation.traceparent, + tracestate: invocation.tracestate, + }; + const parentCtx = propagation.extract(context.active(), carrier); + + // Create a child span under the CLI's span + const tracer = trace.getTracer("my-app"); + return context.with(parentCtx, () => + tracer.startActiveSpan("my-tool", async (span) => { + try { + const result = await doWork(args); + return result; + } finally { + span.end(); + } + }) + ); + }, }); + +// Tool handlers are registered when the session is created. +const session = await client.createSession({ tools: [myTool] }); ``` ### Per-language dependencies diff --git a/docs/setup/README.md b/docs/setup/README.md index cc4183ca7..e4723ab48 100644 --- a/docs/setup/README.md +++ b/docs/setup/README.md @@ -8,5 +8,5 @@ Configure and deploy the GitHub Copilot SDK for your use case. * [Backend services](./backend-services.md): server-side with headless CLI over TCP * [Multi-tenancy and server deployments](./multi-tenancy.md): SDK options for multi-user server mode * [GitHub OAuth](./github-oauth.md): implement the OAuth flow -* [Azure managed identity](./azure-managed-identity.md): BYOK with Azure AI Foundry +* [Azure managed identity](./azure-managed-identity.md): BYOK with Microsoft Foundry * [Scaling and multi-tenancy](./scaling.md): horizontal scaling, isolation patterns diff --git a/docs/setup/bundled-cli.md b/docs/setup/bundled-cli.md index 7c7d2fbbc..f067de8fd 100644 --- a/docs/setup/bundled-cli.md +++ b/docs/setup/bundled-cli.md @@ -79,7 +79,7 @@ await client.stop() Go > [!NOTE] -> The Go SDK does not bundle the CLI. You must install the CLI separately or set `Connection` to point to an existing binary. See [Local CLI Setup](./local-cli.md) for details. +> Unlike Node.js, Python, and .NET, the Go SDK does not include a CLI as an automatic dependency. With no explicit path, `NewClient(nil)` uses an embedded CLI when available, then falls back to `copilot` on `PATH`. To embed a CLI, run the [bundler tool](../../go/README.md#distributing-your-application-with-an-embedded-github-copilot-cli) at build time. You can also set `COPILOT_CLI_PATH` or point a `Connection` at an existing binary. See [Local CLI Setup](./local-cli.md) for details. ```go @@ -145,7 +145,7 @@ Console.WriteLine(response?.Data.Content); Java > [!NOTE] -> The Java SDK does not bundle or embed the Copilot CLI. You must install the CLI separately and configure its path via `Connection` or the `COPILOT_CLI_PATH` environment variable. +> The Java SDK does not bundle or embed the Copilot CLI. Install the CLI separately and either make `copilot` available on your `PATH` or set its location with `setCliPath(...)` (or connect to a running CLI server with `setCliUrl(...)`). ```java import com.github.copilot.CopilotClient; diff --git a/docs/setup/local-cli.md b/docs/setup/local-cli.md index 72394b348..79a656396 100644 --- a/docs/setup/local-cli.md +++ b/docs/setup/local-cli.md @@ -2,7 +2,7 @@ Use a specific CLI binary instead of the SDK's automatic CLI management. This is an advanced option—you supply the CLI path explicitly, and you are responsible for ensuring version compatibility with the SDK. -**Use when:** You need to pin a specific CLI version, or work with the Go SDK (which does not bundle a CLI). +**Use when:** You need to pin a specific CLI version, or work with the Go SDK (which does not include a CLI automatically). ## How it works @@ -78,7 +78,7 @@ await client.stop() Go > [!NOTE] -> The Go SDK does not bundle a CLI, so you must always provide `Connection`. +> The Go SDK does not ship a CLI automatically. Install `copilot` on `PATH`, set the `COPILOT_CLI_PATH` environment variable, embed a CLI with the [bundler tool](../../go/README.md#distributing-your-application-with-an-embedded-github-copilot-cli), or point `StdioConnection.Path` at an installed binary. ```go diff --git a/docs/troubleshooting/compatibility.md b/docs/troubleshooting/compatibility.md index c68d59cc7..da8bf0daa 100644 --- a/docs/troubleshooting/compatibility.md +++ b/docs/troubleshooting/compatibility.md @@ -77,17 +77,19 @@ The Copilot SDK communicates with the CLI via JSON-RPC protocol. Features must b | System message | `systemMessage` config | Append or replace | | Custom provider | `provider` config | BYOK support | | Infinite sessions | `infiniteSessions` config | Auto-compaction | -| Permission handler | `onPermissionRequest` | Approve/deny requests | +| Permission handler | `onPermissionRequest` | Approve/deny requests; optionally attach a `decisionContext` for auto-approval telemetry | | User input handler | `onUserInputRequest` | Handle ask_user | | Skills | `skillDirectories` config | Custom skills | | Disabled skills | `disabledSkills` config | Disable specific skills | | Config directory | `configDir` config | Override default config location | | Client name | `clientName` config | Identify app in User-Agent | | Working directory | `workingDirectory` config | Set session cwd | +| Additional directories | `additionalDirectories` config | Grant session access beyond the working directory; re-supply on resume | | **Experimental** | | | | Agent management | `session.rpc.agent.*` | List, select, deselect, get current agent | | Fleet mode | `session.rpc.fleet.start()` | Parallel sub-agent execution; see [Fleet mode](../features/fleet-mode.md) | | Manual compaction | `session.rpc.history.compact()` | Trigger compaction on demand | +| Context clearing | `session.rpc.history.clearContext()` | Replace conversation context from a terminal tool | | History truncation | `session.rpc.history.truncate()` | Remove events from a point onward | | Session forking | `server.rpc.sessions.fork()` | Fork a session at a point in history | diff --git a/dotnet/README.md b/dotnet/README.md index 796ef2254..6efd6e094 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -37,7 +37,7 @@ using GitHub.Copilot; await using var client = new CopilotClient(); await client.StartAsync(); -// Create a session (OnPermissionRequest is optional; ApproveAll allows every tool) +// ApproveAll is only valid when managed settings are disabled. await using var session = await client.CreateSessionAsync(new SessionConfig { Model = "gpt-5", @@ -64,6 +64,12 @@ await session.SendAsync(new MessageOptions { Prompt = "What is 2+2?" }); await done.Task; ``` +When targeting MCP tools configured through `McpServers`, remember the runtime +tool name is `-`. For `AvailableTools` and +`ExcludedTools`, prefer the source-qualified form +`mcp:-`. For `CustomAgents[].Tools` and +`DefaultAgent.ExcludedTools`, use `-` directly. + ## API Reference ### CopilotClient @@ -78,7 +84,7 @@ new CopilotClient(CopilotClientOptions? options = null) - `Connection` - How to connect to the Copilot runtime. Defaults to `null` (equivalent to `RuntimeConnection.ForStdio()` with the bundled runtime). See "RuntimeConnection" below. - `LogLevel` - Runtime log level. Accepts well-known values `CopilotLogLevel.None`, `Error`, `Warning`, `Info`, `Debug`, `All`. Defaults to null (the runtime's own default). -- `WorkingDirectory` - Working directory for the runtime process. +- `WorkingDirectory` - Working directory for the runtime process. When not set, the spawned runtime inherits the calling application's current working directory. - `BaseDirectory` - Base directory for Copilot data (session state, config, etc.). Sets `COPILOT_HOME` on the spawned runtime process. When not set, the runtime defaults to `~/.copilot`. Useful in restricted environments where only specific directories are writable. Ignored when connecting via `RuntimeConnection.ForUri(...)`. - `EnableRemoteSessions` - Enables remote-session features. - `Environment` - Environment variables to pass to the runtime process. @@ -117,7 +123,7 @@ Create a new conversation session. - `SessionId` - Custom session ID - `Model` - Model to use ("gpt-5", "claude-sonnet-4.5", etc.) -- `ReasoningEffort` - Reasoning effort level for models that support it ("low", "medium", "high", "xhigh"). Use `ListModelsAsync()` to check which models support this option. +- `ReasoningEffort` - Reasoning effort level for models that support it ("low", "medium", "high", "xhigh", "max"). Use `ListModelsAsync()` to check which models support this option. - `Tools` - Custom tool declarations exposed to the CLI. Declarations without an invocable `AIFunction` are left pending for manual resolution. - `SystemMessage` - System message customization - `AvailableTools` - List of tool names to allow @@ -125,7 +131,9 @@ Create a new conversation session. - `Provider` - Custom API provider configuration (BYOK) - `Streaming` - Enable streaming of response chunks (default: false) - `InfiniteSessions` - Configure automatic context compaction (see below) -- `OnPermissionRequest` - Optional handler called before each tool execution to approve or deny it. When omitted, permission requests are emitted as events and left pending for manual resolution. Use `PermissionHandler.ApproveAll` to allow everything, or provide a custom function for fine-grained control. See [Permission Handling](#permission-handling) section. +- `WorkingDirectory` - Working directory for the session. When not set, the runtime uses its own process working directory. +- `EnableSessionStore` - Enables the cross-session store for search and retrieval across sessions. When unset in `CopilotClientMode.CopilotCli`, the runtime default applies (enabled). In `CopilotClientMode.Empty`, defaults to disabled. +- `OnPermissionRequest` - Optional handler called before each tool execution to approve or deny it. When omitted, permission requests are emitted as events and left pending for manual resolution. `PermissionHandler.ApproveAll` approves requests when managed settings are disabled and throws when `EnableManagedSettings` is true. Custom handlers can inspect `ManagedApprovalRequired` for human-facing confirmation logic. See [Permission Handling](#permission-handling) section. - `OnUserInputRequest` - Handler for user input requests from the agent (enables ask_user tool). See [User Input Requests](#user-input-requests) section. - `Hooks` - Hook handlers for session lifecycle events. See [Session Hooks](#session-hooks) section. @@ -289,9 +297,9 @@ The SDK supports image attachments via the `Attachments` parameter. You can atta await session.SendAsync(new MessageOptions { Prompt = "What's in this image?", - Attachments = new List + Attachments = new List { - new UserMessageDataAttachmentsItemFile + new AttachmentFile { Path = "/path/to/image.jpg", DisplayName = "image.jpg", @@ -303,9 +311,9 @@ await session.SendAsync(new MessageOptions await session.SendAsync(new MessageOptions { Prompt = "What's in this image?", - Attachments = new List + Attachments = new List { - new UserMessageDataAttachmentsItemBlob + new AttachmentBlob { Data = base64ImageData, MimeType = "image/png", @@ -714,13 +722,12 @@ await session2.SendAsync(new MessageOptions { Prompt = "Hello from session 2" }) await session.SendAsync(new MessageOptions { Prompt = "Analyze this file", - Attachments = new List + Attachments = new List { - new UserMessageDataAttachmentsItem + new AttachmentFile { - Type = UserMessageDataAttachmentsItemType.File, Path = "/path/to/file.cs", - DisplayName = "My File" + DisplayName = "My File", } } }); @@ -775,7 +782,7 @@ An `OnPermissionRequest` handler is optional when you create or resume a session ### Approve All (simplest) -Use the built-in `PermissionHandler.ApproveAll` helper to allow every tool call without any checks: +Use the built-in `PermissionHandler.ApproveAll` helper to approve ordinary permission requests automatically: ```csharp using GitHub.Copilot; @@ -787,9 +794,11 @@ var session = await client.CreateSessionAsync(new SessionConfig }); ``` +When `EnableManagedSettings` is true for the session, `ApproveAll` throws on the first permission request. Use a custom handler for managed sessions; request-level `ManagedApprovalRequired` remains available for human-facing confirmation logic. + ### Custom Permission Handler -Provide your own permission handler (`Func>`) to inspect each request and apply custom logic: +Provide your own permission handler (`Func>`) to inspect each request and apply custom logic. Check `ManagedApprovalRequired` before any automatic approval: ```csharp var session = await client.CreateSessionAsync(new SessionConfig @@ -797,6 +806,11 @@ var session = await client.CreateSessionAsync(new SessionConfig Model = "gpt-5", OnPermissionRequest = async (request, invocation) => { + if (request.ManagedApprovalRequired is true) + { + return PermissionDecision.NoResult(); + } + // Pattern-match on the discriminated PermissionRequest union to access // per-kind fields (FullCommandText, Path, ToolName, …). return request switch @@ -1024,6 +1038,25 @@ catch (Exception ex) } ``` +## Development + +Development requires [.NET SDK 10+](https://dotnet.microsoft.com/download) and a supported [Node.js version](../nodejs/README.md#prerequisites). From the repository root: + +```bash +cd nodejs +npm ci +``` + +```bash +cd test/harness +npm ci +``` + +```bash +cd dotnet +dotnet test +``` + ## License MIT diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs index f75b75659..58c1074c0 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -76,6 +76,7 @@ public sealed partial class CopilotClient : IDisposable, IAsyncDisposable private readonly ILogger _logger; private readonly int? _optionsPort; private readonly string? _optionsHost; + private readonly string[] _builtinPluginDirectories; private readonly Func>>? _onListModels; private readonly List _lifecycleHandlers = []; @@ -138,6 +139,14 @@ public CopilotClient(CopilotClientOptions? options = null) { _options = options ?? new(); _connection = _options.Connection ?? ResolveDefaultConnection(_options); + _builtinPluginDirectories = _options.BuiltinPluginDirectories?.ToArray() ?? []; + foreach (var path in _builtinPluginDirectories.Where(path => !IsFullyQualifiedPath(path))) + { + throw new ArgumentException( + $"{nameof(CopilotClientOptions)}.{nameof(CopilotClientOptions.BuiltinPluginDirectories)} " + + $"must contain only absolute paths: {path}", + nameof(options)); + } switch (_connection) { @@ -317,6 +326,26 @@ private static Uri ParseRuntimeUrl(string url) return new Uri(url); } + private static bool IsFullyQualifiedPath(string path) + { + if (string.IsNullOrEmpty(path) || !Path.IsPathRooted(path)) + { + return false; + } +#if NETSTANDARD2_0 + if (Path.DirectorySeparatorChar != '\\') + { + return true; + } + + bool IsSeparator(char value) => value == '\\' || value == '/'; + return (path.Length >= 3 && path[1] == ':' && IsSeparator(path[2])) + || (path.Length >= 2 && IsSeparator(path[0]) && IsSeparator(path[1])); +#else + return Path.IsPathFullyQualified(path); +#endif + } + /// /// Starts the Copilot client and connects to the server. /// @@ -423,6 +452,13 @@ async Task StartCoreAsync(CancellationToken ct) "CopilotClient.StartAsync protocol verification complete. Elapsed={Elapsed}", startTimestamp); + if (_builtinPluginDirectories.Length > 0) + { + var request = new BuiltinPluginDirectoriesRequest(_builtinPluginDirectories); + await InvokeRpcAsync( + connection.Rpc, "plugins.builtin.set", [request], null, ct); + } + var sessionFsTimestamp = Stopwatch.GetTimestamp(); await ConfigureSessionFsAsync(ct); if (_options.SessionFs is not null) @@ -783,7 +819,9 @@ private CopilotSession InitializeSession( _logger, this); session.RegisterTools(config.Tools ?? []); - session.RegisterPermissionHandler(config.OnPermissionRequest); + session.RegisterPermissionHandler( + config.OnPermissionRequest, + config.EnableManagedSettings is true || config.ManagedSettings is not null); session.RegisterMcpAuthHandler(config.OnMcpAuthRequest); session.RegisterCommands(config.Commands); session.RegisterElicitationHandler(config.OnElicitationRequest); @@ -906,6 +944,7 @@ private void ApplyConfigDefaultsForMode(SessionConfigBase config) { if (_options.Mode == CopilotClientMode.Empty) { + config.EnableExperimentalMode ??= false; config.EnableSessionTelemetry ??= false; config.SkipEmbeddingRetrieval ??= true; config.EmbeddingCacheStorage ??= EmbeddingCacheStorageMode.InMemory; @@ -916,6 +955,7 @@ private void ApplyConfigDefaultsForMode(SessionConfigBase config) config.EnableSkills ??= false; config.Memory ??= new MemoryConfiguration { Enabled = false }; config.McpOAuthTokenStorage ??= McpOAuthTokenStorageMode.InMemory; + config.CustomAgentsLocalOnly ??= true; } } @@ -1093,9 +1133,11 @@ public async Task CreateSessionAsync(SessionConfig config, Cance config.Hooks.OnPostToolUse != null || config.Hooks.OnPostToolUseFailure != null || config.Hooks.OnUserPromptSubmitted != null || + config.Hooks.OnUserPromptTransformed != null || config.Hooks.OnSessionStart != null || config.Hooks.OnSessionEnd != null || - config.Hooks.OnErrorOccurred != null); + config.Hooks.OnErrorOccurred != null || + config.Hooks.OnAgentStop != null); var (wireSystemMessage, transformCallbacks) = ExtractTransformCallbacks(config.SystemMessage); @@ -1135,6 +1177,7 @@ public async Task CreateSessionAsync(SessionConfig config, Cance config.ContextTier, config.Tools?.Select(ToolDefinition.FromAIFunction).ToList(), config.EnableCitations, + config.EnableFileChangeTracking, wireSystemMessage, toolFilter.AvailableTools, toolFilter.ExcludedTools, @@ -1142,6 +1185,7 @@ public async Task CreateSessionAsync(SessionConfig config, Cance config.Provider, config.Capi, config.EnableSessionTelemetry, + config.EnableExperimentalMode, config.OnPermissionRequest != null ? true : null, config.OnUserInputRequest != null ? true : null, config.OnExitPlanModeRequest != null ? true : null, @@ -1158,6 +1202,7 @@ public async Task CreateSessionAsync(SessionConfig config, Cance config.Agent, config.ConfigDirectory, config.EnableConfigDiscovery, + config.CustomAgentsLocalOnly, config.SkipEmbeddingRetrieval, config.EmbeddingCacheStorage, config.OrganizationCustomInstructions, @@ -1181,6 +1226,7 @@ public async Task CreateSessionAsync(SessionConfig config, Cance Cloud: config.Cloud, InstructionDirectories: config.InstructionDirectories, PluginDirectories: config.PluginDirectories, + DisabledMcpServers: config.DisabledMcpServers, LargeOutput: config.LargeOutput, ToolSearch: config.ToolSearch, Memory: config.Memory, @@ -1195,7 +1241,10 @@ public async Task CreateSessionAsync(SessionConfig config, Cance ToolFilterPrecedence: toolFilter.ToolFilterPrecedence, ExpAssignments: config.ExpAssignments, EnableManagedSettings: config.EnableManagedSettings, - EnableGitHubTelemetryForwarding: _options.OnGitHubTelemetry != null ? true : null); + GitHubMcpToolConfig: config.GitHubMcpToolConfig, + ManagedSettings: config.ManagedSettings, + EnableGitHubTelemetryForwarding: _options.OnGitHubTelemetry != null ? true : null, + AdditionalDirectories: config.AdditionalDirectories); var rpcTimestamp = Stopwatch.GetTimestamp(); @@ -1318,9 +1367,11 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes config.Hooks.OnPostToolUse != null || config.Hooks.OnPostToolUseFailure != null || config.Hooks.OnUserPromptSubmitted != null || + config.Hooks.OnUserPromptTransformed != null || config.Hooks.OnSessionStart != null || config.Hooks.OnSessionEnd != null || - config.Hooks.OnErrorOccurred != null); + config.Hooks.OnErrorOccurred != null || + config.Hooks.OnAgentStop != null); var (wireSystemMessage, transformCallbacks) = ExtractTransformCallbacks(config.SystemMessage); @@ -1346,6 +1397,7 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes config.ContextTier, config.Tools?.Select(ToolDefinition.FromAIFunction).ToList(), config.EnableCitations, + config.EnableFileChangeTracking, wireSystemMessage, toolFilter.AvailableTools, toolFilter.ExcludedTools, @@ -1353,6 +1405,7 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes config.Provider, config.Capi, config.EnableSessionTelemetry, + config.EnableExperimentalMode, config.OnPermissionRequest != null ? true : null, config.OnUserInputRequest != null ? true : null, config.OnExitPlanModeRequest != null ? true : null, @@ -1361,6 +1414,7 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes config.WorkingDirectory, config.ConfigDirectory, config.EnableConfigDiscovery, + config.CustomAgentsLocalOnly, config.SkipEmbeddingRetrieval, config.EmbeddingCacheStorage, config.OrganizationCustomInstructions, @@ -1393,6 +1447,7 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes ContinuePendingWork: config.ContinuePendingWork, InstructionDirectories: config.InstructionDirectories, PluginDirectories: config.PluginDirectories, + DisabledMcpServers: config.DisabledMcpServers, LargeOutput: config.LargeOutput, ToolSearch: config.ToolSearch, Memory: config.Memory, @@ -1408,7 +1463,10 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes ToolFilterPrecedence: toolFilter.ToolFilterPrecedence, ExpAssignments: config.ExpAssignments, EnableManagedSettings: config.EnableManagedSettings, - EnableGitHubTelemetryForwarding: _options.OnGitHubTelemetry != null ? true : null); + GitHubMcpToolConfig: config.GitHubMcpToolConfig, + ManagedSettings: config.ManagedSettings, + EnableGitHubTelemetryForwarding: _options.OnGitHubTelemetry != null ? true : null, + AdditionalDirectories: config.AdditionalDirectories); var rpcTimestamp = Stopwatch.GetTimestamp(); var response = await InvokeRpcAsync( @@ -2699,6 +2757,7 @@ internal record CreateSessionRequest( ContextTier? ContextTier, IList? Tools, bool? EnableCitations, + bool? EnableFileChangeTracking, SystemMessageConfig? SystemMessage, IList? AvailableTools, IList? ExcludedTools, @@ -2706,6 +2765,7 @@ internal record CreateSessionRequest( ProviderConfig? Provider, CapiSessionOptions? Capi, bool? EnableSessionTelemetry, + bool? IsExperimentalMode, bool? RequestPermission, bool? RequestUserInput, bool? RequestExitPlanMode, @@ -2722,6 +2782,7 @@ internal record CreateSessionRequest( string? Agent, [property: JsonPropertyName("configDir")] string? ConfigDirectory, bool? EnableConfigDiscovery, + [property: JsonPropertyName("customAgentsLocalOnly")] bool? CustomAgentsLocalOnly, bool? SkipEmbeddingRetrieval, EmbeddingCacheStorageMode? EmbeddingCacheStorage, string? OrganizationCustomInstructions, @@ -2745,6 +2806,7 @@ internal record CreateSessionRequest( CloudSessionOptions? Cloud = null, IList? InstructionDirectories = null, IList? PluginDirectories = null, + [property: JsonPropertyName("disabledMcpServers")] IList? DisabledMcpServers = null, LargeToolOutputConfig? LargeOutput = null, ToolSearchConfig? ToolSearch = null, MemoryConfiguration? Memory = null, @@ -2760,7 +2822,10 @@ internal record CreateSessionRequest( OptionsUpdateToolFilterPrecedence? ToolFilterPrecedence = null, [property: JsonPropertyName("expAssignments")] CopilotExpAssignmentResponse? ExpAssignments = null, [property: JsonPropertyName("enableManagedSettings")] bool? EnableManagedSettings = null, - bool? EnableGitHubTelemetryForwarding = null); + [property: JsonPropertyName("managedSettings")] ManagedSettings? ManagedSettings = null, + bool? EnableGitHubTelemetryForwarding = null, + [property: JsonPropertyName("githubMcpToolConfig")] GitHubMcpToolConfig? GitHubMcpToolConfig = null, + IList? AdditionalDirectories = null); #pragma warning restore GHCP001 internal record ToolDefinition( @@ -2770,7 +2835,8 @@ internal record ToolDefinition( bool? OverridesBuiltInTool = null, bool? SkipPermission = null, CopilotToolDefer? Defer = null, - IDictionary? Metadata = null) + IDictionary? Metadata = null, + bool? IsTerminal = null) { public static ToolDefinition FromAIFunction(AIFunctionDeclaration function) { @@ -2778,11 +2844,13 @@ public static ToolDefinition FromAIFunction(AIFunctionDeclaration function) var skipPerm = function.AdditionalProperties.TryGetValue(CopilotTool.SkipPermissionKey, out var skipVal) && skipVal is true; var defer = function.AdditionalProperties.TryGetValue(CopilotTool.DeferKey, out var deferVal) && deferVal is CopilotToolDefer d ? d : (CopilotToolDefer?)null; var metadata = function.AdditionalProperties.TryGetValue(CopilotTool.MetadataKey, out var metaVal) && metaVal is IDictionary m ? m : null; + var isTerminal = function.AdditionalProperties.TryGetValue(CopilotTool.IsTerminalKey, out var terminalVal) && terminalVal is true; return new ToolDefinition(function.Name, function.Description, function.JsonSchema, overrides ? true : null, skipPerm ? true : null, defer, - metadata); + metadata, + isTerminal ? true : null); } } @@ -2803,6 +2871,7 @@ internal record ResumeSessionRequest( ContextTier? ContextTier, IList? Tools, bool? EnableCitations, + bool? EnableFileChangeTracking, SystemMessageConfig? SystemMessage, IList? AvailableTools, IList? ExcludedTools, @@ -2810,6 +2879,7 @@ internal record ResumeSessionRequest( ProviderConfig? Provider, CapiSessionOptions? Capi, bool? EnableSessionTelemetry, + bool? IsExperimentalMode, bool? RequestPermission, bool? RequestUserInput, bool? RequestExitPlanMode, @@ -2818,6 +2888,7 @@ internal record ResumeSessionRequest( string? WorkingDirectory, [property: JsonPropertyName("configDir")] string? ConfigDirectory, bool? EnableConfigDiscovery, + [property: JsonPropertyName("customAgentsLocalOnly")] bool? CustomAgentsLocalOnly, bool? SkipEmbeddingRetrieval, EmbeddingCacheStorageMode? EmbeddingCacheStorage, string? OrganizationCustomInstructions, @@ -2850,6 +2921,7 @@ internal record ResumeSessionRequest( bool? ContinuePendingWork = null, IList? InstructionDirectories = null, IList? PluginDirectories = null, + [property: JsonPropertyName("disabledMcpServers")] IList? DisabledMcpServers = null, LargeToolOutputConfig? LargeOutput = null, ToolSearchConfig? ToolSearch = null, MemoryConfiguration? Memory = null, @@ -2866,7 +2938,10 @@ internal record ResumeSessionRequest( OptionsUpdateToolFilterPrecedence? ToolFilterPrecedence = null, [property: JsonPropertyName("expAssignments")] CopilotExpAssignmentResponse? ExpAssignments = null, [property: JsonPropertyName("enableManagedSettings")] bool? EnableManagedSettings = null, - bool? EnableGitHubTelemetryForwarding = null); + [property: JsonPropertyName("managedSettings")] ManagedSettings? ManagedSettings = null, + bool? EnableGitHubTelemetryForwarding = null, + [property: JsonPropertyName("githubMcpToolConfig")] GitHubMcpToolConfig? GitHubMcpToolConfig = null, + IList? AdditionalDirectories = null); #pragma warning restore GHCP001 internal record ResumeSessionResponse( @@ -2907,6 +2982,9 @@ internal record ConnectHandshakeRequest( string? Token, [property: JsonPropertyName("enableGitHubTelemetryForwarding")] bool? EnableGitHubTelemetryForwarding = null); + internal record BuiltinPluginDirectoriesRequest( + string[] Paths); + internal record SetForegroundSessionRequest( string SessionId); @@ -2942,6 +3020,7 @@ internal record HooksInvokeResponse( [JsonSerializable(typeof(GetSessionMetadataRequest))] [JsonSerializable(typeof(GetSessionMetadataResponse))] [JsonSerializable(typeof(ConnectHandshakeRequest))] + [JsonSerializable(typeof(BuiltinPluginDirectoriesRequest))] [JsonSerializable(typeof(McpOAuthTokenStorageMode))] [JsonSerializable(typeof(EmbeddingCacheStorageMode))] [JsonSerializable(typeof(ModelCapabilitiesOverride))] diff --git a/dotnet/src/CopilotTool.cs b/dotnet/src/CopilotTool.cs index e22296bcc..ca62ccc5d 100644 --- a/dotnet/src/CopilotTool.cs +++ b/dotnet/src/CopilotTool.cs @@ -18,6 +18,9 @@ public static class CopilotTool /// The key used in to indicate that a tool can execute without a permission prompt. internal const string SkipPermissionKey = "skip_permission"; + /// The key used in to indicate that a successful call to the tool ends the agent turn. + internal const string IsTerminalKey = "is_terminal"; + /// The key used in to carry the tool's deferral mode. internal const string DeferKey = "defer"; @@ -91,7 +94,7 @@ static void ApplyToolInvocationBinding(AIFunctionFactoryOptions factoryOptions) static void ApplyToolOptions(AIFunctionFactoryOptions factoryOptions, CopilotToolOptions? toolOptions) { - if (toolOptions is not null && (toolOptions.OverridesBuiltInTool || toolOptions.SkipPermission || toolOptions.Defer is not null || toolOptions.Metadata is not null)) + if (toolOptions is not null && (toolOptions.OverridesBuiltInTool || toolOptions.SkipPermission || toolOptions.IsTerminal || toolOptions.Defer is not null || toolOptions.Metadata is not null)) { Dictionary additionalProperties = new(StringComparer.Ordinal); if (factoryOptions.AdditionalProperties is not null) @@ -112,6 +115,11 @@ static void ApplyToolOptions(AIFunctionFactoryOptions factoryOptions, CopilotToo additionalProperties[SkipPermissionKey] = true; } + if (toolOptions.IsTerminal) + { + additionalProperties[IsTerminalKey] = true; + } + if (toolOptions.Defer is { } defer) { additionalProperties[DeferKey] = defer; @@ -152,6 +160,16 @@ public sealed class CopilotToolOptions /// public bool SkipPermission { get; set; } + /// + /// Gets or sets a value indicating whether a successful call to this tool ends the agent turn. + /// + /// + /// When true, the runtime's tool phase halts after a successful call instead of feeding the result back to the + /// model for another round. A failed call leaves the loop running so the model can read the error and retry. + /// The resulting includes "is_terminal": true in its . + /// + public bool IsTerminal { get; set; } + /// /// Gets or sets a value controlling whether this tool may be deferred (loaded lazily via tool search) rather than always pre-loaded. /// diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index d09976bc8..71d49d526 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -65,7 +65,7 @@ internal sealed class ConnectResult [Experimental(Diagnostics.Experimental)] internal sealed class ConnectRequest { - /// Opt this connection in to GitHub telemetry forwarding for its lifetime. When set, the runtime forwards every internal telemetry event it emits — across all sessions, plus sessionless events — to this connection over the `gitHubTelemetry.event` notification, in addition to the runtime's normal GitHub/CTS emission (dual-write). Intended for first-party hosts that re-emit the events into their own telemetry stores. Both unrestricted and restricted events are forwarded, each tagged with a `restricted` discriminator; a backstop drops restricted events when restricted telemetry is disabled. + /// Opt this connection in to GitHub telemetry forwarding for its lifetime. When set, the runtime forwards every internal telemetry event it emits — across all sessions, plus sessionless events — to this connection over the `gitHubTelemetry.event` notification. Regular events are also written to the runtime's normal GitHub/CTS path (dual-write); host-only compatibility events are forward-only and intentionally skip that path. Intended for first-party hosts that re-emit the events into their own telemetry stores. Both unrestricted and restricted events are forwarded, each tagged with a `restricted` discriminator; a backstop drops restricted events when restricted telemetry is disabled — using the process-global gate for ordinary events and an explicit session-scoped decision for host-only events. [JsonPropertyName("enableGitHubTelemetryForwarding")] public bool? EnableGitHubTelemetryForwarding { get; set; } @@ -74,7 +74,7 @@ internal sealed class ConnectRequest public string? Token { get; set; } } -/// Active server-driven promotion for a model, including its discount and expiry. +/// Active server-driven promotion for a model, including its discount and optional expiry. [Experimental(Diagnostics.Experimental)] public sealed class ModelBillingPromo { @@ -82,15 +82,15 @@ public sealed class ModelBillingPromo [JsonPropertyName("discountPercent")] public double? DiscountPercent { get; set; } - /// UTC ISO 8601 timestamp marking when the promotion ends. Always present: the API only surfaces a promo whose expiry parses and is in the future. Consumers should treat a past value as expired. + /// UTC ISO 8601 timestamp marking when the promotion ends. Optional: an open-ended promotion omits this field. When present, the API only surfaces a promo whose expiry parses and is in the future, so consumers should treat a past value as expired. [JsonPropertyName("endsAt")] - public string EndsAt { get; set; } = string.Empty; + public string? EndsAt { get; set; } /// Stable identifier for the promotion campaign. [JsonPropertyName("id")] public string? Id { get; set; } - /// Human-readable promotion message. Does not include the expiry timestamp; consumers may format endsAt and append it. + /// Human-readable promotion message. Does not include the expiry timestamp; consumers may format endsAt and append it when present. [JsonPropertyName("message")] public string? Message { get; set; } } @@ -197,7 +197,7 @@ public sealed class ModelBilling [JsonPropertyName("multiplier")] public double? Multiplier { get; set; } - /// Active server-driven promotion for this model, if any. Present when the model is being promoted with a time-boxed discount. + /// Active server-driven promotion for this model, if any. Present when the model is being promoted with a discount, which may be time-boxed or open-ended. [JsonPropertyName("promo")] public ModelBillingPromo? Promo { get; set; } @@ -299,10 +299,6 @@ public sealed class Model [JsonPropertyName("capabilities")] public ModelCapabilities Capabilities { get => field ??= new(); set; } - /// Default reasoning effort level (only present if model supports reasoning effort). - [JsonPropertyName("defaultReasoningEffort")] - public string? DefaultReasoningEffort { get; set; } - /// Model identifier (e.g., "claude-sonnet-4.5"). [JsonPropertyName("id")] public string Id { get; set; } = string.Empty; @@ -346,6 +342,24 @@ internal sealed class ModelsListRequest public string? GitHubToken { get; set; } } +/// A well-known model in the runtime's built-in catalog. +[Experimental(Diagnostics.Experimental)] +public sealed class BuiltInModelCatalogEntry +{ + /// Well-known runtime model ID suitable for `ProviderConfig.modelId` or `ProviderModelConfig.modelId`. This is not necessarily the provider-facing deployment or model name and does not indicate CAPI entitlement or provider availability. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; +} + +/// The running runtime's complete catalog of well-known built-in model IDs, including supported models and additional IDs with built-in metadata. +[Experimental(Diagnostics.Experimental)] +public sealed class BuiltInModelCatalog +{ + /// Built-in model entries. + [JsonPropertyName("models")] + public IList Models { get => field ??= []; set; } +} + /// Built-in tool metadata with identifier, optional namespaced name, description, input-parameter schema, and usage instructions. [Experimental(Diagnostics.Experimental)] public sealed class Tool @@ -473,6 +487,10 @@ public sealed class CopilotUserResponseEndpoints [JsonPropertyName("api")] public string? Api { get; set; } + /// Gets or sets the exp value. + [JsonPropertyName("exp")] + public string? Exp { get; set; } + /// Gets or sets the origin-tracker value. [JsonPropertyName("origin-tracker")] public string? OriginTracker { get; set; } @@ -1162,6 +1180,75 @@ internal sealed class McpConfigDisableRequest public IList Names { get => field ??= []; set; } } +/// Installed plugin that contributes a discovered extension. +[Experimental(Diagnostics.Experimental)] +public sealed class DiscoveredExtensionPlugin +{ + /// Installed plugin name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; +} + +/// Discovered extension metadata and persistent enablement state. +[Experimental(Diagnostics.Experimental)] +public sealed class DiscoveredExtension +{ + /// Whether this extension's persistent per-ID preference is enabled. + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } + + /// Source-qualified ID accepted by both server and session extension enablement methods. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; + + /// Human-readable extension name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Absolute path to the extension entry module, suitable for revealing it in a file manager. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; + + /// Containing plugin metadata for plugin-contributed extensions. + [JsonPropertyName("plugin")] + public DiscoveredExtensionPlugin? Plugin { get; set; } + + /// Discovery source. + [JsonPropertyName("source")] + public DiscoveredExtensionSource Source { get; set; } +} + +/// Extensions discovered from persisted Copilot home state and their effective loading mode. Launch-scoped additional plugins are not included. +[Experimental(Diagnostics.Experimental)] +public sealed class DiscoveredExtensions +{ + /// Discovered user and enabled installed-plugin extensions from persisted Copilot home state. + [JsonPropertyName("extensions")] + public IList Extensions { get => field ??= []; set; } + + /// Effective extension loading mode. Defaults to load_and_augment when unset. + [JsonPropertyName("mode")] + public DiscoveredExtensionMode Mode { get; set; } +} + +/// Source-qualified extension identifiers to persistently enable for future sessions. +[Experimental(Diagnostics.Experimental)] +internal sealed class DiscoveredExtensionsEnableRequest +{ + /// Source-qualified user or plugin extension IDs to enable. + [JsonPropertyName("ids")] + public IList Ids { get => field ??= []; set; } +} + +/// Source-qualified extension identifiers to persistently disable for future sessions. +[Experimental(Diagnostics.Experimental)] +internal sealed class DiscoveredExtensionsDisableRequest +{ + /// Source-qualified user or plugin extension IDs to disable. + [JsonPropertyName("ids")] + public IList Ids { get => field ??= []; set; } +} + /// Information about an installed plugin tracked in global state. [Experimental(Diagnostics.Experimental)] public sealed class InstalledPluginInfo @@ -1477,6 +1564,10 @@ public sealed class ServerSkill [JsonPropertyName("argumentHint")] public string? ArgumentHint { get; set; } + /// Canonical slash command name used to invoke the skill, without the leading '/'. + [JsonPropertyName("commandName")] + public string? CommandName { get; set; } + /// Description of what the skill does. [JsonPropertyName("description")] public string Description { get; set; } = string.Empty; @@ -1588,7 +1679,7 @@ internal sealed class SkillsConfigSetDisabledSkillsRequest public IList DisabledSkills { get => field ??= []; set; } } -/// Custom agent metadata, including identifiers, display details, source, tools, model, MCP servers, skills, and file path. +/// Agent metadata, including identifiers, display details, source, tools, model, MCP servers, skills, and file path. [Experimental(Diagnostics.Experimental)] public sealed class AgentInfo { @@ -1609,11 +1700,11 @@ public sealed class AgentInfo [JsonPropertyName("mcpServers")] public IDictionary? McpServers { get; set; } - /// Preferred model id for this agent. When omitted, inherits the outer agent's model. + /// Authored preferred model id for this agent. Runtime model selection may choose a different model; omitted means no authored preference. [JsonPropertyName("model")] public string? Model { get; set; } - /// Unique identifier of the custom agent. + /// Name of the agent. Use `id` as the stable selection identifier. [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; @@ -1621,6 +1712,10 @@ public sealed class AgentInfo [JsonPropertyName("path")] public string? Path { get; set; } + /// Authored base prompt for the agent. Runtime prompt assembly may add dynamic context at invocation time. Omitted from `session.agent.list` unless `includePrompt` is true. + [JsonPropertyName("prompt")] + public string? Prompt { get; set; } + /// Skill names preloaded into this agent's context. Omitted means none. [JsonPropertyName("skills")] public IList? Skills { get; set; } @@ -1735,7 +1830,7 @@ public sealed class InstructionSource [JsonPropertyName("location")] public InstructionSourceLocation Location { get; set; } - /// The project path this source was discovered from. Only set by sessionless discovery for repository/working-directory sources, where it disambiguates same-named files (e.g. .github/copilot-instructions.md) across multiple workspace roots. The session-scoped getSources leaves it unset. + /// The project path this source was discovered from. Only set by sessionless discovery for repository, working-directory, and project-scoped plugin sources, where it disambiguates sources across multiple workspace roots. The session-scoped getSources leaves it unset. [JsonPropertyName("projectPath")] public string? ProjectPath { get; set; } @@ -1945,6 +2040,19 @@ internal sealed class UserSettingsSetRequest public JsonElement Settings { get; set; } } +/// Validated device-managed settings discovered before a session exists. +[Experimental(Diagnostics.Experimental)] +public sealed class ManagedSettingsReadResult +{ + /// Discovery or validation error text when managed settings could not be read safely. + [JsonPropertyName("errorMessage")] + public string? ErrorMessage { get; set; } + + /// Validated, canonical managed-settings JSON. Omitted when no managed settings were discovered or when discovered settings failed validation. + [JsonPropertyName("settingsJson")] + public JsonElement? SettingsJson { get; set; } +} + /// Indicates whether the calling client was registered as the session filesystem provider. [Experimental(Diagnostics.Experimental)] public sealed class SessionFsSetProviderResult @@ -2206,11 +2314,6 @@ public sealed class SessionOpenResult [JsonPropertyName("remoteSessionId")] public string? RemoteSessionId { get; set; } - /// In-process SessionClientApi handle for the opened session, returned to CLI callers as a transitional shortcut. Marked internal so the public SDK surface does not expose it; SDK consumers should construct per-session clients from `sessionId` instead. - [JsonInclude] - [JsonPropertyName("sessionApi")] - internal JsonElement? SessionApi { get; set; } - /// Opened session ID. Omitted when status is `not_found`. [JsonPropertyName("sessionId")] public string? SessionId { get; set; } @@ -2484,6 +2587,87 @@ internal sealed class SessionsListRequest public bool? ThrowOnError { get; set; } } +/// Persisted local session metadata, including identifiers, timestamps, summary/name, client, context, detached state, and task ID. +[Experimental(Diagnostics.Experimental)] +public sealed class LocalSessionMetadataValue +{ + /// Runtime client name that created/last resumed this session. + [JsonPropertyName("clientName")] + public string? ClientName { get; set; } + + /// Pre-resolved working-directory context for session startup. + [JsonPropertyName("context")] + public SessionContext? Context { get; set; } + + /// True for detached maintenance sessions that should be hidden from normal resume lists. + [JsonPropertyName("isDetached")] + public bool? IsDetached { get; set; } + + /// Always false for local sessions. + [JsonPropertyName("isRemote")] + public bool IsRemote { get; set; } + + /// GitHub task ID, when this local session is bound to one. Only present for local sessions exported to remote control. + [JsonPropertyName("mcTaskId")] + public string? McTaskId { get; set; } + + /// Last-modified time of the session's persisted state, as ISO 8601. + [JsonPropertyName("modifiedTime")] + public string ModifiedTime { get; set; } = string.Empty; + + /// Optional human-friendly name set via /rename. + [JsonPropertyName("name")] + public string? Name { get; set; } + + /// Stable session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + + /// Session creation time as an ISO 8601 timestamp. + [JsonPropertyName("startTime")] + public string StartTime { get; set; } = string.Empty; + + /// Short summary of the session, when one has been derived. + [JsonPropertyName("summary")] + public string? Summary { get; set; } +} + +/// Persisted local session metadata when the session exists. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionsGetMetadataResult +{ + /// Local session metadata, omitted when the session does not exist. + [JsonPropertyName("session")] + public LocalSessionMetadataValue? Session { get; set; } +} + +/// Session ID whose persisted metadata should be read. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionsGetMetadataRequest +{ + /// Session ID to inspect. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Recent local session IDs that contain user-visible history. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionsListNonEmptySessionIdsResult +{ + /// Session IDs ordered newest-first. + [JsonPropertyName("sessionIds")] + public IList SessionIds { get => field ??= []; set; } +} + +/// Limit for non-empty local session IDs. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionsListNonEmptySessionIdsRequest +{ + /// Maximum number of session IDs to return. + [JsonPropertyName("limit")] + public long? Limit { get; set; } +} + /// ID of the local session bound to the given GitHub task, or omitted when none. [Experimental(Diagnostics.Experimental)] public sealed class SessionsFindByTaskIDResult @@ -2634,6 +2818,19 @@ internal sealed class SessionsBulkDeleteRequest public IList SessionIds { get => field ??= []; set; } } +/// Session ID to delete from disk. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionsDeleteRequest +{ + /// Session ID to delete. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + + /// Internal resolved session directory path to delete. + [JsonPropertyName("sessionPath")] + public string? SessionPath { get; set; } +} + /// Outcome of the prune operation: deleted IDs, dry-run candidates, skipped IDs, total bytes freed, and the dry-run flag. [Experimental(Diagnostics.Experimental)] public sealed class SessionPruneResult @@ -2710,51 +2907,6 @@ internal sealed class SessionsReleaseLockRequest public string SessionId { get; set; } = string.Empty; } -/// Persisted local session metadata, including identifiers, timestamps, summary/name, client, context, detached state, and task ID. -[Experimental(Diagnostics.Experimental)] -public sealed class LocalSessionMetadataValue -{ - /// Runtime client name that created/last resumed this session. - [JsonPropertyName("clientName")] - public string? ClientName { get; set; } - - /// Pre-resolved working-directory context for session startup. - [JsonPropertyName("context")] - public SessionContext? Context { get; set; } - - /// True for detached maintenance sessions that should be hidden from normal resume lists. - [JsonPropertyName("isDetached")] - public bool? IsDetached { get; set; } - - /// Always false for local sessions. - [JsonPropertyName("isRemote")] - public bool IsRemote { get; set; } - - /// GitHub task ID, when this local session is bound to one. Only present for local sessions exported to remote control. - [JsonPropertyName("mcTaskId")] - public string? McTaskId { get; set; } - - /// Last-modified time of the session's persisted state, as ISO 8601. - [JsonPropertyName("modifiedTime")] - public string ModifiedTime { get; set; } = string.Empty; - - /// Optional human-friendly name set via /rename. - [JsonPropertyName("name")] - public string? Name { get; set; } - - /// Stable session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; - - /// Session creation time as an ISO 8601 timestamp. - [JsonPropertyName("startTime")] - public string StartTime { get; set; } = string.Empty; - - /// Short summary of the session, when one has been derived. - [JsonPropertyName("summary")] - public string? Summary { get; set; } -} - /// The enriched metadata records, with summary and context fields backfilled where available. Sessions confirmed empty and unnamed are omitted. [Experimental(Diagnostics.Experimental)] public sealed class SessionEnrichMetadataResult @@ -2848,6 +3000,10 @@ public sealed class InstalledPlugin [JsonPropertyName("source")] public JsonElement? Source { get; set; } + /// Per-plugin source fingerprint (a SHA-256 hash of the plugin's catalog source spec plus its resolved source subtree — NOT a Git commit SHA) captured at marketplace install/update time. Auto-update compares it against the freshly recomputed fingerprint to detect a content change that does not bump the version. Absent for pre-existing installs and for direct (non-marketplace) installs. + [JsonPropertyName("source_sha")] + public string? SourceSha { get; set; } + /// Version installed (if available). [JsonPropertyName("version")] public string? Version { get; set; } @@ -2949,12 +3105,6 @@ public partial class RemoteControlStatusActive : RemoteControlStatus /// Whether the MC session may steer this session. [JsonPropertyName("isSteerable")] public required bool IsSteerable { get; set; } - - /// In-process prompt-manager handle (CLI-only optimization). Marked internal: this field is excluded from the public SDK surface. When the CLI migrates to a process-separated SDK, the same bidirectional prompt-routing handshake is expressed via dedicated remote-control RPCs (register/resolve) rather than a shared in-process object. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonInclude] - [JsonPropertyName("promptManager")] - internal JsonElement? PromptManager { get; set; } } /// The last setup attempt failed. The singleton is otherwise off. @@ -3105,31 +3255,18 @@ internal sealed class SessionsStopRemoteControlRequest [Experimental(Diagnostics.Experimental)] internal sealed class RegisterExtensionToolsResult { - /// In-process unsubscribe function (CLI-only optimization). Marked internal: replaced by an explicit `extensions.unregister` RPC in the SDK migration. - [JsonInclude] - [JsonPropertyName("unsubscribe")] - internal JsonElement Unsubscribe { get; set; } } /// Optional registration options. [Experimental(Diagnostics.Experimental)] public sealed class SessionsRegisterExtensionToolsOnSessionOptions { - /// In-process `() => boolean` gating callback (CLI-only optimization). Marked internal: replaced by runtime-side enable/disable RPCs in the SDK migration. - [JsonInclude] - [JsonPropertyName("enabled")] - internal JsonElement? Enabled { get; set; } } /// Params to attach an extension loader's tools to a session. [Experimental(Diagnostics.Experimental)] internal sealed class RegisterExtensionToolsParams { - /// In-process ExtensionLoader handle (CLI-only optimization). Marked internal: this field is excluded from the public SDK surface. When the CLI migrates to a process-separated SDK, extension discovery/launch moves entirely into the runtime — the CLI passes pure config (search paths, disabled ids) via SessionOptions instead. - [JsonInclude] - [JsonPropertyName("loader")] - internal JsonElement Loader { get; set; } - /// Optional registration options. [JsonPropertyName("options")] public SessionsRegisterExtensionToolsOnSessionOptions? Options { get; set; } @@ -3143,11 +3280,6 @@ internal sealed class RegisterExtensionToolsParams [Experimental(Diagnostics.Experimental)] internal sealed class ConfigureSessionExtensionsParams { - /// In-process ExtensionController delegate (CLI-only optimization). Marked internal: this field is excluded from the public SDK surface. The post-SDK extension surface exposes list/enable/disable/reload via dedicated RPCs served by the runtime. - [JsonInclude] - [JsonPropertyName("controller")] - internal JsonElement? Controller { get; set; } - /// Session to attach the extension controller delegate to. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; @@ -3451,8 +3583,8 @@ internal sealed class SendRequest [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; - /// Optional provenance tag copied to the resulting user.message event. Must match one of three forms: the literal `system`, `command-<command-id>` for messages originating from a command (e.g. slash command, Mission Control command), or `schedule-<numeric-id>` for messages originating from a scheduled job. - [RegularExpression("^(system|command-.*|schedule-\\d+)$")] + /// Optional provenance tag copied to the resulting user.message event. Must be `user`, `system`, `command-<command-id>` for command-originated messages, `schedule-<numeric-id>` for scheduled prompts, or `agent-<agent-id>` for prompts sent by another agent. + [RegularExpression("^(user|system|command-.*|schedule-\\d+|agent-.+)$")] [JsonInclude] [JsonPropertyName("source")] internal string? Source { get; set; } @@ -3465,7 +3597,7 @@ internal sealed class SendRequest [JsonPropertyName("tracestate")] public string? Tracestate { get; set; } - /// If true, await completion of the agentic loop for this message before returning. Defaults to false (fire-and-forget). When true, the result still contains the same `messageId`; the caller can rely on the agent having processed the message before the call resolves. + /// If true, await completion of the agentic loop for this message before returning. Defaults to false (fire-and-forget). When true, the result still contains the same `messageId`; the caller can rely on the agent having processed the message before the call resolves. Transport-dependent tail semantics: on a LOCAL (in-process) session the wait additionally blocks until the completed turn's event tail has been dispatched to this session's in-process subscribers, so a subsequent read of subscriber state already reflects the turn; on a REMOTE session the wait resolves once the loop completes and mirrored delivery follows over the wire. Callers that need the stronger local guarantee on remote sessions should await the event stream explicitly. [JsonPropertyName("wait")] public bool? Wait { get; set; } } @@ -3504,8 +3636,8 @@ public sealed class SendMessageItem [JsonPropertyName("requiredTool")] public string? RequiredTool { get; set; } - /// Optional provenance tag copied to the resulting user.message event. Must match one of three forms: the literal `system`, `command-<command-id>` for messages originating from a command (e.g. slash command, Mission Control command), or `schedule-<numeric-id>` for messages originating from a scheduled job. - [RegularExpression("^(system|command-.*|schedule-\\d+)$")] + /// Optional provenance tag copied to the resulting user.message event. Must be `user`, `system`, `command-<command-id>` for command-originated messages, `schedule-<numeric-id>` for scheduled prompts, or `agent-<agent-id>` for prompts sent by another agent. + [RegularExpression("^(user|system|command-.*|schedule-\\d+|agent-.+)$")] [JsonInclude] [JsonPropertyName("source")] internal string? Source { get; set; } @@ -3547,11 +3679,32 @@ internal sealed class SendMessagesRequest [JsonPropertyName("tracestate")] public string? Tracestate { get; set; } - /// If true, await completion of the agentic loop for this turn before returning. Defaults to false (fire-and-forget). When true, the result still contains the same `messageIds`; the caller can rely on the agent having processed the messages before the call resolves. + /// If true, await completion of the agentic loop for this turn before returning. Defaults to false (fire-and-forget). When true, the result still contains the same `messageIds`; the caller can rely on the agent having processed the messages before the call resolves. Transport-dependent tail semantics: on a LOCAL (in-process) session the wait additionally blocks until the completed turn's event tail has been dispatched to this session's in-process subscribers, so a subsequent read of subscriber state already reflects the turn; on a REMOTE session the wait resolves once the loop completes and mirrored delivery follows over the wire. Callers that need the stronger local guarantee on remote sessions should await the event stream explicitly. [JsonPropertyName("wait")] public bool? Wait { get; set; } } +/// Internal request for sending a system notification. +[Experimental(Diagnostics.Experimental)] +internal sealed class SendSystemNotificationRequest +{ + /// Optional structured notification kind. + [JsonPropertyName("kind")] + public JsonElement? Kind { get; set; } + + /// Notification text to deliver to the model. + [JsonPropertyName("message")] + public string Message { get; set; } = string.Empty; + + /// Internal delivery options, including passive policy. + [JsonPropertyName("options")] + public JsonElement? Options { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + /// Result of aborting the current turn. [Experimental(Diagnostics.Experimental)] public sealed class AbortResult @@ -3578,6 +3731,37 @@ internal sealed class AbortRequest public string SessionId { get; set; } = string.Empty; } +/// Result of interrupting the main agent turn. +[Experimental(Diagnostics.Experimental)] +public sealed class InterruptMainTurnResult +{ + /// Whether an in-flight main agent turn was interrupted. False when the main loop was not processing. + [JsonPropertyName("interrupted")] + public bool Interrupted { get; set; } +} + +/// Parameters for interrupting the main agent turn. +[Experimental(Diagnostics.Experimental)] +internal sealed class InterruptMainTurnRequest +{ + /// When true, the user's queued prompts are preserved and run as the next turn once the interrupted turn unwinds; when false (the default), the queue is cleared like a plain abort. + [JsonPropertyName("flushQueued")] + public bool? FlushQueued { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionCancelAllBackgroundAgentsRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + /// Parameters for shutting down the session. [Experimental(Diagnostics.Experimental)] internal sealed class ShutdownRequest @@ -4095,6 +4279,8 @@ internal sealed class CanvasActionInvokeRequest UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] [JsonDerivedType(typeof(FactoryRunFailureFactoryLimitReached), "factory_limit_reached")] [JsonDerivedType(typeof(FactoryRunFailureFactoryResumeDeclined), "factory_resume_declined")] +[JsonDerivedType(typeof(FactoryRunFailureFactoryDurableFailure), "factory_durable_failure")] +[JsonDerivedType(typeof(FactoryRunFailureFactoryAccountingIncomplete), "factory_accounting_incomplete")] public partial class FactoryRunFailure { /// The type discriminator. @@ -4141,32 +4327,71 @@ public partial class FactoryRunFailureFactoryResumeDeclined : FactoryRunFailure public required string RunId { get; set; } } -/// Complete current or terminal factory run envelope. +/// The factory_durable_failure variant of . [Experimental(Diagnostics.Experimental)] -public sealed class FactoryRunResult +public partial class FactoryRunFailureFactoryDurableFailure : FactoryRunFailure { - /// Error message for an errored run. - [JsonPropertyName("error")] - public string? Error { get; set; } - - /// Machine-readable failure details for an errored run. - [JsonPropertyName("failure")] - public FactoryRunFailure? Failure { get; set; } + /// + [JsonIgnore] + public override string Type => "factory_durable_failure"; - /// Reason for a halted or cancelled run. - [JsonPropertyName("reason")] - public string? Reason { get; set; } + /// Stable failure code. + [JsonPropertyName("code")] + public required string Code { get; set; } - /// Completed factory result. - [JsonPropertyName("result")] - public JsonElement? Result { get; set; } + /// Execution-critical durable operation that failed. + [JsonPropertyName("operation")] + public required FactoryDurableOperation Operation { get; set; } /// Factory run identifier. [JsonPropertyName("runId")] - public string RunId { get; set; } = string.Empty; + public required string RunId { get; set; } +} - /// Partial journal and progress snapshot for a halted, cancelled, or errored run. - [JsonPropertyName("snapshot")] +/// The run stopped because its usage accounting could not be completed. +/// The factory_accounting_incomplete variant of . +[Experimental(Diagnostics.Experimental)] +public partial class FactoryRunFailureFactoryAccountingIncomplete : FactoryRunFailure +{ + /// + [JsonIgnore] + public override string Type => "factory_accounting_incomplete"; + + /// Confirmed usage in nano-AIU, representing the floor of what the run spent. + [JsonPropertyName("drainedNanoAiu")] + public required long DrainedNanoAiu { get; set; } + + /// Factory run identifier. + [JsonPropertyName("runId")] + public required string RunId { get; set; } +} + +/// Complete current or terminal factory run envelope. +[Experimental(Diagnostics.Experimental)] +public sealed class FactoryRunResult +{ + /// Error message for an errored run. + [JsonPropertyName("error")] + public string? Error { get; set; } + + /// Machine-readable failure details for an errored run. + [JsonPropertyName("failure")] + public FactoryRunFailure? Failure { get; set; } + + /// Reason for a halted or cancelled run. + [JsonPropertyName("reason")] + public string? Reason { get; set; } + + /// Completed factory result. + [JsonPropertyName("result")] + public JsonElement? Result { get; set; } + + /// Factory run identifier. + [JsonPropertyName("runId")] + public string RunId { get; set; } = string.Empty; + + /// Partial journal and progress snapshot for a halted, cancelled, or errored run. + [JsonPropertyName("snapshot")] public JsonElement? Snapshot { get; set; } /// Current or terminal factory run status. @@ -4178,6 +4403,10 @@ public sealed class FactoryRunResult [Experimental(Diagnostics.Experimental)] public sealed class FactoryRunLimits { + /// Maximum AI credits consumed by factory subagents and their descendants. The post-paid ceiling is soft: parallel turns can settle beyond it before the run stops. + [JsonPropertyName("maxAiCredits")] + public double? MaxAiCredits { get; set; } + /// Maximum number of factory subagents that may run concurrently. [JsonPropertyName("maxConcurrentSubagents")] public long? MaxConcurrentSubagents { get; set; } @@ -4186,9 +4415,9 @@ public sealed class FactoryRunLimits [JsonPropertyName("maxTotalSubagents")] public long? MaxTotalSubagents { get; set; } - /// Factory active-run timeout in milliseconds. - [JsonPropertyName("timeout")] - public double? Timeout { get; set; } + /// Maximum accumulated active-execution time in seconds. Active execution includes the entire extension body, subprocess waits, queued-agent waits, and sleeps; time between resumed attempts is not counted. + [JsonPropertyName("timeoutSeconds")] + public double? TimeoutSeconds { get; set; } } /// Options controlling factory invocation. @@ -4225,10 +4454,27 @@ internal sealed class FactoryRunRequest public string SessionId { get; set; } = string.Empty; } -/// Parameters for retrieving a factory run. +/// Resolved persisted factory identity and resumed run envelope. [Experimental(Diagnostics.Experimental)] -internal sealed class FactoryGetRunRequest +public sealed class FactoryResumeResult +{ + /// Persisted factory name resolved for the resumed run. + [JsonPropertyName("factoryName")] + public string FactoryName { get; set; } = string.Empty; + + /// Terminal resumed run envelope. + [JsonPropertyName("run")] + public FactoryRunResult Run { get => field ??= new(); set; } +} + +/// Parameters for resuming a factory run from its persisted identity. +[Experimental(Diagnostics.Experimental)] +internal sealed class FactoryResumeRequest { + /// Optional per-invocation resource ceiling overrides. + [JsonPropertyName("limits")] + public FactoryRunLimits? Limits { get; set; } + /// Factory run identifier. [JsonPropertyName("runId")] public string RunId { get; set; } = string.Empty; @@ -4238,9 +4484,9 @@ internal sealed class FactoryGetRunRequest public string SessionId { get; set; } = string.Empty; } -/// Parameters for cancelling a factory run. +/// Parameters for retrieving a factory run. [Experimental(Diagnostics.Experimental)] -internal sealed class FactoryCancelRequest +internal sealed class FactoryGetRunRequest { /// Factory run identifier. [JsonPropertyName("runId")] @@ -4251,794 +4497,1005 @@ internal sealed class FactoryCancelRequest public string SessionId { get; set; } = string.Empty; } -/// Acknowledgement that a factory request was accepted. +/// Declared or approved factory resource ceilings. [Experimental(Diagnostics.Experimental)] -public sealed class FactoryAckResult +public sealed class FactoryDeclaredLimits { -} + /// Gets or sets the maxAiCredits value. + [JsonPropertyName("maxAiCredits")] + public double? MaxAiCredits { get; set; } -/// One ordered factory progress line. -[Experimental(Diagnostics.Experimental)] -public sealed class FactoryLogLine -{ - /// Progress line kind. - [JsonPropertyName("kind")] - public FactoryLogLineKind Kind { get; set; } + /// Gets or sets the maxConcurrentSubagents value. + [JsonPropertyName("maxConcurrentSubagents")] + public long? MaxConcurrentSubagents { get; set; } - /// Monotonic sequence number within the factory run. - [JsonPropertyName("seq")] - public long Seq { get; set; } + /// Gets or sets the maxTotalSubagents value. + [JsonPropertyName("maxTotalSubagents")] + public long? MaxTotalSubagents { get; set; } - /// Progress text. - [JsonPropertyName("text")] - public string Text { get; set; } = string.Empty; + /// Gets or sets the timeoutSeconds value. + [JsonPropertyName("timeoutSeconds")] + public double? TimeoutSeconds { get; set; } } -/// Parameters for recording factory progress. +/// Durable factory resource consumption. [Experimental(Diagnostics.Experimental)] -internal sealed class FactoryLogRequest +public sealed class FactoryRunConsumed { - /// Ordered progress lines to append. - [JsonPropertyName("lines")] - public IList Lines { get => field ??= []; set; } + /// Gets or sets the activeMs value. + [JsonPropertyName("activeMs")] + public long ActiveMs { get; set; } - /// Factory run identifier. - [JsonPropertyName("runId")] - public string RunId { get; set; } = string.Empty; + /// Gets or sets the nanoAiu value. + [JsonPropertyName("nanoAiu")] + public long NanoAiu { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Gets or sets the subagents value. + [JsonPropertyName("subagents")] + public long Subagents { get; set; } } -/// Result of one factory-scoped subagent call. +/// Current factory phase identity. [Experimental(Diagnostics.Experimental)] -public sealed class FactoryAgentResult +public sealed class FactoryCurrentPhase { - /// Agent result, omitted when the agent produced no result. - [JsonPropertyName("result")] - public JsonElement? Result { get; set; } + /// Gets or sets the id value. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; + + /// Gets or sets the ordinal value. + [JsonPropertyName("ordinal")] + public long? Ordinal { get; set; } } -/// Options for one factory-scoped subagent call. +/// Prompt-safe terminal factory outcome. [Experimental(Diagnostics.Experimental)] -public sealed class FactoryAgentOptions +public sealed class FactoryRunTerminal { - /// Optional label distinguishing otherwise identical memoized agent calls. - [JsonPropertyName("label")] - public string? Label { get; set; } + /// Gets or sets the error value. + [JsonPropertyName("error")] + public string? Error { get; set; } - /// Optional model identifier for the subagent. - [JsonPropertyName("model")] - public string? Model { get; set; } + /// Gets or sets the failure value. + [JsonPropertyName("failure")] + public FactoryRunFailure? Failure { get; set; } - /// Optional JSON Schema for structured agent output. - [JsonPropertyName("schema")] - public JsonElement? Schema { get; set; } + /// Gets or sets the reason value. + [JsonPropertyName("reason")] + public string? Reason { get; set; } + + /// Gets or sets the resultPreview value. + [JsonPropertyName("resultPreview")] + public string? ResultPreview { get; set; } } -/// Parameters for one factory-scoped subagent call. +/// Durable factory run summary with read-time live overlays. [Experimental(Diagnostics.Experimental)] -internal sealed class FactoryAgentRequest +public sealed class FactoryRunSummary { - /// Factory run identifier that owns the subagent. - [JsonPropertyName("factoryRunId")] - public string FactoryRunId { get; set; } = string.Empty; + /// Gets or sets the activeSegmentStartedAt value. + [JsonPropertyName("activeSegmentStartedAt")] + public long? ActiveSegmentStartedAt { get; set; } - /// Subagent execution options. - [JsonPropertyName("opts")] - public FactoryAgentOptions Opts { get => field ??= new(); set; } + /// Gets or sets the approved value. + [JsonPropertyName("approved")] + public FactoryDeclaredLimits? Approved { get; set; } - /// Prompt to send to the subagent. - [JsonPropertyName("prompt")] - public string Prompt { get; set; } = string.Empty; + /// Gets or sets the completedAt value. + [JsonPropertyName("completedAt")] + public long? CompletedAt { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Gets or sets the consumed value. + [JsonPropertyName("consumed")] + public FactoryRunConsumed Consumed { get => field ??= new(); set; } -/// Result of reading a factory journal entry. -[Experimental(Diagnostics.Experimental)] -public sealed class FactoryJournalGetResult -{ - /// Whether the journal contained the requested key. - [JsonPropertyName("hit")] - public bool Hit { get; set; } + /// Gets or sets the createdAt value. + [JsonPropertyName("createdAt")] + public long CreatedAt { get; set; } - /// Cached JSON result. The hit field distinguishes a cached JSON null from a miss. - [JsonPropertyName("resultJson")] - public JsonElement? ResultJson { get; set; } -} + /// Gets or sets the currentPhase value. + [JsonPropertyName("currentPhase")] + public FactoryCurrentPhase? CurrentPhase { get; set; } -/// Parameters for reading a factory journal entry. -[Experimental(Diagnostics.Experimental)] -internal sealed class FactoryJournalGetRequest -{ - /// Namespaced journal key. - [JsonPropertyName("key")] - public string Key { get; set; } = string.Empty; + /// Gets or sets the declaredLimits value. + [JsonPropertyName("declaredLimits")] + public FactoryDeclaredLimits DeclaredLimits { get => field ??= new(); set; } - /// Factory run identifier. - [JsonPropertyName("runId")] - public string RunId { get; set; } = string.Empty; + /// Gets or sets the declaredPhaseCount value. + [JsonPropertyName("declaredPhaseCount")] + public long DeclaredPhaseCount { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Gets or sets the description value. + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; -/// Parameters for storing a factory journal entry. -[Experimental(Diagnostics.Experimental)] -internal sealed class FactoryJournalPutRequest -{ - /// Namespaced journal key. - [JsonPropertyName("key")] - public string Key { get; set; } = string.Empty; + /// Gets or sets the factoryName value. + [JsonPropertyName("factoryName")] + public string FactoryName { get; set; } = string.Empty; - /// JSON result to memoize. - [JsonPropertyName("resultJson")] - public JsonElement ResultJson { get; set; } + /// Gets or sets the liveAgentCount value. + [JsonPropertyName("liveAgentCount")] + public long LiveAgentCount { get; set; } - /// Factory run identifier. + /// Gets or sets the observedAt value. + [JsonPropertyName("observedAt")] + public long ObservedAt { get; set; } + + /// Gets or sets the revision value. + [JsonPropertyName("revision")] + public long Revision { get; set; } + + /// Gets or sets the runId value. [JsonPropertyName("runId")] public string RunId { get; set; } = string.Empty; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Gets or sets the startedAt value. + [JsonPropertyName("startedAt")] + public long? StartedAt { get; set; } + + /// Gets or sets the status value. + [JsonPropertyName("status")] + public FactoryRunStatus Status { get; set; } + + /// Gets or sets the terminal value. + [JsonPropertyName("terminal")] + public FactoryRunTerminal? Terminal { get; set; } + + /// Gets or sets the totalSpawnedAgentCount value. + [JsonPropertyName("totalSpawnedAgentCount")] + public long TotalSpawnedAgentCount { get; set; } + + /// Gets or sets the updatedAt value. + [JsonPropertyName("updatedAt")] + public long UpdatedAt { get; set; } } -/// The currently selected model, reasoning effort, and context tier for the session. The context tier reflects `Session.getContextTier()`, restored from the session journal on resume. +/// A page of factory runs in durable creation order. [Experimental(Diagnostics.Experimental)] -public sealed class CurrentModel +public sealed class FactoryListRunsResult { - /// Context tier for models that support multiple context-window sizes. - [JsonPropertyName("contextTier")] - public ContextTier? ContextTier { get; set; } + /// Whether terminal runs newer than this page exist. + [JsonPropertyName("hasMoreNewer")] + public bool? HasMoreNewer { get; set; } - /// Currently active model identifier. - [JsonPropertyName("modelId")] - public string? ModelId { get; set; } + /// Newest terminal-run cursor in this page, or null when the terminal window is empty. + [JsonPropertyName("newestSeq")] + public long? NewestSeq { get; set; } - /// Reasoning effort level currently applied to the active model, when one is set. Reads `Session.getReasoningEffort()` synchronously after `getSelectedModel()` resolves so the two values are reported as a snapshot. - [JsonPropertyName("reasoningEffort")] - public string? ReasoningEffort { get; set; } + /// Oldest terminal-run cursor in this page, or null when the terminal window is empty. + [JsonPropertyName("oldestSeq")] + public long? OldestSeq { get; set; } + + /// Number of terminal runs older than this page. + [JsonPropertyName("omittedOlder")] + public long? OmittedOlder { get; set; } + + /// Gets or sets the runs value. + [JsonPropertyName("runs")] + public IList Runs { get => field ??= []; set; } } -/// Identifies the target session. +/// Parameters for paging factory runs. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionModelGetCurrentRequest +internal sealed class FactoryListRunsRequest { + /// Exclusive forward cursor. + [JsonPropertyName("afterSeq")] + public long? AfterSeq { get; set; } + + /// Exclusive backward cursor. + [JsonPropertyName("beforeSeq")] + public long? BeforeSeq { get; set; } + + /// Maximum terminal runs to return. Defaults to 200 and is capped at 500. + [JsonPropertyName("limit")] + public int? Limit { get; set; } + /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// The model identifier active on the session after the switch. +/// Prompt-safe durable identity and live status for a direct factory agent. [Experimental(Diagnostics.Experimental)] -public sealed class ModelSwitchToResult +public sealed class FactoryAgentSummary { - /// Currently active model identifier after the switch. - [JsonPropertyName("modelId")] - public string? ModelId { get; set; } -} + /// Gets or sets the activeMs value. + [JsonPropertyName("activeMs")] + public long ActiveMs { get; set; } -/// Vision-specific limits. -[Experimental(Diagnostics.Experimental)] -public sealed class ModelCapabilitiesOverrideLimitsVision -{ - /// Maximum image size in bytes. - [JsonPropertyName("max_prompt_image_size")] - public long? MaxPromptImageSize { get; set; } + /// Gets or sets the activity value. + [JsonPropertyName("activity")] + public string? Activity { get; set; } - /// Maximum number of images per prompt. - [JsonPropertyName("max_prompt_images")] - public long? MaxPromptImages { get; set; } + /// Gets or sets the agentId value. + [JsonPropertyName("agentId")] + public string AgentId { get; set; } = string.Empty; - /// MIME types the model accepts. - [JsonPropertyName("supported_media_types")] - public IList? SupportedMediaTypes { get; set; } + /// Gets or sets the agentType value. + [JsonPropertyName("agentType")] + public string AgentType { get; set; } = string.Empty; + + /// Gets or sets the completedAt value. + [JsonPropertyName("completedAt")] + public long? CompletedAt { get; set; } + + /// Gets or sets the label value. + [JsonPropertyName("label")] + public string Label { get; set; } = string.Empty; + + /// Gets or sets the phaseId value. + [JsonPropertyName("phaseId")] + public string? PhaseId { get; set; } + + /// Gets or sets the requestedModel value. + [JsonPropertyName("requestedModel")] + public string? RequestedModel { get; set; } + + /// Gets or sets the resolvedModel value. + [JsonPropertyName("resolvedModel")] + public string? ResolvedModel { get; set; } + + /// Gets or sets the runId value. + [JsonPropertyName("runId")] + public string RunId { get; set; } = string.Empty; + + /// Gets or sets the startedAt value. + [JsonPropertyName("startedAt")] + public long? StartedAt { get; set; } + + /// Gets or sets the status value. + [JsonPropertyName("status")] + public string Status { get; set; } = string.Empty; + + /// Gets or sets the toolCallId value. + [JsonPropertyName("toolCallId")] + public string ToolCallId { get; set; } = string.Empty; } -/// Token limits for prompts, outputs, and context window. +/// Durable lifecycle and timing for one factory phase. [Experimental(Diagnostics.Experimental)] -public sealed class ModelCapabilitiesOverrideLimits +public sealed class FactoryPhaseObservation { - /// Maximum total context window size in tokens. - [JsonPropertyName("max_context_window_tokens")] - public long? MaxContextWindowTokens { get; set; } + /// Gets or sets the accumulatedActiveMs value. + [JsonPropertyName("accumulatedActiveMs")] + public long AccumulatedActiveMs { get; set; } - /// Maximum number of output/completion tokens. - [JsonPropertyName("max_output_tokens")] - public long? MaxOutputTokens { get; set; } + /// Gets or sets the completedAt value. + [JsonPropertyName("completedAt")] + public long? CompletedAt { get; set; } - /// Maximum number of prompt/input tokens. - [JsonPropertyName("max_prompt_tokens")] - public long? MaxPromptTokens { get; set; } + /// Gets or sets the currentActiveMs value. + [JsonPropertyName("currentActiveMs")] + public long CurrentActiveMs { get; set; } - /// Vision-specific limits. - [JsonPropertyName("vision")] - public ModelCapabilitiesOverrideLimitsVision? Vision { get; set; } + /// Gets or sets the detail value. + [JsonPropertyName("detail")] + public string? Detail { get; set; } + + /// Gets or sets the entryCount value. + [JsonPropertyName("entryCount")] + public long EntryCount { get; set; } + + /// Gets or sets the id value. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; + + /// Gets or sets the lastEnteredRunAttempt value. + [JsonPropertyName("lastEnteredRunAttempt")] + public long LastEnteredRunAttempt { get; set; } + + /// Gets or sets the liveAgentCount value. + [JsonPropertyName("liveAgentCount")] + public long LiveAgentCount { get; set; } + + /// Gets or sets the ordinal value. + [JsonPropertyName("ordinal")] + public long? Ordinal { get; set; } + + /// Gets or sets the startedAt value. + [JsonPropertyName("startedAt")] + public long? StartedAt { get; set; } + + /// Gets or sets the status value. + [JsonPropertyName("status")] + public FactoryPhaseStatus Status { get; set; } + + /// Gets or sets the title value. + [JsonPropertyName("title")] + public string Title { get; set; } = string.Empty; + + /// Gets or sets the totalAgentCount value. + [JsonPropertyName("totalAgentCount")] + public long TotalAgentCount { get; set; } } -/// Feature flags indicating what the model supports. +/// One durable factory progress record. [Experimental(Diagnostics.Experimental)] -public sealed class ModelCapabilitiesOverrideSupports +public sealed class FactoryProgressLine { - /// Resolved Anthropic adaptive-thinking capability — unsupported / optional / required. 'required' models reject thinking.type='enabled' with HTTP 400 (e.g. opus-4.7/4.8). - [JsonPropertyName("adaptive_thinking")] - public AdaptiveThinkingSupport? AdaptiveThinking { get; set; } + /// Resume attempt that emitted this record. + [JsonPropertyName("attempt")] + public long Attempt { get; set; } - /// Whether this model supports reasoning effort configuration. - [JsonPropertyName("reasoningEffort")] - public bool? ReasoningEffort { get; set; } + /// Progress record kind. + [JsonPropertyName("kind")] + public FactoryLogLineKind Kind { get; set; } - /// Whether this model supports vision/image input. - [JsonPropertyName("vision")] - public bool? Vision { get; set; } + /// Phase active when the record was emitted, or null before any phase. + [JsonPropertyName("phaseId")] + public string? PhaseId { get; set; } + + /// Epoch milliseconds when the record was persisted. + [JsonPropertyName("recordedAt")] + public long RecordedAt { get; set; } + + /// Global monotonic sequence number within the run. + [JsonPropertyName("seq")] + public long Seq { get; set; } + + /// Prompt-safe progress text. + [JsonPropertyName("text")] + public string Text { get; set; } = string.Empty; } -/// Optional capability overrides (vision, tool_calls, reasoning, etc.). +/// A bidirectional page of factory progress. [Experimental(Diagnostics.Experimental)] -public sealed class ModelCapabilitiesOverride +public sealed class FactoryProgressPage { - /// Token limits for prompts, outputs, and context window. - [JsonPropertyName("limits")] - public ModelCapabilitiesOverrideLimits? Limits { get; set; } + /// Gets or sets the hasMoreNewer value. + [JsonPropertyName("hasMoreNewer")] + public bool HasMoreNewer { get; set; } - /// Feature flags indicating what the model supports. - [JsonPropertyName("supports")] - public ModelCapabilitiesOverrideSupports? Supports { get; set; } + /// Gets or sets the hasMoreOlder value. + [JsonPropertyName("hasMoreOlder")] + public bool HasMoreOlder { get; set; } + + /// Gets or sets the newestSeq value. + [JsonPropertyName("newestSeq")] + public long? NewestSeq { get; set; } + + /// Gets or sets the oldestSeq value. + [JsonPropertyName("oldestSeq")] + public long? OldestSeq { get; set; } + + /// Gets or sets the records value. + [JsonPropertyName("records")] + public IList Records { get => field ??= []; set; } + + /// Run revision reflected by this page. + [JsonPropertyName("revision")] + public long Revision { get; set; } } -/// Target model identifier and optional reasoning effort, summary, capability overrides, and context tier. +/// Full factory run observability detail. [Experimental(Diagnostics.Experimental)] -internal sealed class ModelSwitchToRequest +public sealed class FactoryRunDetail { - /// Explicit context tier for the selected model. `"default"` / `"long_context"` apply the requested tier; omit this field to use normal model behavior with no explicit tier. - [JsonPropertyName("contextTier")] - public ContextTier? ContextTier { get; set; } + /// Gets or sets the activeSegmentStartedAt value. + [JsonPropertyName("activeSegmentStartedAt")] + public long? ActiveSegmentStartedAt { get; set; } - /// Override individual model capabilities resolved by the runtime. - [JsonPropertyName("modelCapabilities")] - public ModelCapabilitiesOverride? ModelCapabilities { get; set; } + /// Gets or sets the agents value. + [JsonPropertyName("agents")] + public IList Agents { get => field ??= []; set; } - /// Model selection id to switch to, as returned by `list`. A bare id (e.g. `claude-sonnet-4.6`) names a Copilot (CAPI) model; a provider-qualified id (`provider/id`, e.g. `acme/claude-sonnet`) targets a registry BYOK model. - [JsonPropertyName("modelId")] - public string ModelId { get; set; } = string.Empty; + /// Gets or sets the approved value. + [JsonPropertyName("approved")] + public FactoryDeclaredLimits? Approved { get; set; } - /// Reasoning effort level to use for the model. "none" disables reasoning. - [JsonPropertyName("reasoningEffort")] - public string? ReasoningEffort { get; set; } + /// Gets or sets the completedAt value. + [JsonPropertyName("completedAt")] + public long? CompletedAt { get; set; } - /// Reasoning summary mode to request for supported model clients. - [JsonPropertyName("reasoningSummary")] - public ReasoningSummary? ReasoningSummary { get; set; } + /// Gets or sets the consumed value. + [JsonPropertyName("consumed")] + public FactoryRunConsumed Consumed { get => field ??= new(); set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Gets or sets the createdAt value. + [JsonPropertyName("createdAt")] + public long CreatedAt { get; set; } - /// Output verbosity level to request for supported models. - [JsonPropertyName("verbosity")] - public Verbosity? Verbosity { get; set; } -} + /// Gets or sets the currentPhase value. + [JsonPropertyName("currentPhase")] + public FactoryCurrentPhase? CurrentPhase { get; set; } -/// Update the session's reasoning effort without changing the selected model. Use `switchTo` instead when you also need to change the model. The runtime stores the effort on the session and applies it to subsequent turns. -[Experimental(Diagnostics.Experimental)] -public sealed class ModelSetReasoningEffortResult -{ - /// Reasoning effort level recorded on the session after the update. - [JsonPropertyName("reasoningEffort")] - public string ReasoningEffort { get; set; } = string.Empty; -} + /// Gets or sets the declaredLimits value. + [JsonPropertyName("declaredLimits")] + public FactoryDeclaredLimits DeclaredLimits { get => field ??= new(); set; } -/// Reasoning effort level to apply to the currently selected model. -[Experimental(Diagnostics.Experimental)] -internal sealed class ModelSetReasoningEffortRequest -{ - /// Reasoning effort level to apply to the currently selected model. The host is responsible for validating the value against the model's supported levels before calling. - [JsonPropertyName("reasoningEffort")] - public string ReasoningEffort { get; set; } = string.Empty; + /// Gets or sets the declaredPhaseCount value. + [JsonPropertyName("declaredPhaseCount")] + public long DeclaredPhaseCount { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Gets or sets the description value. + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; -/// Cost-category metadata for a CAPI model. -[Experimental(Diagnostics.Experimental)] -public sealed class SessionModelPriceCategory -{ - /// Gets or sets the id value. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// Gets or sets the factoryName value. + [JsonPropertyName("factoryName")] + public string FactoryName { get; set; } = string.Empty; - /// Gets or sets the priceCategory value. - [JsonPropertyName("priceCategory")] - public ModelPickerPriceCategory PriceCategory { get; set; } -} + /// Gets or sets the liveAgentCount value. + [JsonPropertyName("liveAgentCount")] + public long LiveAgentCount { get; set; } -/// The list of models available to this session. -[Experimental(Diagnostics.Experimental)] -public sealed class SessionModelList -{ - /// Available models, ordered with the most preferred default first. Includes both Copilot (CAPI) models and any registry BYOK models; a BYOK model appears under its provider-qualified selection id (`provider/id`). - [JsonPropertyName("list")] - public IList List { get => field ??= []; set; } + /// Gets or sets the observedAt value. + [JsonPropertyName("observedAt")] + public long ObservedAt { get; set; } - /// Cost categories for the full CAPI catalog, including picker-disabled models that Auto may select. Metadata only; entries absent from `list` are not manually selectable. - [JsonPropertyName("modelPriceCategories")] - public IList? ModelPriceCategories { get; set; } + /// Gets or sets the phases value. + [JsonPropertyName("phases")] + public IList Phases { get => field ??= []; set; } - /// Per-quota snapshots returned alongside the model list, keyed by quota type. - [JsonPropertyName("quotaSnapshots")] - public IDictionary? QuotaSnapshots { get; set; } -} + /// Gets or sets the progress value. + [JsonPropertyName("progress")] + public FactoryProgressPage Progress { get => field ??= new(); set; } -/// Optional listing options. -[Experimental(Diagnostics.Experimental)] -public sealed class ModelListRequest -{ - /// If true, bypasses the per-session model list cache and re-fetches from CAPI. - [JsonPropertyName("skipCache")] - public bool? SkipCache { get; set; } -} + /// Gets or sets the revision value. + [JsonPropertyName("revision")] + public long Revision { get; set; } -/// Optional listing options. -[Experimental(Diagnostics.Experimental)] -internal sealed class ModelListRequestWithSession -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Gets or sets the runId value. + [JsonPropertyName("runId")] + public string RunId { get; set; } = string.Empty; - /// If true, bypasses the per-session model list cache and re-fetches from CAPI. - [JsonPropertyName("skipCache")] - public bool? SkipCache { get; set; } + /// Gets or sets the startedAt value. + [JsonPropertyName("startedAt")] + public long? StartedAt { get; set; } + + /// Gets or sets the status value. + [JsonPropertyName("status")] + public FactoryRunStatus Status { get; set; } + + /// Gets or sets the terminal value. + [JsonPropertyName("terminal")] + public FactoryRunTerminal? Terminal { get; set; } + + /// Gets or sets the totalSpawnedAgentCount value. + [JsonPropertyName("totalSpawnedAgentCount")] + public long TotalSpawnedAgentCount { get; set; } + + /// Gets or sets the updatedAt value. + [JsonPropertyName("updatedAt")] + public long UpdatedAt { get; set; } } -/// Identifies the target session. +/// Parameters for paging factory progress. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionModeGetRequest +internal sealed class FactoryGetRunProgressRequest { + /// Exclusive forward cursor. + [JsonPropertyName("afterSeq")] + public long? AfterSeq { get; set; } + + /// Exclusive backward cursor. + [JsonPropertyName("beforeSeq")] + public long? BeforeSeq { get; set; } + + /// Maximum records to return. Defaults to 200 and is capped at 500. + [JsonPropertyName("limit")] + public int? Limit { get; set; } + + /// Optional phase identifier used to scope records and cursors. + [JsonPropertyName("phaseId")] + public string? PhaseId { get; set; } + + /// Factory run identifier. + [JsonPropertyName("runId")] + public string RunId { get; set; } = string.Empty; + /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Agent interaction mode to apply to the session. +/// Parameters for cancelling a factory run. [Experimental(Diagnostics.Experimental)] -internal sealed class ModeSetRequest +internal sealed class FactoryCancelRequest { - /// The session mode the agent is operating in. - [JsonPropertyName("mode")] - public SessionMode Mode { get; set; } + /// Factory run identifier. + [JsonPropertyName("runId")] + public string RunId { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// The session's friendly name, or null when not yet set. +/// Acknowledgement that a factory request was accepted. [Experimental(Diagnostics.Experimental)] -public sealed class NameGetResult +public sealed class FactoryAckResult { - /// The session name (user-set or auto-generated), or null if not yet set. - [JsonPropertyName("name")] - public string? Name { get; set; } } -/// Identifies the target session. +/// One ordered factory progress line. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionNameGetRequest +public sealed class FactoryLogLine { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Progress line kind. + [JsonPropertyName("kind")] + public FactoryLogLineKind Kind { get; set; } + + /// Monotonic sequence number within the factory run. + [JsonPropertyName("seq")] + public long Seq { get; set; } + + /// Progress text. + [JsonPropertyName("text")] + public string Text { get; set; } = string.Empty; } -/// New friendly name to apply to the session. +/// Parameters for recording factory progress. [Experimental(Diagnostics.Experimental)] -internal sealed class NameSetRequest +internal sealed class FactoryLogRequest { - /// New session name (1–100 characters, trimmed of leading/trailing whitespace). - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] - [MinLength(1)] - [MaxLength(100)] - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Opaque token identifying the current factory execution attempt. + [JsonPropertyName("executionToken")] + public string ExecutionToken { get; set; } = string.Empty; + + /// Ordered progress lines to append. + [JsonPropertyName("lines")] + public IList Lines { get => field ??= []; set; } + + /// Factory run identifier. + [JsonPropertyName("runId")] + public string RunId { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Indicates whether the auto-generated summary was applied as the session's name. +/// Result of one factory-scoped subagent call. [Experimental(Diagnostics.Experimental)] -public sealed class NameSetAutoResult +public sealed class FactoryAgentResult { - /// Whether the auto-generated summary was persisted. False if the session already has a user-set name, the summary normalized to empty, or the session does not have a workspace. - [JsonPropertyName("applied")] - public bool Applied { get; set; } + /// Agent result, omitted when the agent produced no result. + [JsonPropertyName("result")] + public JsonElement? Result { get; set; } } -/// Auto-generated session summary to apply as the session's name when no user-set name exists. +/// Options for one factory-scoped subagent call. [Experimental(Diagnostics.Experimental)] -internal sealed class NameSetAutoRequest +public sealed class FactoryAgentOptions { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Optional custom agent name for the subagent. This field is accepted but not yet honored. + [JsonPropertyName("agent")] + public string? Agent { get; set; } - /// Auto-generated session summary. Empty/whitespace-only values are ignored; values are trimmed before persisting. - [JsonPropertyName("summary")] - public string Summary { get; set; } = string.Empty; -} + /// Optional context tier for the subagent. This field is accepted but not yet honored. + [JsonPropertyName("contextTier")] + public ContextTier? ContextTier { get; set; } -/// Existence, contents, and resolved path of the session plan file. -[Experimental(Diagnostics.Experimental)] -public sealed class PlanReadResult -{ - /// The content of the plan file, or null if it does not exist. - [JsonPropertyName("content")] - public string? Content { get; set; } + /// Optional label distinguishing otherwise identical memoized agent calls. + [JsonPropertyName("label")] + public string? Label { get; set; } - /// Whether the plan file exists in the workspace. - [JsonPropertyName("exists")] - public bool Exists { get; set; } + /// Optional model identifier for the subagent. + [JsonPropertyName("model")] + public string? Model { get; set; } - /// Absolute file path of the plan file, or null if workspace is not enabled. - [JsonPropertyName("path")] - public string? Path { get; set; } + /// Optional reasoning effort for the subagent. This field is accepted but not yet honored. + [JsonPropertyName("reasoningEffort")] + public string? ReasoningEffort { get; set; } + + /// Optional JSON Schema for structured agent output. + [JsonPropertyName("schema")] + public JsonElement? Schema { get; set; } } -/// Identifies the target session. +/// Parameters for one factory-scoped subagent call. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionPlanReadRequest +internal sealed class FactoryAgentRequest { + /// Opaque token identifying the current factory execution attempt. + [JsonPropertyName("executionToken")] + public string ExecutionToken { get; set; } = string.Empty; + + /// Factory run identifier that owns the subagent. + [JsonPropertyName("factoryRunId")] + public string FactoryRunId { get; set; } = string.Empty; + + /// Subagent execution options. + [JsonPropertyName("opts")] + public FactoryAgentOptions Opts { get => field ??= new(); set; } + + /// Prompt to send to the subagent. + [JsonPropertyName("prompt")] + public string Prompt { get; set; } = string.Empty; + /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Replacement contents to write to the session plan file. +/// Result of reading a factory journal entry. [Experimental(Diagnostics.Experimental)] -internal sealed class PlanUpdateRequest +public sealed class FactoryJournalGetResult { - /// The new content for the plan file. - [JsonPropertyName("content")] - public string Content { get; set; } = string.Empty; + /// Whether the journal contained the requested key. + [JsonPropertyName("hit")] + public bool Hit { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Cached JSON result. The hit field distinguishes a cached JSON null from a miss. + [JsonPropertyName("resultJson")] + public JsonElement? ResultJson { get; set; } } -/// Identifies the target session. +/// Parameters for reading a factory journal entry. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionPlanDeleteRequest +internal sealed class FactoryJournalGetRequest { + /// Opaque token identifying the current factory execution attempt. + [JsonPropertyName("executionToken")] + public string ExecutionToken { get; set; } = string.Empty; + + /// Namespaced journal key. + [JsonPropertyName("key")] + public string Key { get; set; } = string.Empty; + + /// Factory run identifier. + [JsonPropertyName("runId")] + public string RunId { get; set; } = string.Empty; + /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// A single todo row read from the session SQL `todos` table. All fields are optional because the SQL schema is best-effort and the agent may not have populated every column. +/// Parameters for storing a factory journal entry. [Experimental(Diagnostics.Experimental)] -public sealed class PlanSqlTodosRow +internal sealed class FactoryJournalPutRequest { - /// Todo description. - [JsonPropertyName("description")] - public string? Description { get; set; } + /// Opaque token identifying the current factory execution attempt. + [JsonPropertyName("executionToken")] + public string ExecutionToken { get; set; } = string.Empty; - /// Todo identifier. - [JsonPropertyName("id")] - public string? Id { get; set; } + /// Namespaced journal key. + [JsonPropertyName("key")] + public string Key { get; set; } = string.Empty; - /// Todo status. - [JsonPropertyName("status")] - public string? Status { get; set; } + /// JSON result to memoize. + [JsonPropertyName("resultJson")] + public JsonElement ResultJson { get; set; } - /// Todo title. - [JsonPropertyName("title")] - public string? Title { get; set; } + /// Factory run identifier. + [JsonPropertyName("runId")] + public string RunId { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Todo rows read from the session SQL database. Empty when no session database is available. +/// The currently selected model, reasoning effort, and context tier for the session. The context tier reflects `Session.getContextTier()`, restored from the session journal on resume. [Experimental(Diagnostics.Experimental)] -public sealed class PlanReadSqlTodosResult +public sealed class CurrentModel { - /// Rows from the session SQL todos table, ordered by creation time and id. - [JsonPropertyName("rows")] - public IList Rows { get => field ??= []; set; } + /// Context tier for models that support multiple context-window sizes. + [JsonPropertyName("contextTier")] + public ContextTier? ContextTier { get; set; } + + /// Currently active model identifier. + [JsonPropertyName("modelId")] + public string? ModelId { get; set; } + + /// Reasoning effort level currently applied to the active model, when one is set. Reads `Session.getReasoningEffort()` synchronously after `getSelectedModel()` resolves so the two values are reported as a snapshot. + [JsonPropertyName("reasoningEffort")] + public string? ReasoningEffort { get; set; } } /// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionPlanReadSqlTodosRequest +internal sealed class SessionModelGetCurrentRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// A single dependency edge read from the session SQL `todo_deps` table, indicating that one todo must complete before another. +/// The model identifier active on the session after the switch. [Experimental(Diagnostics.Experimental)] -public sealed class PlanSqlTodoDependency +public sealed class ModelSwitchToResult { - /// ID of the todo it depends on. - [JsonPropertyName("dependsOn")] - public string DependsOn { get; set; } = string.Empty; + /// True when the switch was deferred (enqueued as a cancellable `/model` command) because a turn was active or another model change was already queued, rather than applied immediately. When true, the session's live model is unchanged until the queued change drains. + [JsonPropertyName("deferred")] + public bool? Deferred { get; set; } - /// ID of the todo that has the dependency. - [JsonPropertyName("todoId")] - public string TodoId { get; set; } = string.Empty; + /// Currently active model identifier after the switch. + [JsonPropertyName("modelId")] + public string? ModelId { get; set; } } -/// Todo rows + dependency edges read from the session SQL database. +/// Vision-specific limits. [Experimental(Diagnostics.Experimental)] -public sealed class PlanReadSqlTodosWithDependenciesResult +public sealed class ModelCapabilitiesOverrideLimitsVision { - /// Edges from the session SQL todo_deps table. Empty when no database, no todo_deps table, or the SELECT failed. Read independently from `rows`, so a broken todo_deps table does not affect the rows result and vice versa. - [JsonPropertyName("dependencies")] - public IList Dependencies { get => field ??= []; set; } + /// Maximum image size in bytes. + [JsonPropertyName("max_prompt_image_size")] + public long? MaxPromptImageSize { get; set; } - /// Rows from the session SQL todos table, ordered by creation time and id. Empty when no database, no todos table, or the SELECT failed. - [JsonPropertyName("rows")] - public IList Rows { get => field ??= []; set; } + /// Maximum number of images per prompt. + [JsonPropertyName("max_prompt_images")] + public long? MaxPromptImages { get; set; } + + /// MIME types the model accepts. + [JsonPropertyName("supported_media_types")] + public IList? SupportedMediaTypes { get; set; } } -/// Identifies the target session. +/// Token limits for prompts, outputs, and context window. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionPlanReadSqlTodosWithDependenciesRequest +public sealed class ModelCapabilitiesOverrideLimits { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Maximum total context window size in tokens. + [JsonPropertyName("max_context_window_tokens")] + public long? MaxContextWindowTokens { get; set; } -/// RPC data type for WorkspacesGetWorkspaceResultWorkspace operations. -public sealed class WorkspacesGetWorkspaceResultWorkspace -{ - /// Gets or sets the branch value. - [JsonPropertyName("branch")] - public string? Branch { get; set; } + /// Maximum number of output/completion tokens. + [JsonPropertyName("max_output_tokens")] + public long? MaxOutputTokens { get; set; } - /// Gets or sets the chronicle_sync_dismissed value. - [JsonPropertyName("chronicle_sync_dismissed")] - public bool? ChronicleSyncDismissed { get; set; } + /// Maximum number of prompt/input tokens. + [JsonPropertyName("max_prompt_tokens")] + public long? MaxPromptTokens { get; set; } - /// Gets or sets the client_name value. - [JsonPropertyName("client_name")] - public string? ClientName { get; set; } + /// Vision-specific limits. + [JsonPropertyName("vision")] + public ModelCapabilitiesOverrideLimitsVision? Vision { get; set; } +} - /// Gets or sets the created_at value. - [JsonPropertyName("created_at")] - public DateTimeOffset? CreatedAt { get; set; } +/// Feature flags indicating what the model supports. +[Experimental(Diagnostics.Experimental)] +public sealed class ModelCapabilitiesOverrideSupports +{ + /// Resolved Anthropic adaptive-thinking capability — unsupported / optional / required. 'required' models reject thinking.type='enabled' with HTTP 400 (e.g. opus-4.7/4.8). + [JsonPropertyName("adaptive_thinking")] + public AdaptiveThinkingSupport? AdaptiveThinking { get; set; } - /// Gets or sets the cwd value. - [JsonPropertyName("cwd")] - public string? Cwd { get; set; } + /// Whether this model supports reasoning effort configuration. + [JsonPropertyName("reasoningEffort")] + public bool? ReasoningEffort { get; set; } - /// Gets or sets the git_root value. - [JsonPropertyName("git_root")] - public string? GitRoot { get; set; } + /// Whether this model supports vision/image input. + [JsonPropertyName("vision")] + public bool? Vision { get; set; } +} - /// Allowed values for the `WorkspacesWorkspaceDetailsHostType` enumeration. - [JsonPropertyName("host_type")] - public WorkspacesWorkspaceDetailsHostType? HostType { get; set; } +/// Optional capability overrides (vision, tool_calls, reasoning, etc.). +[Experimental(Diagnostics.Experimental)] +public sealed class ModelCapabilitiesOverride +{ + /// Token limits for prompts, outputs, and context window. + [JsonPropertyName("limits")] + public ModelCapabilitiesOverrideLimits? Limits { get; set; } - /// Gets or sets the id value. - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] - [MinLength(1)] - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// Feature flags indicating what the model supports. + [JsonPropertyName("supports")] + public ModelCapabilitiesOverrideSupports? Supports { get; set; } +} - /// Gets or sets the mc_last_event_id value. - [JsonPropertyName("mc_last_event_id")] - public string? McLastEventId { get; set; } +/// Target model identifier and optional reasoning effort, summary, capability overrides, and context tier. +[Experimental(Diagnostics.Experimental)] +internal sealed class ModelSwitchToRequest +{ + /// Explicit context tier for the selected model. `"default"` / `"long_context"` apply the requested tier; omit this field to use normal model behavior with no explicit tier. + [JsonPropertyName("contextTier")] + public ContextTier? ContextTier { get; set; } - /// Gets or sets the mc_session_id value. - [JsonPropertyName("mc_session_id")] - public string? McSessionId { get; set; } + /// When true, defer this switch (enqueue it) if another model change is already queued, even when no turn is active — so it drains last (FIFO) and wins over the already-queued change. Intended for genuine user-initiated model selections; internal restore/reapply switches omit it and apply immediately when no turn is active. When no other model change is queued this has no effect (a switch still applies immediately unless a turn is active). + [JsonPropertyName("deferIfModelChangeQueued")] + public bool? DeferIfModelChangeQueued { get; set; } - /// Gets or sets the mc_task_id value. - [JsonPropertyName("mc_task_id")] - public string? McTaskId { get; set; } + /// Override individual model capabilities resolved by the runtime. + [JsonPropertyName("modelCapabilities")] + public ModelCapabilitiesOverride? ModelCapabilities { get; set; } - /// Gets or sets the name value. - [JsonPropertyName("name")] - public string? Name { get; set; } + /// Model selection id to switch to, as returned by `list`. A bare id (e.g. `claude-sonnet-4.6`) names a Copilot (CAPI) model; a provider-qualified id (`provider/id`, e.g. `acme/claude-sonnet`) targets a registry BYOK model. + [JsonPropertyName("modelId")] + public string ModelId { get; set; } = string.Empty; - /// Gets or sets the remote_steerable value. - [JsonPropertyName("remote_steerable")] - public bool? RemoteSteerable { get; set; } + /// Reasoning effort level to use for the model. CAPI values are model-defined and validated against the selected model; BYOK providers may define additional values. "none" disables reasoning. When omitted, no effort override is applied. + [JsonPropertyName("reasoningEffort")] + public string? ReasoningEffort { get; set; } - /// Gets or sets the repository value. - [JsonPropertyName("repository")] - public string? Repository { get; set; } + /// Reasoning summary mode to request for supported model clients. + [JsonPropertyName("reasoningSummary")] + public ReasoningSummary? ReasoningSummary { get; set; } - /// Gets or sets the summary_count value. - [JsonPropertyName("summary_count")] - public long? SummaryCount { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; - /// Gets or sets the updated_at value. - [JsonPropertyName("updated_at")] - public DateTimeOffset? UpdatedAt { get; set; } - - /// Gets or sets the user_named value. - [JsonPropertyName("user_named")] - public bool? UserNamed { get; set; } + /// Output verbosity level to request for supported models. + [JsonPropertyName("verbosity")] + public Verbosity? Verbosity { get; set; } } -/// Current workspace metadata for the session, including its absolute filesystem path when available. +/// Update the session's reasoning effort without changing the selected model. Use `switchTo` instead when you also need to change the model. The runtime stores the effort on the session and applies it to subsequent turns. [Experimental(Diagnostics.Experimental)] -public sealed class WorkspacesGetWorkspaceResult +public sealed class ModelSetReasoningEffortResult { - /// Absolute filesystem path to the workspace directory. Omitted when the session has no workspace (e.g. remote sessions). - [JsonPropertyName("path")] - public string? Path { get; set; } - - /// Current workspace metadata, or null if not available. - [JsonPropertyName("workspace")] - public WorkspacesGetWorkspaceResultWorkspace? Workspace { get; set; } + /// Reasoning effort level recorded on the session after the update. + [JsonPropertyName("reasoningEffort")] + public string ReasoningEffort { get; set; } = string.Empty; } -/// Identifies the target session. +/// Reasoning effort level to apply to the currently selected model. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionWorkspacesGetWorkspaceRequest +internal sealed class ModelSetReasoningEffortRequest { + /// Reasoning effort level to apply to the currently selected model. The host is responsible for validating the value against the model's supported levels before calling. + [JsonPropertyName("reasoningEffort")] + public string ReasoningEffort { get; set; } = string.Empty; + /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Relative paths of files stored in the session workspace files directory. +/// Cost-category metadata for a CAPI model. [Experimental(Diagnostics.Experimental)] -public sealed class WorkspacesListFilesResult +public sealed class SessionModelPriceCategory { - /// Relative file paths in the workspace files directory. - [JsonPropertyName("files")] - public IList Files { get => field ??= []; set; } + /// Gets or sets the id value. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; + + /// Gets or sets the priceCategory value. + [JsonPropertyName("priceCategory")] + public ModelPickerPriceCategory PriceCategory { get; set; } } -/// Identifies the target session. +/// The list of models available to this session. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionWorkspacesListFilesRequest +public sealed class SessionModelList { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Available models, ordered with the most preferred default first. Includes both Copilot (CAPI) models and any registry BYOK models; a BYOK model appears under its provider-qualified selection id (`provider/id`). + [JsonPropertyName("list")] + public IList List { get => field ??= []; set; } + + /// Cost categories for the full CAPI catalog, including picker-disabled models that Auto may select. Metadata only; entries absent from `list` are not manually selectable. + [JsonPropertyName("modelPriceCategories")] + public IList? ModelPriceCategories { get; set; } + + /// Per-quota snapshots returned alongside the model list, keyed by quota type. + [JsonPropertyName("quotaSnapshots")] + public IDictionary? QuotaSnapshots { get; set; } } -/// Contents of the requested workspace file as a UTF-8 string. +/// RPC data type for SessionModelList operations. [Experimental(Diagnostics.Experimental)] -public sealed class WorkspacesReadFileResult +public sealed class SessionModelListRequest { - /// File content as a UTF-8 string. - [JsonPropertyName("content")] - public string Content { get; set; } = string.Empty; + /// If true, bypasses the per-session model list cache and re-fetches from CAPI. + [JsonPropertyName("skipCache")] + public bool? SkipCache { get; set; } } -/// Relative path of the workspace file to read. +/// RPC data type for SessionModelListRequestWithSession operations. [Experimental(Diagnostics.Experimental)] -internal sealed class WorkspacesReadFileRequest +internal sealed class SessionModelListRequestWithSession { - /// Relative path within the workspace files directory. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; - /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; + + /// If true, bypasses the per-session model list cache and re-fetches from CAPI. + [JsonPropertyName("skipCache")] + public bool? SkipCache { get; set; } } -/// Relative path and UTF-8 content for the workspace file to create or overwrite. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class WorkspacesCreateFileRequest +internal sealed class SessionModeGetRequest { - /// File content to write as a UTF-8 string. - [JsonPropertyName("content")] - public string Content { get; set; } = string.Empty; - - /// Relative path within the workspace files directory. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; - /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Workspace checkpoint metadata with assigned number, human-readable title, and checkpoint filename. +/// Agent interaction mode to apply to the session. [Experimental(Diagnostics.Experimental)] -public sealed class WorkspacesCheckpoints +internal sealed class ModeSetRequest { - /// Filename of the checkpoint within the workspace checkpoints directory. - [JsonPropertyName("filename")] - public string Filename { get; set; } = string.Empty; - - /// Checkpoint number assigned by the workspace manager. - [JsonPropertyName("number")] - public long Number { get; set; } + /// The session mode the agent is operating in. + [JsonPropertyName("mode")] + public SessionMode Mode { get; set; } - /// Human-readable checkpoint title. - [JsonPropertyName("title")] - public string Title { get; set; } = string.Empty; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Workspace checkpoints in chronological order; empty when the workspace is not enabled. +/// The session's friendly name, or null when not yet set. [Experimental(Diagnostics.Experimental)] -public sealed class WorkspacesListCheckpointsResult +public sealed class NameGetResult { - /// Workspace checkpoints in chronological order. Empty when workspace is not enabled. - [JsonPropertyName("checkpoints")] - public IList Checkpoints { get => field ??= []; set; } + /// The session name (user-set or auto-generated), or null if not yet set. + [JsonPropertyName("name")] + public string? Name { get; set; } } /// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionWorkspacesListCheckpointsRequest +internal sealed class SessionNameGetRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Checkpoint content as a UTF-8 string, or null when the checkpoint or workspace is missing. +/// New friendly name to apply to the session. [Experimental(Diagnostics.Experimental)] -public sealed class WorkspacesReadCheckpointResult +internal sealed class NameSetRequest { - /// Checkpoint content as a UTF-8 string, or null when the checkpoint or workspace is missing. - [JsonPropertyName("content")] - public string? Content { get; set; } + /// New session name (1–100 characters, trimmed of leading/trailing whitespace). + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] + [MinLength(1)] + [MaxLength(100)] + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Checkpoint number to read. +/// Indicates whether the auto-generated summary was applied as the session's name. [Experimental(Diagnostics.Experimental)] -internal sealed class WorkspacesReadCheckpointRequest +public sealed class NameSetAutoResult { - /// Checkpoint number to read. - [JsonPropertyName("number")] - public long Number { get; set; } + /// Whether the auto-generated summary was persisted. False if the session already has a user-set name, the summary normalized to empty, or the session does not have a workspace. + [JsonPropertyName("applied")] + public bool Applied { get; set; } +} +/// Auto-generated session summary to apply as the session's name when no user-set name exists. +[Experimental(Diagnostics.Experimental)] +internal sealed class NameSetAutoRequest +{ /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; + + /// Auto-generated session summary. Empty/whitespace-only values are ignored; values are trimmed before persisting. + [JsonPropertyName("summary")] + public string Summary { get; set; } = string.Empty; } -/// RPC data type for WorkspacesSaveLargePasteResultSaved operations. -public sealed class WorkspacesSaveLargePasteResultSaved +/// Existence, contents, and resolved path of the session plan file. +[Experimental(Diagnostics.Experimental)] +public sealed class PlanReadResult { - /// Filename within the workspace files directory. - [JsonPropertyName("filename")] - public string Filename { get; set; } = string.Empty; + /// The content of the plan file, or null if it does not exist. + [JsonPropertyName("content")] + public string? Content { get; set; } - /// Absolute filesystem path to the saved paste file. - [JsonPropertyName("filePath")] - public string FilePath { get; set; } = string.Empty; + /// Whether the plan file exists in the workspace. + [JsonPropertyName("exists")] + public bool Exists { get; set; } - /// Size of the saved file in bytes. - [JsonPropertyName("sizeBytes")] - public long SizeBytes { get; set; } + /// Absolute file path of the plan file, or null if workspace is not enabled. + [JsonPropertyName("path")] + public string? Path { get; set; } } -/// Descriptor for the saved paste file, or null when the workspace is unavailable. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -public sealed class WorkspacesSaveLargePasteResult +internal sealed class SessionPlanReadRequest { - /// Saved-paste descriptor, or null when the workspace is unavailable (e.g. CCA runtime, non-infinite sessions, remote sessions). - [JsonPropertyName("saved")] - public WorkspacesSaveLargePasteResultSaved? Saved { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Pasted content to save as a UTF-8 file in the session workspace. +/// Replacement contents to write to the session plan file. [Experimental(Diagnostics.Experimental)] -internal sealed class WorkspacesSaveLargePasteRequest +internal sealed class PlanUpdateRequest { - /// Pasted content to save as a UTF-8 file. + /// The new content for the plan file. [JsonPropertyName("content")] public string Content { get; set; } = string.Empty; @@ -5047,846 +5504,764 @@ internal sealed class WorkspacesSaveLargePasteRequest public string SessionId { get; set; } = string.Empty; } -/// A single changed file and its unified diff. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -public sealed class WorkspaceDiffFileChange +internal sealed class SessionPlanDeleteRequest { - /// Type of change represented by this file diff. - [JsonPropertyName("changeType")] - public WorkspaceDiffFileChangeType ChangeType { get; set; } - - /// Unified diff content for the file. Empty when the diff was truncated. - [JsonPropertyName("diff")] - public string Diff { get; set; } = string.Empty; - - /// Whether the diff content was omitted because it exceeded the per-file size limit. - [JsonPropertyName("isTruncated")] - public bool? IsTruncated { get; set; } - - /// Original file path for renamed files. - [JsonPropertyName("oldPath")] - public string? OldPath { get; set; } - - /// Path to the changed file, relative to the workspace root. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Workspace diff result for the requested mode. +/// A single todo row read from the session SQL `todos` table. All fields are optional because the SQL schema is best-effort and the agent may not have populated every column. [Experimental(Diagnostics.Experimental)] -public sealed class WorkspaceDiffResult +public sealed class PlanSqlTodosRow { - /// Default branch used for a branch diff, when branch mode was requested. - [JsonPropertyName("baseBranch")] - public string? BaseBranch { get; set; } - - /// Changed files and their unified diffs. - [JsonPropertyName("changes")] - public IList Changes { get => field ??= []; set; } + /// Todo description. + [JsonPropertyName("description")] + public string? Description { get; set; } - /// Whether a requested branch diff fell back to unstaged changes because branch diff failed. - [JsonPropertyName("isFallback")] - public bool IsFallback { get; set; } + /// Todo identifier. + [JsonPropertyName("id")] + public string? Id { get; set; } - /// Effective mode used for the returned changes. - [JsonPropertyName("mode")] - public WorkspaceDiffMode Mode { get; set; } + /// Todo status. + [JsonPropertyName("status")] + public string? Status { get; set; } - /// Diff mode requested by the client. - [JsonPropertyName("requestedMode")] - public WorkspaceDiffMode RequestedMode { get; set; } + /// Todo title. + [JsonPropertyName("title")] + public string? Title { get; set; } } -/// Parameters for computing a workspace diff. +/// Todo rows read from the session SQL database. Empty when no session database is available. [Experimental(Diagnostics.Experimental)] -internal sealed class WorkspacesDiffRequest +public sealed class PlanReadSqlTodosResult { - /// When true, ignore whitespace-only changes (git `--ignore-all-space`). Defaults to false. - [JsonPropertyName("ignoreWhitespace")] - public bool? IgnoreWhitespace { get; set; } - - /// Diff mode requested by the client. - [JsonPropertyName("mode")] - public WorkspaceDiffMode Mode { get; set; } + /// Rows from the session SQL todos table, ordered by creation time and id. + [JsonPropertyName("rows")] + public IList Rows { get => field ??= []; set; } +} +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionPlanReadSqlTodosRequest +{ /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Characters that, when typed in the composer, should trigger a `completions.request`. Empty when the session has no host-driven completions (e.g. local sessions, or a relay host that does not advertise `completionTriggerCharacters`). +/// A single dependency edge read from the session SQL `todo_deps` table, indicating that one todo must complete before another. [Experimental(Diagnostics.Experimental)] -public sealed class CompletionsGetTriggerCharactersResult +public sealed class PlanSqlTodoDependency { - /// Trigger characters advertised by the host (e.g. `["@", "#"]`). Empty disables host-driven completions for the session. - [JsonPropertyName("triggerCharacters")] - public IList TriggerCharacters { get => field ??= []; set; } + /// ID of the todo it depends on. + [JsonPropertyName("dependsOn")] + public string DependsOn { get; set; } = string.Empty; + + /// ID of the todo that has the dependency. + [JsonPropertyName("todoId")] + public string TodoId { get; set; } = string.Empty; +} + +/// Todo rows + dependency edges read from the session SQL database. +[Experimental(Diagnostics.Experimental)] +public sealed class PlanReadSqlTodosWithDependenciesResult +{ + /// Edges from the session SQL todo_deps table. Empty when no database, no todo_deps table, or the SELECT failed. Read independently from `rows`, so a broken todo_deps table does not affect the rows result and vice versa. + [JsonPropertyName("dependencies")] + public IList Dependencies { get => field ??= []; set; } + + /// Rows from the session SQL todos table, ordered by creation time and id. Empty when no database, no todos table, or the SELECT failed. + [JsonPropertyName("rows")] + public IList Rows { get => field ??= []; set; } } /// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionCompletionsGetTriggerCharactersRequest +internal sealed class SessionPlanReadSqlTodosWithDependenciesRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// A single host-driven completion. Accepting an item replaces `[rangeStart, rangeEnd)` (UTF-16 code units) in the composer with `insertText`; when the range is absent, the active token around the cursor is replaced. -[Experimental(Diagnostics.Experimental)] -public sealed class SessionCompletionItem +/// RPC data type for WorkspacesGetWorkspaceResultWorkspace operations. +public sealed class WorkspacesGetWorkspaceResultWorkspace { - /// Text spliced into the composer when the item is accepted. - [JsonPropertyName("insertText")] - public string InsertText { get; set; } = string.Empty; + /// Gets or sets the branch value. + [JsonPropertyName("branch")] + public string? Branch { get; set; } - /// Render-kind hint for the picker row (e.g. `"document"`, `"directory"`), derived from the host's display kind. - [JsonPropertyName("kind")] - public string? Kind { get; set; } + /// Gets or sets the chronicle_sync_dismissed value. + [JsonPropertyName("chronicle_sync_dismissed")] + public bool? ChronicleSyncDismissed { get; set; } - /// Primary display label for the picker row. Falls back to `insertText` when absent. - [JsonPropertyName("label")] - public string? Label { get; set; } + /// Gets or sets the client_name value. + [JsonPropertyName("client_name")] + public string? ClientName { get; set; } - /// End (exclusive) of the replacement range in `text`, in UTF-16 code units. - [JsonPropertyName("rangeEnd")] - public long? RangeEnd { get; set; } + /// Gets or sets the created_at value. + [JsonPropertyName("created_at")] + public DateTimeOffset? CreatedAt { get; set; } - /// Start of the replacement range in `text`, in UTF-16 code units. - [JsonPropertyName("rangeStart")] - public long? RangeStart { get; set; } -} + /// Gets or sets the cwd value. + [JsonPropertyName("cwd")] + public string? Cwd { get; set; } -/// Host-driven completion items for the current composer input. Empty when the host returns no items or does not support completions. -[Experimental(Diagnostics.Experimental)] -public sealed class CompletionsRequestResult -{ - /// Completion items in host-ranked order. - [JsonPropertyName("items")] - public IList Items { get => field ??= []; set; } -} + /// Gets or sets the git_root value. + [JsonPropertyName("git_root")] + public string? GitRoot { get; set; } -/// Request host-driven completions for the current composer input. -[Experimental(Diagnostics.Experimental)] -internal sealed class CompletionsRequestRequest -{ - /// Cursor offset within `text`, in UTF-16 code units. - [JsonPropertyName("offset")] - public long Offset { get; set; } + /// Allowed values for the `WorkspacesWorkspaceDetailsHostType` enumeration. + [JsonPropertyName("host_type")] + public WorkspacesWorkspaceDetailsHostType? HostType { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Gets or sets the id value. + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] + [MinLength(1)] + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; - /// The full composed composer input. - [JsonPropertyName("text")] - public string Text { get; set; } = string.Empty; + /// Gets or sets the mc_last_event_id value. + [JsonPropertyName("mc_last_event_id")] + public string? McLastEventId { get; set; } + + /// Gets or sets the mc_session_id value. + [JsonPropertyName("mc_session_id")] + public string? McSessionId { get; set; } + + /// Gets or sets the mc_task_id value. + [JsonPropertyName("mc_task_id")] + public string? McTaskId { get; set; } + + /// Gets or sets the name value. + [JsonPropertyName("name")] + public string? Name { get; set; } + + /// Gets or sets the remote_steerable value. + [JsonPropertyName("remote_steerable")] + public bool? RemoteSteerable { get; set; } + + /// Gets or sets the repository value. + [JsonPropertyName("repository")] + public string? Repository { get; set; } + + /// Gets or sets the summary_count value. + [JsonPropertyName("summary_count")] + public long? SummaryCount { get; set; } + + /// Gets or sets the updated_at value. + [JsonPropertyName("updated_at")] + public DateTimeOffset? UpdatedAt { get; set; } + + /// Gets or sets the user_named value. + [JsonPropertyName("user_named")] + public bool? UserNamed { get; set; } } -/// Instruction sources loaded for the session, in merge order. +/// Current workspace metadata for the session, including its absolute filesystem path when available. [Experimental(Diagnostics.Experimental)] -public sealed class InstructionsGetSourcesResult +public sealed class WorkspacesGetWorkspaceResult { - /// Instruction sources for the session. - [JsonPropertyName("sources")] - public IList Sources { get => field ??= []; set; } + /// Absolute filesystem path to the workspace directory. Omitted when the session has no workspace (e.g. remote sessions). + [JsonPropertyName("path")] + public string? Path { get; set; } + + /// Current workspace metadata, or null if not available. + [JsonPropertyName("workspace")] + public WorkspacesGetWorkspaceResultWorkspace? Workspace { get; set; } } /// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionInstructionsGetSourcesRequest +internal sealed class SessionWorkspacesGetWorkspaceRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Indicates whether fleet mode was successfully activated. +/// Workspace metadata fields to update. [Experimental(Diagnostics.Experimental)] -public sealed class FleetStartResult +internal sealed class WorkspacesUpdateMetadataRequest { - /// Whether fleet mode was successfully activated. - [JsonPropertyName("started")] - public bool Started { get; set; } -} + /// Opaque workspace context supplied by the session host. + [JsonPropertyName("context")] + public JsonElement? Context { get; set; } -/// Optional user prompt to combine with the fleet orchestration instructions. -[Experimental(Diagnostics.Experimental)] -internal sealed class FleetStartRequest -{ - /// Optional user prompt to combine with fleet instructions. - [JsonPropertyName("prompt")] - public string? Prompt { get; set; } + /// Optional workspace display name override. + [JsonPropertyName("name")] + public string? Name { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Custom agents available to the session. +/// Optional session context used when creating a local workspace. [Experimental(Diagnostics.Experimental)] -public sealed class AgentList +internal sealed class WorkspacesEnsureRequest { - /// Available custom agents. - [JsonPropertyName("agents")] - public IList Agents { get => field ??= []; set; } -} + /// Opaque workspace context supplied by the session host. + [JsonPropertyName("context")] + public JsonElement? Context { get; set; } -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionAgentListRequest -{ /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// The currently selected custom agent, or null when using the default agent. +/// Relative paths of files stored in the session workspace files directory. [Experimental(Diagnostics.Experimental)] -public sealed class AgentGetCurrentResult +public sealed class WorkspacesListFilesResult { - /// Currently selected custom agent, or null if using the default agent. - [JsonPropertyName("agent")] - public AgentInfo? Agent { get; set; } + /// Relative file paths in the workspace files directory. + [JsonPropertyName("files")] + public IList Files { get => field ??= []; set; } } /// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionAgentGetCurrentRequest +internal sealed class SessionWorkspacesListFilesRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// The newly selected custom agent. +/// Contents of the requested workspace file as a UTF-8 string. [Experimental(Diagnostics.Experimental)] -public sealed class AgentSelectResult +public sealed class WorkspacesReadFileResult { - /// The newly selected custom agent. - [JsonPropertyName("agent")] - public AgentInfo Agent { get => field ??= new(); set; } + /// File content as a UTF-8 string. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; } -/// Name of the custom agent to select for subsequent turns. +/// Relative path of the workspace file to read. [Experimental(Diagnostics.Experimental)] -internal sealed class AgentSelectRequest +internal sealed class WorkspacesReadFileRequest { - /// Name of the custom agent to select. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Relative path within the workspace files directory. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Identifies the target session. +/// Relative path and UTF-8 content for the workspace file to create or overwrite. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionAgentDeselectRequest +internal sealed class WorkspacesCreateFileRequest { + /// File content to write as a UTF-8 string. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; + + /// Relative path within the workspace files directory. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; + /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Custom agents available to the session after reloading definitions from disk. +/// Workspace checkpoint metadata with assigned number, human-readable title, and checkpoint filename. [Experimental(Diagnostics.Experimental)] -public sealed class AgentReloadResult +public sealed class WorkspacesCheckpoints { - /// Reloaded custom agents. - [JsonPropertyName("agents")] - public IList Agents { get => field ??= []; set; } + /// Filename of the checkpoint within the workspace checkpoints directory. + [JsonPropertyName("filename")] + public string Filename { get; set; } = string.Empty; + + /// Checkpoint number assigned by the workspace manager. + [JsonPropertyName("number")] + public long Number { get; set; } + + /// Human-readable checkpoint title. + [JsonPropertyName("title")] + public string Title { get; set; } = string.Empty; } -/// Identifies the target session. +/// Workspace checkpoints in chronological order; empty when the workspace is not enabled. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionAgentReloadRequest +public sealed class WorkspacesListCheckpointsResult +{ + /// Workspace checkpoints in chronological order. Empty when workspace is not enabled. + [JsonPropertyName("checkpoints")] + public IList Checkpoints { get => field ??= []; set; } +} + +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionWorkspacesListCheckpointsRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Identifier assigned to the newly started background agent task. +/// Checkpoint content as a UTF-8 string, or null when the checkpoint or workspace is missing. [Experimental(Diagnostics.Experimental)] -public sealed class TasksStartAgentResult +public sealed class WorkspacesReadCheckpointResult { - /// Generated agent ID for the background task. - [JsonPropertyName("agentId")] - public string AgentId { get; set; } = string.Empty; + /// Checkpoint content as a UTF-8 string, or null when the checkpoint or workspace is missing. + [JsonPropertyName("content")] + public string? Content { get; set; } } -/// Agent type, prompt, name, and optional description and model override for the new task. +/// Checkpoint number to read. [Experimental(Diagnostics.Experimental)] -internal sealed class TasksStartAgentRequest +internal sealed class WorkspacesReadCheckpointRequest { - /// Type of agent to start (e.g., 'explore', 'task', 'general-purpose'). - [JsonPropertyName("agentType")] - public string AgentType { get; set; } = string.Empty; - - /// Short description of the task. - [JsonPropertyName("description")] - public string? Description { get; set; } - - /// Optional model override. - [JsonPropertyName("model")] - public string? Model { get; set; } - - /// Short name for the agent, used to generate a human-readable ID. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; - - /// Task prompt for the agent. - [JsonPropertyName("prompt")] - public string Prompt { get; set; } = string.Empty; + /// Checkpoint number to read. + [JsonPropertyName("number")] + public long Number { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Tracked task union returned by task APIs, containing either an agent task or a shell task. -/// Polymorphic base type discriminated by type. -[Experimental(Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "type", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(TaskInfoAgent), "agent")] -[JsonDerivedType(typeof(TaskInfoShell), "shell")] -public partial class TaskInfo +/// RPC data type for WorkspacesAddSummaryResultSummary operations. +public sealed class WorkspacesAddSummaryResultSummary { - /// The type discriminator. - [JsonPropertyName("type")] - public virtual string Type { get; set; } = string.Empty; } +/// RPC data type for WorkspacesAddSummaryResultWorkspace operations. +public sealed class WorkspacesAddSummaryResultWorkspace +{ +} -/// Tracked background agent task metadata, including IDs, status, timing, agent type, prompt, model, result, and latest response. -/// The agent variant of . +/// Persisted summary metadata and refreshed workspace metadata. [Experimental(Diagnostics.Experimental)] -public partial class TaskInfoAgent : TaskInfo +public sealed class WorkspacesAddSummaryResult { - /// - [JsonIgnore] - public override string Type => "agent"; - - /// ISO 8601 timestamp when the current active period began. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("activeStartedAt")] - public DateTimeOffset? ActiveStartedAt { get; set; } - - /// Accumulated active execution time in milliseconds. - [JsonConverter(typeof(MillisecondsTimeSpanConverter))] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("activeTimeMs")] - public TimeSpan? ActiveTime { get; set; } - - /// Type of agent running this task. - [JsonPropertyName("agentType")] - public required string AgentType { get; set; } - - /// Whether the task is currently in the original sync wait and can be moved to background mode. False once it is already backgrounded, idle, finished, or no longer has a promotable sync waiter. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("canPromoteToBackground")] - public bool? CanPromoteToBackground { get; set; } - - /// ISO 8601 timestamp when the task finished. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("completedAt")] - public DateTimeOffset? CompletedAt { get; set; } - - /// Short description of the task. - [JsonPropertyName("description")] - public required string Description { get; set; } - - /// Error message when the task failed. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("error")] - public string? Error { get; set; } - - /// Whether task execution is synchronously awaited or managed in the background. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("executionMode")] - public TaskExecutionMode? ExecutionMode { get; set; } - - /// Unique task identifier. - [JsonPropertyName("id")] - public required string Id { get; set; } - - /// ISO 8601 timestamp when the agent entered idle state. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("idleSince")] - public DateTimeOffset? IdleSince { get; set; } - - /// Most recent response text from the agent. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("latestResponse")] - public string? LatestResponse { get; set; } - - /// Requested model override for the task when specified. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("model")] - public string? Model { get; set; } - - /// Most recent prompt delivered to the agent. Updated whenever the agent receives a follow-up message. - [JsonPropertyName("prompt")] - public required string Prompt { get; set; } - - /// Runtime model resolved for the task when available. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("resolvedModel")] - public string? ResolvedModel { get; set; } - - /// Result text from the task when available. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("result")] - public string? Result { get; set; } - - /// ISO 8601 timestamp when the task was started. - [JsonPropertyName("startedAt")] - public required DateTimeOffset StartedAt { get; set; } - - /// Current lifecycle status of the task. - [JsonPropertyName("status")] - public required TaskStatus Status { get; set; } + /// Gets or sets the summary value. + [JsonPropertyName("summary")] + public WorkspacesAddSummaryResultSummary? Summary { get; set; } - /// Tool call ID associated with this agent task. - [JsonPropertyName("toolCallId")] - public required string ToolCallId { get; set; } + /// Gets or sets the workspace value. + [JsonPropertyName("workspace")] + public WorkspacesAddSummaryResultWorkspace? Workspace { get; set; } } -/// Tracked shell task metadata, including ID, command, status, timing, attachment/execution mode, log path, and PID. -/// The shell variant of . +/// Compaction summary checkpoint to persist. [Experimental(Diagnostics.Experimental)] -public partial class TaskInfoShell : TaskInfo +internal sealed class WorkspacesAddSummaryRequest { - /// - [JsonIgnore] - public override string Type => "shell"; - - /// Whether the shell runs inside a managed PTY session or as an independent background process. - [JsonPropertyName("attachmentMode")] - public required TaskShellInfoAttachmentMode AttachmentMode { get; set; } - - /// Whether this shell task can be promoted to background mode. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("canPromoteToBackground")] - public bool? CanPromoteToBackground { get; set; } - - /// Command being executed. - [JsonPropertyName("command")] - public required string Command { get; set; } - - /// ISO 8601 timestamp when the task finished. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("completedAt")] - public DateTimeOffset? CompletedAt { get; set; } - - /// Short description of the task. - [JsonPropertyName("description")] - public required string Description { get; set; } - - /// Whether task execution is synchronously awaited or managed in the background. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("executionMode")] - public TaskExecutionMode? ExecutionMode { get; set; } - - /// Unique task identifier. - [JsonPropertyName("id")] - public required string Id { get; set; } + /// Markdown summary content to persist. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; - /// Path to the detached shell log, when available. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("logPath")] - public string? LogPath { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; - /// Process ID when available. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("pid")] - public long? Pid { get; set; } + /// Summary title shown in checkpoint listings. + [JsonPropertyName("title")] + public string Title { get; set; } = string.Empty; +} - /// ISO 8601 timestamp when the task was started. - [JsonPropertyName("startedAt")] - public required DateTimeOffset StartedAt { get; set; } +/// Rollback point for local workspace summaries. +[Experimental(Diagnostics.Experimental)] +internal sealed class WorkspacesTruncateSummariesRequest +{ + /// Number of newest summaries to keep. + [JsonPropertyName("keepCount")] + public long KeepCount { get; set; } - /// Current lifecycle status of the task. - [JsonPropertyName("status")] - public required TaskStatus Status { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Background tasks currently tracked by the session. +/// Autopilot objective file content, or null when missing. [Experimental(Diagnostics.Experimental)] -public sealed class TaskList +public sealed class WorkspacesReadAutopilotObjectiveResult { - /// Currently tracked tasks. - [JsonPropertyName("tasks")] - public IList Tasks { get => field ??= []; set; } + /// Autopilot objective file content, or null when missing. + [JsonPropertyName("content")] + public string? Content { get; set; } } /// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionTasksListRequest +internal sealed class SessionWorkspacesReadAutopilotObjectiveRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Refresh metadata for any detached background shells the runtime knows about. Use after a long pause to pick up exit/output state for shells running outside the agent loop. +/// Result of writing the autopilot objective file. [Experimental(Diagnostics.Experimental)] -public sealed class TasksRefreshResult +public sealed class WorkspacesWriteAutopilotObjectiveResult { + /// Filesystem operation performed. + [JsonPropertyName("operation")] + public string Operation { get; set; } = string.Empty; } -/// Identifies the target session. +/// Autopilot objective file content to persist. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionTasksRefreshRequest +internal sealed class WorkspacesWriteAutopilotObjectiveRequest { + /// Autopilot objective file content. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; + /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Wait until all in-flight background tasks (agents + shells) and any follow-up turns scheduled by their completions have settled. Returns when the runtime is fully drained or after an internal timeout (default 10 minutes; configurable via COPILOT_TASK_WAIT_TIMEOUT_SECONDS). +/// Result of deleting the autopilot objective file. [Experimental(Diagnostics.Experimental)] -public sealed class TasksWaitForPendingResult +public sealed class WorkspacesDeleteAutopilotObjectiveResult { + /// True when a file was deleted. + [JsonPropertyName("deleted")] + public bool Deleted { get; set; } } /// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionTasksWaitForPendingRequest +internal sealed class SessionWorkspacesDeleteAutopilotObjectiveRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Polymorphic base type discriminated by type. -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "type", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(TasksGetProgressResultProgressAgent), "agent")] -[JsonDerivedType(typeof(TasksGetProgressResultProgressShell), "shell")] -public partial class TasksGetProgressResultProgress +/// Whether the autopilot objective file exists. +[Experimental(Diagnostics.Experimental)] +public sealed class WorkspacesAutopilotObjectiveExistsResult { - /// The type discriminator. - [JsonPropertyName("type")] - public virtual string Type { get; set; } = string.Empty; + /// True when the objective file exists. + [JsonPropertyName("exists")] + public bool Exists { get; set; } } - -/// Timestamped display line for task progress output or recent agent activity. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -public sealed class TaskProgressLine +internal sealed class SessionWorkspacesAutopilotObjectiveExistsRequest { - /// Display message, e.g., "▸ bash", "✓ edit src/foo.ts". - [JsonPropertyName("message")] - public string Message { get; set; } = string.Empty; - - /// ISO 8601 timestamp when this event occurred. - [JsonPropertyName("timestamp")] - public DateTimeOffset Timestamp { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Progress snapshot for an agent task, with recent activity lines and optional latest intent. -/// The agent variant of . -public partial class TasksGetProgressResultProgressAgent : TasksGetProgressResultProgress +/// RPC data type for WorkspacesSaveLargePasteResultSaved operations. +public sealed class WorkspacesSaveLargePasteResultSaved { - /// - [JsonIgnore] - public override string Type => "agent"; + /// Filename within the workspace files directory. + [JsonPropertyName("filename")] + public string Filename { get; set; } = string.Empty; - /// The most recent intent reported by the agent. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("latestIntent")] - public string? LatestIntent { get; set; } + /// Absolute filesystem path to the saved paste file. + [JsonPropertyName("filePath")] + public string FilePath { get; set; } = string.Empty; - /// Recent tool execution events converted to display lines. - [JsonPropertyName("recentActivity")] - public required IList RecentActivity { get; set; } + /// Size of the saved file in bytes. + [JsonPropertyName("sizeBytes")] + public long SizeBytes { get; set; } } -/// Progress snapshot for a shell task, with recent stdout/stderr output and optional process ID. -/// The shell variant of . -public partial class TasksGetProgressResultProgressShell : TasksGetProgressResultProgress +/// Descriptor for the saved paste file, or null when the workspace is unavailable. +[Experimental(Diagnostics.Experimental)] +public sealed class WorkspacesSaveLargePasteResult { - /// - [JsonIgnore] - public override string Type => "shell"; + /// Saved-paste descriptor, or null when the workspace is unavailable (e.g. CCA runtime, non-infinite sessions, remote sessions). + [JsonPropertyName("saved")] + public WorkspacesSaveLargePasteResultSaved? Saved { get; set; } +} - /// Process ID when available. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("pid")] - public long? Pid { get; set; } +/// Pasted content to save as a UTF-8 file in the session workspace. +[Experimental(Diagnostics.Experimental)] +internal sealed class WorkspacesSaveLargePasteRequest +{ + /// Pasted content to save as a UTF-8 file. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; - /// Recent stdout/stderr lines from the running shell command. - [JsonPropertyName("recentOutput")] - public required string RecentOutput { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Progress information for the task, or null when no task with that ID is tracked. +/// A single changed file and its unified diff. [Experimental(Diagnostics.Experimental)] -public sealed class TasksGetProgressResult +public sealed class WorkspaceDiffFileChange { - /// Progress information for the task, discriminated by type. Returns null when no task with this ID is currently tracked. - [JsonPropertyName("progress")] - public TasksGetProgressResultProgress? Progress { get; set; } + /// Type of change represented by this file diff. + [JsonPropertyName("changeType")] + public WorkspaceDiffFileChangeType ChangeType { get; set; } + + /// Unified diff content for the file. Empty when the diff was truncated. + [JsonPropertyName("diff")] + public string Diff { get; set; } = string.Empty; + + /// Whether the diff content was omitted because it exceeded the per-file size limit. + [JsonPropertyName("isTruncated")] + public bool? IsTruncated { get; set; } + + /// Original file path for renamed files. + [JsonPropertyName("oldPath")] + public string? OldPath { get; set; } + + /// Path to the changed file, relative to the workspace root when the file lives under it. A file changed outside the workspace root keeps a `../`-relative path, or an absolute path when no relative path exists (for example a different Windows drive). + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; } -/// Identifier of the background task to fetch progress for. +/// Workspace diff result for the requested mode. [Experimental(Diagnostics.Experimental)] -internal sealed class TasksGetProgressRequest +public sealed class WorkspaceDiffResult { - /// Task identifier (agent ID or shell ID). - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// Default branch used for a branch diff, when branch mode was requested. + [JsonPropertyName("baseBranch")] + public string? BaseBranch { get; set; } + + /// Changed files and their unified diffs. + [JsonPropertyName("changes")] + public IList Changes { get => field ??= []; set; } + + /// Whether the requested diff fell back to unstaged changes, either because branch diff failed or session diff was unavailable. + [JsonPropertyName("isFallback")] + public bool IsFallback { get; set; } + + /// Effective mode used for the returned changes. + [JsonPropertyName("mode")] + public WorkspaceDiffMode Mode { get; set; } + + /// Diff mode requested by the client. + [JsonPropertyName("requestedMode")] + public WorkspaceDiffMode RequestedMode { get; set; } + + /// Why the session diff could not be produced, when applicable. Set only when `session` mode was requested and `isFallback` is true, so a client can tell the permanent `file-change-tracking-disabled` apart from the transient `session-busy`, which the same request answers once the session settles. Never set for `unstaged` or `branch` mode, and never `unsupported-remote-session`: a remote session's captures live on its own host, so a `session`-mode diff is rejected for one rather than answered with a controller-side fallback. + [JsonPropertyName("unavailableReason")] + public HistoryRewindUnavailableReason? UnavailableReason { get; set; } +} + +/// Parameters for computing a workspace diff. +[Experimental(Diagnostics.Experimental)] +internal sealed class WorkspacesDiffRequest +{ + /// When true, ignore whitespace-only changes (git `--ignore-all-space`). Defaults to false. + [JsonPropertyName("ignoreWhitespace")] + public bool? IgnoreWhitespace { get; set; } + + /// Diff mode requested by the client. + [JsonPropertyName("mode")] + public WorkspaceDiffMode Mode { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// The first sync-waiting task that can currently be promoted to background mode. +/// Characters that, when typed in the composer, should trigger a `completions.request`. Empty when the session has no host-driven completions (e.g. local sessions, or a relay host that does not advertise `completionTriggerCharacters`). [Experimental(Diagnostics.Experimental)] -public sealed class TasksGetCurrentPromotableResult +public sealed class CompletionsGetTriggerCharactersResult { - /// The first sync-waiting task (agent first, then shell) that can currently be promoted to background mode. Omitted if no such task exists. The returned task is guaranteed to have executionMode='sync' and canPromoteToBackground=true at the time of the call. - [JsonPropertyName("task")] - public TaskInfo? Task { get; set; } + /// Trigger characters advertised by the host (e.g. `["@", "#"]`). Empty disables host-driven completions for the session. + [JsonPropertyName("triggerCharacters")] + public IList TriggerCharacters { get => field ??= []; set; } } /// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionTasksGetCurrentPromotableRequest +internal sealed class SessionCompletionsGetTriggerCharactersRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Indicates whether the task was successfully promoted to background mode. +/// A single host-driven completion. Accepting an item replaces `[rangeStart, rangeEnd)` (UTF-16 code units) in the composer with `insertText`; when the range is absent, the active token around the cursor is replaced. [Experimental(Diagnostics.Experimental)] -public sealed class TasksPromoteToBackgroundResult +public sealed class SessionCompletionItem { - /// Whether the task was successfully promoted to background mode. - [JsonPropertyName("promoted")] - public bool Promoted { get; set; } + /// Text spliced into the composer when the item is accepted. + [JsonPropertyName("insertText")] + public string InsertText { get; set; } = string.Empty; + + /// Render-kind hint for the picker row (e.g. `"document"`, `"directory"`), derived from the host's display kind. + [JsonPropertyName("kind")] + public string? Kind { get; set; } + + /// Primary display label for the picker row. Falls back to `insertText` when absent. + [JsonPropertyName("label")] + public string? Label { get; set; } + + /// End (exclusive) of the replacement range in `text`, in UTF-16 code units. + [JsonPropertyName("rangeEnd")] + public long? RangeEnd { get; set; } + + /// Start of the replacement range in `text`, in UTF-16 code units. + [JsonPropertyName("rangeStart")] + public long? RangeStart { get; set; } } -/// Identifier of the task to promote to background mode. +/// Host-driven completion items for the current composer input. Empty when the host returns no items or does not support completions. [Experimental(Diagnostics.Experimental)] -internal sealed class TasksPromoteToBackgroundRequest +public sealed class CompletionsRequestResult { - /// Task identifier. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// Completion items in host-ranked order. + [JsonPropertyName("items")] + public IList Items { get => field ??= []; set; } +} + +/// Request host-driven completions for the current composer input. +[Experimental(Diagnostics.Experimental)] +internal sealed class CompletionsRequestRequest +{ + /// Cursor offset within `text`, in UTF-16 code units. + [JsonPropertyName("offset")] + public long Offset { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; + + /// The full composed composer input. + [JsonPropertyName("text")] + public string Text { get; set; } = string.Empty; } -/// The promoted task as it now exists in background mode, omitted if no promotable task was waiting. +/// Instruction sources loaded for the session, in merge order. [Experimental(Diagnostics.Experimental)] -public sealed class TasksPromoteCurrentToBackgroundResult +public sealed class InstructionsGetSourcesResult { - /// The promoted task as it now exists in background mode, omitted if no promotable task was waiting. Atomic operation: avoids the race window of getCurrentPromotable + promoteToBackground. - [JsonPropertyName("task")] - public TaskInfo? Task { get; set; } + /// Instruction sources for the session. + [JsonPropertyName("sources")] + public IList Sources { get => field ??= []; set; } } /// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionTasksPromoteCurrentToBackgroundRequest +internal sealed class SessionInstructionsGetSourcesRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Indicates whether the background task was successfully cancelled. +/// Indicates whether fleet mode was successfully activated. [Experimental(Diagnostics.Experimental)] -public sealed class TasksCancelResult +public sealed class FleetStartResult { - /// Whether the task was successfully cancelled. - [JsonPropertyName("cancelled")] - public bool Cancelled { get; set; } + /// Whether fleet mode was successfully activated. + [JsonPropertyName("started")] + public bool Started { get; set; } } -/// Identifier of the background task to cancel. +/// Optional user prompt to combine with the fleet orchestration instructions. [Experimental(Diagnostics.Experimental)] -internal sealed class TasksCancelRequest +internal sealed class FleetStartRequest { - /// Task identifier. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// Optional user prompt to combine with fleet instructions. + [JsonPropertyName("prompt")] + public string? Prompt { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Indicates whether the task was removed. False when the task does not exist or is still running/idle. +/// Agents available to the session. [Experimental(Diagnostics.Experimental)] -public sealed class TasksRemoveResult +public sealed class AgentList { - /// Whether the task was removed. Returns false if the task does not exist or is still running/idle (cancel it first). - [JsonPropertyName("removed")] - public bool Removed { get; set; } + /// Available agents. + [JsonPropertyName("agents")] + public IList Agents { get => field ??= []; set; } } -/// Identifier of the completed or cancelled task to remove from tracking. +/// RPC data type for SessionAgentList operations. [Experimental(Diagnostics.Experimental)] -internal sealed class TasksRemoveRequest +public sealed class SessionAgentListRequest { - /// Task identifier. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// When true, request the session's configured built-in agents alongside custom agents. Listing applies feature, context, inclusion, exclusion, and user-disabled-agent policy, but does not evaluate transient invocation requirements such as model availability. Built-in metadata may be omitted when the session cannot project it, such as a relay session. + [JsonPropertyName("includeBuiltInAgents")] + public bool? IncludeBuiltInAgents { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// When true, request authored base prompt text on each AgentInfo. Prompt text may be omitted when unavailable, such as for agents projected through a relay session. + [JsonPropertyName("includePrompt")] + public bool? IncludePrompt { get; set; } } -/// Indicates whether the message was delivered, with an error message when delivery failed. +/// RPC data type for SessionAgentListRequestWithSession operations. [Experimental(Diagnostics.Experimental)] -public sealed class TasksSendMessageResult +internal sealed class SessionAgentListRequestWithSession { - /// Error message if delivery failed. - [JsonPropertyName("error")] - public string? Error { get; set; } + /// When true, request the session's configured built-in agents alongside custom agents. Listing applies feature, context, inclusion, exclusion, and user-disabled-agent policy, but does not evaluate transient invocation requirements such as model availability. Built-in metadata may be omitted when the session cannot project it, such as a relay session. + [JsonPropertyName("includeBuiltInAgents")] + public bool? IncludeBuiltInAgents { get; set; } - /// Whether the message was successfully delivered or steered. - [JsonPropertyName("sent")] - public bool Sent { get; set; } + /// When true, request authored base prompt text on each AgentInfo. Prompt text may be omitted when unavailable, such as for agents projected through a relay session. + [JsonPropertyName("includePrompt")] + public bool? IncludePrompt { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Identifier of the target agent task, message content, and optional sender agent ID. +/// An in-memory authored prompt override for an available agent. [Experimental(Diagnostics.Experimental)] -internal sealed class TasksSendMessageRequest +internal sealed class AgentSetPromptRequest { - /// Agent ID of the sender, if sent on behalf of another agent. - [JsonPropertyName("fromAgentId")] - public string? FromAgentId { get; set; } - - /// Agent task identifier. + /// Stable effective agent id. Plugin namespace separators are normalized. [JsonPropertyName("id")] public string Id { get; set; } = string.Empty; - /// Message content to send to the agent. - [JsonPropertyName("message")] - public string Message { get; set; } = string.Empty; + /// Replacement authored prompt. Empty text is valid. + [JsonPropertyName("prompt")] + public string Prompt { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Skill metadata available to a session, with name, description, source, enabled/invocable state, path, plugin, and argument hint. -[Experimental(Diagnostics.Experimental)] -public sealed class Skill -{ - /// Optional freeform hint describing the skill's expected arguments, from the `argument-hint` frontmatter field. - [JsonPropertyName("argumentHint")] - public string? ArgumentHint { get; set; } - - /// Description of what the skill does. - [JsonPropertyName("description")] - public string Description { get; set; } = string.Empty; - - /// Whether the skill is currently enabled. - [JsonPropertyName("enabled")] - public bool Enabled { get; set; } - - /// Unique identifier for the skill. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; - - /// Absolute path to the skill file. - [JsonPropertyName("path")] - public string? Path { get; set; } - - /// Name of the plugin that provides the skill, when source is 'plugin'. - [JsonPropertyName("pluginName")] - public string? PluginName { get; set; } - - /// Source location type (e.g., project, personal-copilot, plugin, builtin). - [JsonPropertyName("source")] - public SkillSource Source { get; set; } - - /// Whether the skill can be invoked by the user as a slash command. - [JsonPropertyName("userInvocable")] - public bool UserInvocable { get; set; } -} - -/// Skills available to the session, with their enabled state. +/// The currently selected custom agent, or null when using the default agent. [Experimental(Diagnostics.Experimental)] -public sealed class SkillList +public sealed class AgentGetCurrentResult { - /// Available skills. - [JsonPropertyName("skills")] - public IList Skills { get => field ??= []; set; } + /// Currently selected custom agent, or null if using the default agent. + [JsonPropertyName("agent")] + public AgentInfo? Agent { get; set; } } /// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionSkillsListRequest +internal sealed class SessionAgentGetCurrentRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Skill invocation record with name, path, content, allowed tools, and turn number. -[Experimental(Diagnostics.Experimental)] -public sealed class SkillsInvokedSkill -{ - /// Tools that should be auto-approved when this skill is active, captured at invocation time. - [JsonPropertyName("allowedTools")] - public IList? AllowedTools { get; set; } - - /// Full content of the skill file. - [JsonPropertyName("content")] - public string Content { get; set; } = string.Empty; - - /// Turn number when the skill was invoked. - [JsonPropertyName("invokedAtTurn")] - public long InvokedAtTurn { get; set; } - - /// Unique identifier for the skill. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; - - /// Path to the SKILL.md file. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; -} - -/// Skills invoked during this session, ordered by invocation time (most recent last). -[Experimental(Diagnostics.Experimental)] -public sealed class SkillsGetInvokedResult -{ - /// Skills invoked during this session, ordered by invocation time (most recent last). - [JsonPropertyName("skills")] - public IList Skills { get => field ??= []; set; } -} - -/// Identifies the target session. +/// The newly selected custom agent. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionSkillsGetInvokedRequest +public sealed class AgentSelectResult { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// The newly selected custom agent. + [JsonPropertyName("agent")] + public AgentInfo Agent { get => field ??= new(); set; } } -/// Name of the skill to enable for the session. +/// Name of the custom agent to select for subsequent turns. [Experimental(Diagnostics.Experimental)] -internal sealed class SkillsEnableRequest +internal sealed class AgentSelectRequest { - /// Name of the skill to enable. + /// Name of the custom agent to select. [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; @@ -5895,797 +6270,821 @@ internal sealed class SkillsEnableRequest public string SessionId { get; set; } = string.Empty; } -/// Name of the skill to disable for the session. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class SkillsDisableRequest +internal sealed class SessionAgentDeselectRequest { - /// Name of the skill to disable. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; - /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Diagnostics from reloading skill definitions, with warnings and errors as separate lists. +/// Custom agents available to the session after reloading definitions from disk. [Experimental(Diagnostics.Experimental)] -public sealed class SkillsLoadDiagnostics +public sealed class AgentReloadResult { - /// Errors emitted while loading skills (e.g. skills that failed to load entirely). - [JsonPropertyName("errors")] - public IList Errors { get => field ??= []; set; } - - /// Warnings emitted while loading skills (e.g. skills that loaded but had issues). - [JsonPropertyName("warnings")] - public IList Warnings { get => field ??= []; set; } + /// Reloaded custom agents. + [JsonPropertyName("agents")] + public IList Agents { get => field ??= []; set; } } /// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionSkillsReloadRequest +internal sealed class SessionAgentReloadRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Identifies the target session. +/// Identifier assigned to the newly started background agent task. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionSkillsEnsureLoadedRequest +public sealed class TasksStartAgentResult { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Generated agent ID for the background task. + [JsonPropertyName("agentId")] + public string AgentId { get; set; } = string.Empty; } -/// Recorded MCP server connection failure. +/// Agent type, prompt, name, and optional description and model override for the new task. [Experimental(Diagnostics.Experimental)] -public sealed class McpServerFailureInfo +internal sealed class TasksStartAgentRequest { - /// Failure message produced when the MCP server connection failed. - [JsonPropertyName("message")] - public string Message { get; set; } = string.Empty; + /// Type of agent to start (e.g., 'explore', 'task', 'general-purpose'). + [JsonPropertyName("agentType")] + public string AgentType { get; set; } = string.Empty; - /// epoch-ms timestamp at which the failure was recorded. - [JsonPropertyName("timestamp")] - public long Timestamp { get; set; } + /// Short description of the task. + [JsonPropertyName("description")] + public string? Description { get; set; } + + /// Optional model override. + [JsonPropertyName("model")] + public string? Model { get; set; } + + /// Short name for the agent, used to generate a human-readable ID. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Task prompt for the agent. + [JsonPropertyName("prompt")] + public string Prompt { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Recorded MCP server pending-auth state. +/// Tracked task union returned by task APIs, containing either an agent task or a shell task. +/// Polymorphic base type discriminated by type. [Experimental(Diagnostics.Experimental)] -public sealed class McpServerNeedsAuthInfo +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "type", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(TaskInfoAgent), "agent")] +[JsonDerivedType(typeof(TaskInfoShell), "shell")] +public partial class TaskInfo { - /// epoch-ms timestamp at which the server signalled it needs authentication. - [JsonPropertyName("timestamp")] - public long Timestamp { get; set; } + /// The type discriminator. + [JsonPropertyName("type")] + public virtual string Type { get; set; } = string.Empty; } -/// Host-level state, omitted when no MCP host is initialized. + +/// Tracked background agent task metadata, including IDs, status, timing, agent type, prompt, model, result, and latest response. +/// The agent variant of . [Experimental(Diagnostics.Experimental)] -public sealed class McpHostState +public partial class TaskInfoAgent : TaskInfo { - /// Names of currently-connected MCP clients. - [JsonPropertyName("clients")] - public IList Clients { get => field ??= []; set; } + /// + [JsonIgnore] + public override string Type => "agent"; - /// Configured servers that are explicitly disabled. - [JsonPropertyName("disabledServers")] - public IList DisabledServers { get => field ??= []; set; } + /// ISO 8601 timestamp when the current active period began. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("activeStartedAt")] + public DateTimeOffset? ActiveStartedAt { get; set; } - /// Map of server name to recorded connection failure. - [JsonPropertyName("failedServers")] - public IDictionary FailedServers { get => field ??= new Dictionary(); set; } + /// Accumulated active execution time in milliseconds. + [JsonConverter(typeof(MillisecondsTimeSpanConverter))] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("activeTimeMs")] + public TimeSpan? ActiveTime { get; set; } - /// Configured servers filtered out by enterprise allowlist policy. - [JsonPropertyName("filteredServers")] - public IList FilteredServers { get => field ??= []; set; } + /// Type of agent running this task. + [JsonPropertyName("agentType")] + public required string AgentType { get; set; } - /// Whether third-party MCP servers are policy-enabled for this session. - [JsonPropertyName("mcp3pEnabled")] - public bool Mcp3pEnabled { get; set; } + /// Whether the task is currently in the original sync wait and can be moved to background mode. False once it is already backgrounded, idle, finished, or no longer has a promotable sync waiter. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("canPromoteToBackground")] + public bool? CanPromoteToBackground { get; set; } - /// Map of server name to recorded pending-auth state. - [JsonPropertyName("needsAuthServers")] - public IDictionary NeedsAuthServers { get => field ??= new Dictionary(); set; } + /// ISO 8601 timestamp when the task finished. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("completedAt")] + public DateTimeOffset? CompletedAt { get; set; } - /// Names of servers with in-flight connection attempts. - [JsonPropertyName("pendingConnections")] - public IList PendingConnections { get => field ??= []; set; } -} + /// Short description of the task. + [JsonPropertyName("description")] + public required string Description { get; set; } -/// MCP server status entry, including config source/plugin source and any connection error. -[Experimental(Diagnostics.Experimental)] -public sealed class McpServer -{ - /// Error message if the server failed to connect. + /// Error message when the task failed. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("error")] public string? Error { get; set; } - /// Server name (config key). - [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] - [MinLength(1)] - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; - - /// Configuration source: user, workspace, plugin, or builtin. - [JsonPropertyName("source")] - public McpServerSource? Source { get; set; } + /// Whether task execution is synchronously awaited or managed in the background. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("executionMode")] + public TaskExecutionMode? ExecutionMode { get; set; } - /// Plugin name that provided this server, when source is plugin. - [JsonPropertyName("sourcePlugin")] - public string? SourcePlugin { get; set; } + /// Unique task identifier. + [JsonPropertyName("id")] + public required string Id { get; set; } - /// Plugin version that provided this server, when source is plugin. - [JsonPropertyName("sourcePluginVersion")] - public string? SourcePluginVersion { get; set; } + /// ISO 8601 timestamp when the agent entered idle state. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("idleSince")] + public DateTimeOffset? IdleSince { get; set; } + + /// Most recent response text from the agent. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("latestResponse")] + public string? LatestResponse { get; set; } + + /// Requested model override for the task when specified. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("model")] + public string? Model { get; set; } + + /// Most recent prompt delivered to the agent. Updated whenever the agent receives a follow-up message. + [JsonPropertyName("prompt")] + public required string Prompt { get; set; } + + /// Runtime model resolved for the task when available. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("resolvedModel")] + public string? ResolvedModel { get; set; } + + /// Result text from the task when available. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("result")] + public string? Result { get; set; } + + /// ISO 8601 timestamp when the task was started. + [JsonPropertyName("startedAt")] + public required DateTimeOffset StartedAt { get; set; } - /// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured. + /// Current lifecycle status of the task. [JsonPropertyName("status")] - public McpServerStatus Status { get; set; } + public required TaskStatus Status { get; set; } + + /// Tool call ID associated with this agent task. + [JsonPropertyName("toolCallId")] + public required string ToolCallId { get; set; } } -/// MCP servers configured for the session, with their connection status and host-level state. +/// Tracked shell task metadata, including ID, command, status, timing, attachment/execution mode, log path, and PID. +/// The shell variant of . [Experimental(Diagnostics.Experimental)] -public sealed class McpServerList +public partial class TaskInfoShell : TaskInfo { - /// Host-level state, omitted when no MCP host is initialized. - [JsonPropertyName("host")] - public McpHostState? Host { get; set; } + /// + [JsonIgnore] + public override string Type => "shell"; - /// Configured MCP servers. - [JsonPropertyName("servers")] - public IList Servers { get => field ??= []; set; } -} + /// Whether the shell runs inside a managed PTY session or as an independent background process. + [JsonPropertyName("attachmentMode")] + public required TaskShellInfoAttachmentMode AttachmentMode { get; set; } -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionMcpListRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Whether this shell task can be promoted to background mode. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("canPromoteToBackground")] + public bool? CanPromoteToBackground { get; set; } -/// Normalized MCP Apps discovery metadata from a tool's `_meta.ui` block. -[Experimental(Diagnostics.Experimental)] -public sealed class McpToolUi -{ - /// URI of the tool's MCP App resource, typically a `ui://` resource identifier. Use `session.mcp.resources.read` to fetch its HTML and resource metadata. - [JsonPropertyName("resourceUri")] - public string? ResourceUri { get; set; } + /// Command being executed. + [JsonPropertyName("command")] + public required string Command { get; set; } - /// Tool visibility advertised by the server. When absent, MCP Apps defaults apply. - [JsonPropertyName("visibility")] - public IList? Visibility { get; set; } -} + /// ISO 8601 timestamp when the task finished. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("completedAt")] + public DateTimeOffset? CompletedAt { get; set; } -/// MCP tool metadata with tool name, optional description, and normalized MCP Apps discovery metadata. -[Experimental(Diagnostics.Experimental)] -public sealed class McpTools -{ - /// Tool description, when provided. + /// Short description of the task. [JsonPropertyName("description")] - public string? Description { get; set; } + public required string Description { get; set; } - /// Tool name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Whether task execution is synchronously awaited or managed in the background. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("executionMode")] + public TaskExecutionMode? ExecutionMode { get; set; } - /// Normalized MCP Apps discovery metadata. An empty object indicates that a valid `_meta.ui` block was present without recognized fields. - [JsonPropertyName("ui")] - public McpToolUi? Ui { get; set; } + /// Unique task identifier. + [JsonPropertyName("id")] + public required string Id { get; set; } + + /// Path to the detached shell log, when available. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("logPath")] + public string? LogPath { get; set; } + + /// Process ID when available. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("pid")] + public long? Pid { get; set; } + + /// ISO 8601 timestamp when the task was started. + [JsonPropertyName("startedAt")] + public required DateTimeOffset StartedAt { get; set; } + + /// Current lifecycle status of the task. + [JsonPropertyName("status")] + public required TaskStatus Status { get; set; } } -/// Tools exposed by the connected MCP server. Throws when the server is not connected. +/// Background tasks currently tracked by the session. [Experimental(Diagnostics.Experimental)] -public sealed class McpListToolsResult +public sealed class TaskList { - /// Tools exposed by the server. - [JsonPropertyName("tools")] - public IList Tools { get => field ??= []; set; } + /// Currently tracked tasks. + [JsonPropertyName("tasks")] + public IList Tasks { get => field ??= []; set; } } -/// Server name whose tool list should be returned. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class McpListToolsRequest +internal sealed class SessionTasksListRequest { - /// Name of the connected MCP server whose tools to list. - [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] - [MinLength(1)] - [JsonPropertyName("serverName")] - public string ServerName { get; set; } = string.Empty; - /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Name of the MCP server to enable for the session. +/// Refresh metadata for any detached background shells the runtime knows about. Use after a long pause to pick up exit/output state for shells running outside the agent loop. [Experimental(Diagnostics.Experimental)] -internal sealed class McpEnableRequest +public sealed class TasksRefreshResult { - /// Name of the MCP server to enable. - [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] - [MinLength(1)] - [JsonPropertyName("serverName")] - public string ServerName { get; set; } = string.Empty; +} +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionTasksRefreshRequest +{ /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Name of the MCP server to disable for the session. +/// Wait until all in-flight background tasks (agents + shells) and any follow-up turns scheduled by their completions have settled. Returns when the runtime is fully drained or after an internal timeout (default 10 minutes; configurable via COPILOT_TASK_WAIT_TIMEOUT_SECONDS). [Experimental(Diagnostics.Experimental)] -internal sealed class McpDisableRequest +public sealed class TasksWaitForPendingResult { - /// Name of the MCP server to disable. - [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] - [MinLength(1)] - [JsonPropertyName("serverName")] - public string ServerName { get; set; } = string.Empty; - - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; } /// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionMcpReloadRequest +internal sealed class SessionTasksWaitForPendingRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// MCP server allowed by policy, with server name and optional PII-free explanatory note. -[Experimental(Diagnostics.Experimental)] -public sealed class McpAllowedServer +/// Polymorphic base type discriminated by type. +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "type", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(TasksGetProgressResultProgressAgent), "agent")] +[JsonDerivedType(typeof(TasksGetProgressResultProgressShell), "shell")] +public partial class TasksGetProgressResultProgress { - /// Allowed server name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; - - /// PII-free note explaining why the server was allowed. - [JsonPropertyName("redactedNote")] - public string? RedactedNote { get; set; } + /// The type discriminator. + [JsonPropertyName("type")] + public virtual string Type { get; set; } = string.Empty; } -/// MCP server filtered by policy, with name, reason, optional redacted reason, and enterprise login. + +/// Timestamped display line for task progress output or recent agent activity. [Experimental(Diagnostics.Experimental)] -public sealed class McpFilteredServer +public sealed class TaskProgressLine { - /// Enterprise login associated with an allowlist policy. - [JsonPropertyName("enterpriseName")] - public string? EnterpriseName { get; set; } - - /// Filtered server name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; - - /// Human-readable filter reason. - [JsonPropertyName("reason")] - public string Reason { get; set; } = string.Empty; + /// Display message, e.g., "▸ bash", "✓ edit src/foo.ts". + [JsonPropertyName("message")] + public string Message { get; set; } = string.Empty; - /// PII-free filter reason. - [JsonPropertyName("redactedReason")] - public string? RedactedReason { get; set; } + /// ISO 8601 timestamp when this event occurred. + [JsonPropertyName("timestamp")] + public DateTimeOffset Timestamp { get; set; } } -/// MCP server startup filtering result. -[Experimental(Diagnostics.Experimental)] -internal sealed class McpStartServersResult +/// Progress snapshot for an agent task, with recent activity lines and optional latest intent. +/// The agent variant of . +public partial class TasksGetProgressResultProgressAgent : TasksGetProgressResultProgress { - /// Non-default servers allowed by policy. - [JsonPropertyName("allowedServers")] - public IList? AllowedServers { get; set; } + /// + [JsonIgnore] + public override string Type => "agent"; - /// Servers filtered out before startup. - [JsonPropertyName("filteredServers")] - public IList FilteredServers { get => field ??= []; set; } + /// The most recent intent reported by the agent. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("latestIntent")] + public string? LatestIntent { get; set; } + + /// Recent tool execution events converted to display lines. + [JsonPropertyName("recentActivity")] + public required IList RecentActivity { get; set; } } -/// Opaque MCP reload configuration. -[Experimental(Diagnostics.Experimental)] -internal sealed class McpReloadWithConfigRequest -{ - /// Opaque runtime MCP reload configuration. Marked internal: an in-process runtime shape (reloadMcpServers throws over the wire). - [JsonInclude] - [JsonPropertyName("config")] - internal JsonElement Config { get; set; } - - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} - -/// MCP CreateMessageResult payload (with optional 'tools' extension), present when action='success'. Treated as opaque at the schema layer; consumers should construct/consume it per the MCP CreateMessageResult shape. -[Experimental(Diagnostics.Experimental)] -public sealed class McpExecuteSamplingResult -{ -} - -/// Outcome of an MCP sampling execution: success result, failure error, or cancellation. -[Experimental(Diagnostics.Experimental)] -public sealed class McpSamplingExecutionResult +/// Progress snapshot for a shell task, with recent stdout/stderr output and optional process ID. +/// The shell variant of . +public partial class TasksGetProgressResultProgressShell : TasksGetProgressResultProgress { - /// Outcome of the sampling inference. 'success' produced a response; 'failure' encountered an error (including agent-side rejection by content filter or criteria); 'cancelled' the caller cancelled this execution via cancelSamplingExecution. - [JsonPropertyName("action")] - public McpSamplingExecutionAction Action { get; set; } + /// + [JsonIgnore] + public override string Type => "shell"; - /// Error description, present when action='failure'. - [JsonPropertyName("error")] - public string? Error { get; set; } + /// Process ID when available. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("pid")] + public long? Pid { get; set; } - /// MCP CreateMessageResult payload (with optional 'tools' extension), present when action='success'. Treated as opaque at the schema layer; consumers should construct/consume it per the MCP CreateMessageResult shape. - [JsonPropertyName("result")] - public McpExecuteSamplingResult? Result { get; set; } + /// Recent stdout/stderr lines from the running shell command. + [JsonPropertyName("recentOutput")] + public required string RecentOutput { get; set; } } -/// Raw MCP CreateMessageRequest params, as received in the `sampling.requested` event. Treated as opaque at the schema layer; the runtime converts the embedded MCP messages into the OpenAI chat-completion shape internally. +/// Progress information for the task, or null when no task with that ID is tracked. [Experimental(Diagnostics.Experimental)] -public sealed class McpExecuteSamplingRequest +public sealed class TasksGetProgressResult { + /// Progress information for the task, discriminated by type. Returns null when no task with this ID is currently tracked. + [JsonPropertyName("progress")] + public TasksGetProgressResultProgress? Progress { get; set; } } -/// Identifiers and raw MCP CreateMessageRequest params used to run a sampling inference. +/// Identifier of the background task to fetch progress for. [Experimental(Diagnostics.Experimental)] -internal sealed class McpExecuteSamplingParams +internal sealed class TasksGetProgressRequest { - /// The original MCP JSON-RPC request ID (string or number). Used by the runtime to correlate the inference with the originating MCP request for telemetry; this is distinct from `requestId` (which is the schema-level cancellation handle). - [JsonPropertyName("mcpRequestId")] - public JsonElement McpRequestId { get; set; } - - /// Raw MCP CreateMessageRequest params, as received in the `sampling.requested` event. Treated as opaque at the schema layer; the runtime converts the embedded MCP messages into the OpenAI chat-completion shape internally. - [JsonPropertyName("request")] - public McpExecuteSamplingRequest Request { get => field ??= new(); set; } - - /// Caller-provided unique identifier for this sampling execution. Use this same ID with cancelSamplingExecution to cancel the in-flight call. Must be unique within the session for the lifetime of the call. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; - - /// Name of the MCP server that initiated the sampling request. - [JsonPropertyName("serverName")] - public string ServerName { get; set; } = string.Empty; + /// Task identifier (agent ID or shell ID). + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Indicates whether an in-flight sampling execution with the given requestId was found and cancelled. +/// The first sync-waiting task that can currently be promoted to background mode. [Experimental(Diagnostics.Experimental)] -public sealed class McpCancelSamplingExecutionResult +public sealed class TasksGetCurrentPromotableResult { - /// True if an in-flight execution with the given requestId was found and signalled to cancel. False when no such execution is in flight (already completed, never started, or cancelled by another caller). - [JsonPropertyName("cancelled")] - public bool Cancelled { get; set; } + /// The first sync-waiting task (agent first, then shell) that can currently be promoted to background mode. Omitted if no such task exists. The returned task is guaranteed to have executionMode='sync' and canPromoteToBackground=true at the time of the call. + [JsonPropertyName("task")] + public TaskInfo? Task { get; set; } } -/// The requestId previously passed to executeSampling that should be cancelled. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class McpCancelSamplingExecutionParams +internal sealed class SessionTasksGetCurrentPromotableRequest { - /// The requestId previously passed to executeSampling that should be cancelled. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; - /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Env-value mode recorded on the session after the update. +/// Indicates whether the task was successfully promoted to background mode. [Experimental(Diagnostics.Experimental)] -public sealed class McpSetEnvValueModeResult +public sealed class TasksPromoteToBackgroundResult { - /// Mode recorded on the session after the update. - [JsonPropertyName("mode")] - public McpSetEnvValueModeDetails Mode { get; set; } + /// Whether the task was successfully promoted to background mode. + [JsonPropertyName("promoted")] + public bool Promoted { get; set; } } -/// Mode controlling how MCP server env values are resolved (`direct` or `indirect`). +/// Identifier of the task to promote to background mode. [Experimental(Diagnostics.Experimental)] -internal sealed class McpSetEnvValueModeParams +internal sealed class TasksPromoteToBackgroundRequest { - /// How environment-variable values supplied to MCP servers are resolved. "direct" passes literal string values; "indirect" treats values as references (e.g. names of environment variables on the host) that the runtime resolves before launch. Defaults to the runtime's startup mode; clients that intentionally launch MCP servers with literal values (e.g. CLI prompt mode and ACP) set this to "direct". - [JsonPropertyName("mode")] - public McpSetEnvValueModeDetails Mode { get; set; } + /// Task identifier. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Indicates whether the auto-managed `github` MCP server was removed (false when nothing to remove). +/// The promoted task as it now exists in background mode, omitted if no promotable task was waiting. [Experimental(Diagnostics.Experimental)] -public sealed class McpRemoveGitHubResult +public sealed class TasksPromoteCurrentToBackgroundResult { - /// True when the auto-managed `github` MCP server was removed; false when no removal happened (e.g. user has explicitly configured a `github` server, or the server was not registered). - [JsonPropertyName("removed")] - public bool Removed { get; set; } + /// The promoted task as it now exists in background mode, omitted if no promotable task was waiting. Atomic operation: avoids the race window of getCurrentPromotable + promoteToBackground. + [JsonPropertyName("task")] + public TaskInfo? Task { get; set; } } /// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionMcpRemoveGitHubRequest +internal sealed class SessionTasksPromoteCurrentToBackgroundRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Result of configuring GitHub MCP. +/// Indicates whether the background task was successfully cancelled. [Experimental(Diagnostics.Experimental)] -internal sealed class McpConfigureGitHubResult +public sealed class TasksCancelResult { - /// Whether GitHub MCP configuration changed. - [JsonPropertyName("changed")] - public bool Changed { get; set; } + /// Whether the task was successfully cancelled. + [JsonPropertyName("cancelled")] + public bool Cancelled { get; set; } } -/// Opaque auth info used to configure GitHub MCP. +/// Identifier of the background task to cancel. [Experimental(Diagnostics.Experimental)] -internal sealed class McpConfigureGitHubRequest +internal sealed class TasksCancelRequest { - /// Opaque runtime auth info for GitHub MCP configuration. Marked internal: an in-process runtime shape (configureGitHubMcp is a no-op over the wire). - [JsonInclude] - [JsonPropertyName("authInfo")] - internal JsonElement AuthInfo { get; set; } + /// Task identifier. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Server name and configuration for an individual MCP server start. +/// Indicates whether the task was removed. False when the task does not exist or is still running/idle. [Experimental(Diagnostics.Experimental)] -internal sealed class McpStartServerRequest +public sealed class TasksRemoveResult { - /// MCP server configuration (stdio process or remote HTTP/SSE). - [JsonPropertyName("config")] - public JsonElement Config { get; set; } + /// Whether the task was removed. Returns false if the task does not exist or is still running/idle (cancel it first). + [JsonPropertyName("removed")] + public bool Removed { get; set; } +} - /// Name of the MCP server to start. - [JsonPropertyName("serverName")] - public string ServerName { get; set; } = string.Empty; +/// Identifier of the completed or cancelled task to remove from tracking. +[Experimental(Diagnostics.Experimental)] +internal sealed class TasksRemoveRequest +{ + /// Task identifier. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Server name and optional replacement configuration for an individual MCP server restart. Omit `config` for a config-free restart-by-name of an already-configured server. +/// Indicates whether the message was delivered, with an error message when delivery failed. [Experimental(Diagnostics.Experimental)] -internal sealed class McpRestartServerRequest +public sealed class TasksSendMessageResult { - /// Replacement MCP server configuration (stdio process or remote HTTP/SSE). Omit to restart the server with its already-registered configuration (config-free restart-by-name). - [JsonPropertyName("config")] - public JsonElement? Config { get; set; } - - /// Name of the MCP server to restart. - [JsonPropertyName("serverName")] - public string ServerName { get; set; } = string.Empty; + /// Error message if delivery failed. + [JsonPropertyName("error")] + public string? Error { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Whether the message was successfully delivered or steered. + [JsonPropertyName("sent")] + public bool Sent { get; set; } } -/// Server name for an individual MCP server stop. +/// Identifier of the target agent task, message content, and optional sender agent ID. [Experimental(Diagnostics.Experimental)] -internal sealed class McpStopServerRequest +internal sealed class TasksSendMessageRequest { - /// Name of the MCP server to stop. - [JsonPropertyName("serverName")] - public string ServerName { get; set; } = string.Empty; + /// Agent ID of the sender, if sent on behalf of another agent. + [JsonPropertyName("fromAgentId")] + public string? FromAgentId { get; set; } + + /// Agent task identifier. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; + + /// Message content to send to the agent. + [JsonPropertyName("message")] + public string Message { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Registration parameters for an external MCP client. +/// Skill metadata available to a session, with name, description, source, enabled/invocable state, path, plugin, and argument hint. [Experimental(Diagnostics.Experimental)] -internal sealed class McpRegisterExternalClientRequest +public sealed class Skill { - /// In-process MCP Client instance. Marked internal: cannot be serialized across the JSON-RPC boundary. - [JsonInclude] - [JsonPropertyName("client")] - internal JsonElement Client { get; set; } + /// Optional freeform hint describing the skill's expected arguments, from the `argument-hint` frontmatter field. + [JsonPropertyName("argumentHint")] + public string? ArgumentHint { get; set; } - /// In-process server config (MCPServerConfig) paired with the in-process client/transport. Marked internal alongside its companions. - [JsonInclude] - [JsonPropertyName("config")] - internal JsonElement Config { get; set; } + /// Canonical slash command name used to invoke the skill, without the leading '/'. + [JsonPropertyName("commandName")] + public string? CommandName { get; set; } - /// Logical server name for the external client. - [JsonPropertyName("serverName")] - public string ServerName { get; set; } = string.Empty; + /// Description of what the skill does. + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Whether the skill is currently enabled. + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } - /// In-process MCP Transport instance. Marked internal: cannot be serialized across the JSON-RPC boundary. - [JsonInclude] - [JsonPropertyName("transport")] - internal JsonElement Transport { get; set; } + /// Unique identifier for the skill. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Absolute path to the skill file. + [JsonPropertyName("path")] + public string? Path { get; set; } + + /// Name of the plugin that provides the skill, when source is 'plugin'. + [JsonPropertyName("pluginName")] + public string? PluginName { get; set; } + + /// Source location type (e.g., project, personal-copilot, plugin, builtin). + [JsonPropertyName("source")] + public SkillSource Source { get; set; } + + /// Whether the skill can be invoked by the user as a slash command. + [JsonPropertyName("userInvocable")] + public bool UserInvocable { get; set; } } -/// Server name identifying the external client to remove. +/// Skills available to the session, with their enabled state. [Experimental(Diagnostics.Experimental)] -internal sealed class McpUnregisterExternalClientRequest +public sealed class SkillList { - /// Server name of the external client to unregister. - [JsonPropertyName("serverName")] - public string ServerName { get; set; } = string.Empty; + /// Available skills. + [JsonPropertyName("skills")] + public IList Skills { get => field ??= []; set; } +} +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionSkillsListRequest +{ /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Whether the named MCP server is running. +/// Skill invocation record with name, path, content, allowed tools, and turn number. [Experimental(Diagnostics.Experimental)] -public sealed class McpIsServerRunningResult +public sealed class SkillsInvokedSkill { - /// True if the server has an active client and transport. - [JsonPropertyName("running")] - public bool Running { get; set; } + /// Tools that should be auto-approved when this skill is active, captured at invocation time. + [JsonPropertyName("allowedTools")] + public IList? AllowedTools { get; set; } + + /// Full content of the skill file. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; + + /// Turn number when the skill was invoked. + [JsonPropertyName("invokedAtTurn")] + public long InvokedAtTurn { get; set; } + + /// Unique identifier for the skill. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Path to the SKILL.md file. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; } -/// Server name to check running status for. +/// Skills invoked during this session, ordered by invocation time (most recent last). [Experimental(Diagnostics.Experimental)] -internal sealed class McpIsServerRunningRequest +public sealed class SkillsGetInvokedResult { - /// Name of the MCP server to check. - [JsonPropertyName("serverName")] - public string ServerName { get; set; } = string.Empty; + /// Skills invoked during this session, ordered by invocation time (most recent last). + [JsonPropertyName("skills")] + public IList Skills { get => field ??= []; set; } +} +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionSkillsGetInvokedRequest +{ /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Indicates whether the pending MCP OAuth response was accepted. +/// Name of the skill to enable for the session. [Experimental(Diagnostics.Experimental)] -public sealed class McpOauthHandlePendingResult +internal sealed class SkillsEnableRequest { - /// Whether the response was accepted. False if the request was unknown, timed out, or already resolved. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// Name of the skill to enable. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Host response to the pending OAuth request. -/// Polymorphic base type discriminated by kind. +/// Name of the skill to disable for the session. [Experimental(Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(McpOauthPendingRequestResponseToken), "token")] -[JsonDerivedType(typeof(McpOauthPendingRequestResponseCancelled), "cancelled")] -public partial class McpOauthPendingRequestResponse +internal sealed class SkillsDisableRequest { - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; -} + /// Name of the skill to disable. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} -/// The token variant of . +/// Diagnostics from reloading skill definitions, with warnings and errors as separate lists. [Experimental(Diagnostics.Experimental)] -public partial class McpOauthPendingRequestResponseToken : McpOauthPendingRequestResponse +public sealed class SkillsLoadDiagnostics { - /// - [JsonIgnore] - public override string Kind => "token"; - - /// Access token acquired by the SDK host. - [JsonPropertyName("accessToken")] - public required string AccessToken { get; set; } - - /// Token lifetime in seconds, if known. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("expiresIn")] - public long? ExpiresIn { get; set; } + /// Errors emitted while loading skills (e.g. skills that failed to load entirely). + [JsonPropertyName("errors")] + public IList Errors { get => field ??= []; set; } - /// OAuth token type. Defaults to Bearer when omitted. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("tokenType")] - public string? TokenType { get; set; } + /// Warnings emitted while loading skills (e.g. skills that loaded but had issues). + [JsonPropertyName("warnings")] + public IList Warnings { get => field ??= []; set; } } -/// The cancelled variant of . +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -public partial class McpOauthPendingRequestResponseCancelled : McpOauthPendingRequestResponse +internal sealed class SessionSkillsReloadRequest { - /// - [JsonIgnore] - public override string Kind => "cancelled"; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Pending MCP OAuth request ID and host-provided token or cancellation response. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class McpOauthHandlePendingRequest +internal sealed class SessionSkillsEnsureLoadedRequest { - /// OAuth request identifier from the mcp.oauth_required event. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; - - /// Host response to the pending OAuth request. - [JsonPropertyName("result")] - public McpOauthPendingRequestResponse Result { get => field ??= new(); set; } - /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// OAuth authorization URL the caller should open, or empty when cached tokens already authenticated the server. +/// Recorded MCP server connection failure. [Experimental(Diagnostics.Experimental)] -public sealed class McpOauthLoginResult +public sealed class McpServerFailureInfo { - /// URL the caller should open in a browser to complete OAuth. Omitted when cached tokens were still valid and no browser interaction was needed — the server is already reconnected in that case. When present, the runtime starts the callback listener before returning and continues the flow in the background; completion is signaled via session.mcp_server_status_changed. - [Url] - [StringSyntax(StringSyntaxAttribute.Uri)] - [JsonPropertyName("authorizationUrl")] - public string? AuthorizationUrl { get; set; } + /// Failure message produced when the MCP server connection failed. + [JsonPropertyName("message")] + public string Message { get; set; } = string.Empty; + + /// epoch-ms timestamp at which the failure was recorded. + [JsonPropertyName("timestamp")] + public long Timestamp { get; set; } } -/// Remote MCP server name and optional overrides controlling reauthentication, OAuth client display name, callback success-page copy, and static OAuth client selection. +/// Recorded MCP server pending-auth state. [Experimental(Diagnostics.Experimental)] -internal sealed class McpOauthLoginRequest +public sealed class McpServerNeedsAuthInfo { - /// Optional override for the body text shown on the OAuth loopback callback success page. When omitted, the runtime applies a neutral fallback; callers driving interactive auth should pass surface-specific copy telling the user where to return. - [JsonPropertyName("callbackSuccessMessage")] - public string? CallbackSuccessMessage { get; set; } + /// epoch-ms timestamp at which the server signalled it needs authentication. + [JsonPropertyName("timestamp")] + public long Timestamp { get; set; } +} - /// Optional OAuth client ID override for this login. When set, the runtime uses this pre-registered static client instead of dynamic client registration. - [JsonPropertyName("clientId")] - public string? ClientId { get; set; } +/// Host-level state, omitted when no MCP host is initialized. +[Experimental(Diagnostics.Experimental)] +public sealed class McpHostState +{ + /// Names of currently-connected MCP clients. + [JsonPropertyName("clients")] + public IList Clients { get => field ??= []; set; } - /// Optional override for the OAuth client display name shown on the consent screen. Applies to newly registered dynamic clients only — existing registrations keep the name they were created with. When omitted, the runtime applies a neutral fallback; callers driving interactive auth should pass their own surface-specific label so the consent screen matches the product the user sees. - [JsonPropertyName("clientName")] - public string? ClientName { get; set; } + /// Configured servers that are explicitly disabled. + [JsonPropertyName("disabledServers")] + public IList DisabledServers { get => field ??= []; set; } - /// Optional OAuth client secret override for this login. The runtime treats this as an ephemeral host-owned secret, uses it for this authentication attempt and does not persist it. - [JsonPropertyName("clientSecret")] - public string? ClientSecret { get; set; } + /// Map of server name to recorded connection failure. + [JsonPropertyName("failedServers")] + public IDictionary FailedServers { get => field ??= new Dictionary(); set; } - /// When true, clears any cached OAuth token for the server and runs a full new authorization. Use when the user explicitly wants to switch accounts or believes their session is stuck. - [JsonPropertyName("forceReauth")] - public bool? ForceReauth { get; set; } + /// Configured servers filtered out by MCP server policy. + [JsonPropertyName("filteredServers")] + public IList FilteredServers { get => field ??= []; set; } - /// Optional OAuth grant type override for this login. Defaults to the server configuration, or authorization_code when no grant type is specified. - [JsonPropertyName("grantType")] - public McpOauthLoginGrantType? GrantType { get; set; } + /// Whether third-party MCP servers are policy-enabled for this session. + [JsonPropertyName("mcp3pEnabled")] + public bool Mcp3pEnabled { get; set; } - /// Optional override indicating whether the static OAuth client is public. When false, the runtime treats it as confidential and uses the per-login clientSecret if provided, otherwise retrieving the client secret from the MCP OAuth secret store. - [JsonPropertyName("publicClient")] - public bool? PublicClient { get; set; } + /// Map of server name to recorded pending-auth state. + [JsonPropertyName("needsAuthServers")] + public IDictionary NeedsAuthServers { get => field ??= new Dictionary(); set; } - /// Name of the remote MCP server to authenticate. + /// Names of servers with in-flight connection attempts. + [JsonPropertyName("pendingConnections")] + public IList PendingConnections { get => field ??= []; set; } +} + +/// MCP server status entry, including config source/plugin source and any connection error. +[Experimental(Diagnostics.Experimental)] +public sealed class McpServer +{ + /// Error message if the server failed to connect. + [JsonPropertyName("error")] + public string? Error { get; set; } + + /// Server name (config key). [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] [MinLength(1)] - [JsonPropertyName("serverName")] - public string ServerName { get; set; } = string.Empty; + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Configuration source: user, workspace, plugin, or builtin. + [JsonPropertyName("source")] + public McpServerSource? Source { get; set; } + + /// Plugin name that provided this server, when source is plugin. + [JsonPropertyName("sourcePlugin")] + public string? SourcePlugin { get; set; } + + /// Plugin version that provided this server, when source is plugin. + [JsonPropertyName("sourcePluginVersion")] + public string? SourcePluginVersion { get; set; } + + /// Connection status: connected, failed, needs-auth, pending, disabled, stopped, or not_configured. + [JsonPropertyName("status")] + public McpServerStatus Status { get; set; } } -/// Indicates whether the pending MCP headers refresh response was accepted. +/// MCP servers configured for the session, with their connection status and host-level state. [Experimental(Diagnostics.Experimental)] -public sealed class McpHeadersHandlePendingHeadersRefreshRequestResult +public sealed class McpServerList { - /// Whether the response was accepted. False if the request was unknown, timed out, or already resolved. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// Host-level state, omitted when no MCP host is initialized. + [JsonPropertyName("host")] + public McpHostState? Host { get; set; } + + /// Configured MCP servers. + [JsonPropertyName("servers")] + public IList Servers { get => field ??= []; set; } } -/// Host response: supply dynamic headers or decline this refresh. -/// Polymorphic base type discriminated by kind. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(McpHeadersHandlePendingHeadersRefreshRequestHeaders), "headers")] -[JsonDerivedType(typeof(McpHeadersHandlePendingHeadersRefreshRequestNone), "none")] -public partial class McpHeadersHandlePendingHeadersRefreshRequest +internal sealed class SessionMcpListRequest { - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } - -/// The headers variant of . -[Experimental(Diagnostics.Experimental)] -public partial class McpHeadersHandlePendingHeadersRefreshRequestHeaders : McpHeadersHandlePendingHeadersRefreshRequest -{ - /// - [JsonIgnore] - public override string Kind => "headers"; - - /// Headers to overlay onto the MCP request. Dynamic headers override static config headers but do not replace SDK-managed request headers. - [JsonPropertyName("headers")] - public required IDictionary Headers { get; set; } -} - -/// The none variant of . -[Experimental(Diagnostics.Experimental)] -public partial class McpHeadersHandlePendingHeadersRefreshRequestNone : McpHeadersHandlePendingHeadersRefreshRequest -{ - /// - [JsonIgnore] - public override string Kind => "none"; -} - -/// MCP headers refresh request id and the host response. +/// Normalized MCP Apps discovery metadata from a tool's `_meta.ui` block. [Experimental(Diagnostics.Experimental)] -internal sealed class McpHeadersHandlePendingHeadersRefreshRequestRequest +public sealed class McpToolUi { - /// Headers refresh request identifier from mcp.headers_refresh_required. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; - - /// Host response: supply dynamic headers or decline this refresh. - [JsonPropertyName("result")] - public McpHeadersHandlePendingHeadersRefreshRequest Result { get => field ??= new(); set; } + /// URI of the tool's MCP App resource, typically a `ui://` resource identifier. Use `session.mcp.resources.read` to fetch its HTML and resource metadata. + [JsonPropertyName("resourceUri")] + public string? ResourceUri { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Tool visibility advertised by the server. When absent, MCP Apps defaults apply. + [JsonPropertyName("visibility")] + public IList? Visibility { get; set; } } -/// MCP Apps resource content with URI, optional MIME type, text or base64 blob, and resource metadata. +/// MCP tool metadata with tool name, optional description, and normalized MCP Apps discovery metadata. [Experimental(Diagnostics.Experimental)] -public sealed class McpAppsResourceContent +public sealed class McpTools { - /// Resource-level metadata (CSP, permissions, etc.). - [JsonPropertyName("_meta")] - public IDictionary? Meta { get; set; } - - /// Base64-encoded binary content. - [JsonPropertyName("blob")] - public string? Blob { get; set; } - - /// MIME type of the content. - [JsonPropertyName("mimeType")] - public string? MimeType { get; set; } + /// Tool description, when provided. + [JsonPropertyName("description")] + public string? Description { get; set; } - /// Text content (e.g. HTML). - [JsonPropertyName("text")] - public string? Text { get; set; } + /// Tool name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// The resource URI (typically ui://...). - [JsonPropertyName("uri")] - public string Uri { get; set; } = string.Empty; + /// Normalized MCP Apps discovery metadata. An empty object indicates that a valid `_meta.ui` block was present without recognized fields. + [JsonPropertyName("ui")] + public McpToolUi? Ui { get; set; } } -/// Resource contents returned by the MCP server. +/// Tools exposed by the connected MCP server. Throws when the server is not connected. [Experimental(Diagnostics.Experimental)] -public sealed class McpAppsReadResourceResult +public sealed class McpListToolsResult { - /// Resource contents returned by the server. - [JsonPropertyName("contents")] - public IList Contents { get => field ??= []; set; } + /// Tools exposed by the server. + [JsonPropertyName("tools")] + public IList Tools { get => field ??= []; set; } } -/// MCP server and resource URI to fetch. +/// Server name whose tool list should be returned. [Experimental(Diagnostics.Experimental)] -internal sealed class McpAppsReadResourceRequest +internal sealed class McpListToolsRequest { - /// Name of the MCP server hosting the resource. + /// Name of the connected MCP server whose tools to list. [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] [MinLength(1)] @@ -6695,33 +7094,13 @@ internal sealed class McpAppsReadResourceRequest /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; - - /// Resource URI (typically ui://...). - [JsonPropertyName("uri")] - public string Uri { get; set; } = string.Empty; -} - -/// App-callable tools from the named MCP server. -[Experimental(Diagnostics.Experimental)] -public sealed class McpAppsListToolsResult -{ - /// App-callable tools from the server. - [JsonPropertyName("tools")] - public IList> Tools { get => field ??= []; set; } } -/// MCP server to list app-callable tools for. +/// Name of the MCP server to enable for the session. [Experimental(Diagnostics.Experimental)] -internal sealed class McpAppsListToolsRequest +internal sealed class McpEnableRequest { - /// **Required.** Server whose ui:// view issued the request. Per SEP-1865 ('callable by the app from this server only'), the call is rejected when this differs from `serverName`, and rejected outright when missing. - [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] - [MinLength(1)] - [JsonPropertyName("originServerName")] - public string OriginServerName { get; set; } = string.Empty; - - /// MCP server hosting the app. + /// Name of the MCP server to enable. [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] [MinLength(1)] @@ -6733,22 +7112,11 @@ internal sealed class McpAppsListToolsRequest public string SessionId { get; set; } = string.Empty; } -/// MCP server, tool name, and arguments to invoke from an MCP App view. +/// Name of the MCP server to disable for the session. [Experimental(Diagnostics.Experimental)] -internal sealed class McpAppsCallToolRequest +internal sealed class McpDisableRequest { - /// Tool arguments. - [JsonPropertyName("arguments")] - public IDictionary? Arguments { get; set; } - - /// **Required.** Server whose ui:// view issued the request. Per SEP-1865 ('callable by the app from this server only'), the call is rejected when this differs from `serverName`, and rejected outright when missing. - [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] - [MinLength(1)] - [JsonPropertyName("originServerName")] - public string OriginServerName { get; set; } = string.Empty; - - /// MCP server hosting the tool. + /// Name of the MCP server to disable. [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] [MinLength(1)] @@ -6758,168 +7126,123 @@ internal sealed class McpAppsCallToolRequest /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; - - /// MCP tool name. - [JsonPropertyName("toolName")] - public string ToolName { get; set; } = string.Empty; -} - -/// Host context advertised to MCP App guests. -[Experimental(Diagnostics.Experimental)] -public sealed class McpAppsSetHostContextDetails -{ - /// Display modes the host supports. - [JsonPropertyName("availableDisplayModes")] - public IList? AvailableDisplayModes { get; set; } - - /// Current display mode (SEP-1865). - [JsonPropertyName("displayMode")] - public McpAppsSetHostContextDetailsDisplayMode? DisplayMode { get; set; } - - /// BCP-47 locale, e.g. 'en-US'. - [JsonPropertyName("locale")] - public string? Locale { get; set; } - - /// Platform type for responsive design. - [JsonPropertyName("platform")] - public McpAppsSetHostContextDetailsPlatform? Platform { get; set; } - - /// UI theme preference per SEP-1865. - [JsonPropertyName("theme")] - public McpAppsSetHostContextDetailsTheme? Theme { get; set; } - - /// IANA timezone, e.g. 'America/New_York'. - [JsonPropertyName("timeZone")] - public string? TimeZone { get; set; } - - /// Host application identifier. - [JsonPropertyName("userAgent")] - public string? UserAgent { get; set; } } -/// Host context to advertise to MCP App guests. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class McpAppsSetHostContextRequest +internal sealed class SessionMcpReloadRequest { - /// Host context advertised to MCP App guests. - [JsonPropertyName("context")] - public McpAppsSetHostContextDetails Context { get => field ??= new(); set; } - /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Current host context. +/// MCP server allowed by policy, with server name and optional PII-free explanatory note. [Experimental(Diagnostics.Experimental)] -public sealed class McpAppsHostContextDetails +public sealed class McpAllowedServer { - /// Display modes the host supports. - [JsonPropertyName("availableDisplayModes")] - public IList? AvailableDisplayModes { get; set; } - - /// Current display mode (SEP-1865). - [JsonPropertyName("displayMode")] - public McpAppsHostContextDetailsDisplayMode? DisplayMode { get; set; } + /// Allowed server name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// BCP-47 locale, e.g. 'en-US'. - [JsonPropertyName("locale")] - public string? Locale { get; set; } + /// PII-free note explaining why the server was allowed. + [JsonPropertyName("redactedNote")] + public string? RedactedNote { get; set; } +} - /// Platform type for responsive design. - [JsonPropertyName("platform")] - public McpAppsHostContextDetailsPlatform? Platform { get; set; } +/// MCP server filtered by policy, with name, reason, and optional redacted reason. +[Experimental(Diagnostics.Experimental)] +public sealed class McpFilteredServer +{ + /// Deprecated. This field is no longer populated. + [EditorBrowsable(EditorBrowsableState.Never)] +#if NET5_0_OR_GREATER + [Obsolete("This member is deprecated and will be removed in a future version.", DiagnosticId = "GHCP001")] +#endif + [JsonPropertyName("enterpriseName")] + public string? EnterpriseName { get; set; } - /// UI theme preference per SEP-1865. - [JsonPropertyName("theme")] - public McpAppsHostContextDetailsTheme? Theme { get; set; } + /// Filtered server name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// IANA timezone, e.g. 'America/New_York'. - [JsonPropertyName("timeZone")] - public string? TimeZone { get; set; } + /// Human-readable filter reason. + [JsonPropertyName("reason")] + public string Reason { get; set; } = string.Empty; - /// Host application identifier. - [JsonPropertyName("userAgent")] - public string? UserAgent { get; set; } + /// PII-free filter reason. + [JsonPropertyName("redactedReason")] + public string? RedactedReason { get; set; } } -/// Current host context advertised to MCP App guests. +/// MCP server startup filtering result. [Experimental(Diagnostics.Experimental)] -public sealed class McpAppsHostContext +internal sealed class McpStartServersResult { - /// Current host context. - [JsonPropertyName("context")] - public McpAppsHostContextDetails Context { get => field ??= new(); set; } + /// Non-default servers allowed by policy. + [JsonPropertyName("allowedServers")] + public IList? AllowedServers { get; set; } + + /// Servers filtered out before startup. + [JsonPropertyName("filteredServers")] + public IList FilteredServers { get => field ??= []; set; } } -/// Identifies the target session. +/// Opaque MCP reload configuration. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionMcpAppsGetHostContextRequest +internal sealed class McpReloadWithConfigRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Capability negotiation snapshot. +/// MCP CreateMessageResult payload (with optional 'tools' extension), present when action='success'. Treated as opaque at the schema layer; consumers should construct/consume it per the MCP CreateMessageResult shape. [Experimental(Diagnostics.Experimental)] -public sealed class McpAppsDiagnoseCapability +public sealed class McpExecuteSamplingResult { - /// Whether the runtime advertises `extensions.io.modelcontextprotocol/ui` to MCP servers. - [JsonPropertyName("advertised")] - public bool Advertised { get; set; } +} - /// Whether the MCP_APPS feature flag (or COPILOT_MCP_APPS env override) is on. - [JsonPropertyName("featureFlagEnabled")] - public bool FeatureFlagEnabled { get; set; } - - /// Whether the session has the `mcp-apps` capability. - [JsonPropertyName("sessionHasMcpApps")] - public bool SessionHasMcpApps { get; set; } -} - -/// What the server returned for this session. +/// Outcome of an MCP sampling execution: success result, failure error, or cancellation. [Experimental(Diagnostics.Experimental)] -public sealed class McpAppsDiagnoseServer +public sealed class McpSamplingExecutionResult { - /// Whether the named server is currently connected. - [JsonPropertyName("connected")] - public bool Connected { get; set; } - - /// Up to 5 tool names with `_meta.ui` for quick inspection. - [JsonPropertyName("sampleToolNames")] - public IList SampleToolNames { get => field ??= []; set; } + /// Outcome of the sampling inference. 'success' produced a response; 'failure' encountered an error (including agent-side rejection by content filter or criteria); 'cancelled' the caller cancelled this execution via cancelSamplingExecution. + [JsonPropertyName("action")] + public McpSamplingExecutionAction Action { get; set; } - /// Total tools returned by the server's tools/list. - [JsonPropertyName("toolCount")] - public double ToolCount { get; set; } + /// Error description, present when action='failure'. + [JsonPropertyName("error")] + public string? Error { get; set; } - /// Tools whose `_meta.ui` is populated (resourceUri and/or visibility set). - [JsonPropertyName("toolsWithUiMeta")] - public double ToolsWithUiMeta { get; set; } + /// MCP CreateMessageResult payload (with optional 'tools' extension), present when action='success'. Treated as opaque at the schema layer; consumers should construct/consume it per the MCP CreateMessageResult shape. + [JsonPropertyName("result")] + public McpExecuteSamplingResult? Result { get; set; } } -/// Diagnostic snapshot of MCP Apps wiring for the named server. +/// Raw MCP CreateMessageRequest params, as received in the `sampling.requested` event. Treated as opaque at the schema layer; the runtime converts the embedded MCP messages into the OpenAI chat-completion shape internally. [Experimental(Diagnostics.Experimental)] -public sealed class McpAppsDiagnoseResult +public sealed class McpExecuteSamplingRequest { - /// Capability negotiation snapshot. - [JsonPropertyName("capability")] - public McpAppsDiagnoseCapability Capability { get => field ??= new(); set; } - - /// What the server returned for this session. - [JsonPropertyName("server")] - public McpAppsDiagnoseServer Server { get => field ??= new(); set; } } -/// MCP server to diagnose MCP Apps wiring for. +/// Identifiers and raw MCP CreateMessageRequest params used to run a sampling inference. [Experimental(Diagnostics.Experimental)] -internal sealed class McpAppsDiagnoseRequest +internal sealed class McpExecuteSamplingParams { - /// MCP server to probe. - [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] - [MinLength(1)] + /// The original MCP JSON-RPC request ID (string or number). Used by the runtime to correlate the inference with the originating MCP request for telemetry; this is distinct from `requestId` (which is the schema-level cancellation handle). + [JsonPropertyName("mcpRequestId")] + public JsonElement McpRequestId { get; set; } + + /// Raw MCP CreateMessageRequest params, as received in the `sampling.requested` event. Treated as opaque at the schema layer; the runtime converts the embedded MCP messages into the OpenAI chat-completion shape internally. + [JsonPropertyName("request")] + public McpExecuteSamplingRequest Request { get => field ??= new(); set; } + + /// Caller-provided unique identifier for this sampling execution. Use this same ID with cancelSamplingExecution to cancel the in-flight call. Must be unique within the session for the lifetime of the call. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; + + /// Name of the MCP server that initiated the sampling request. [JsonPropertyName("serverName")] public string ServerName { get; set; } = string.Empty; @@ -6928,176 +7251,95 @@ internal sealed class McpAppsDiagnoseRequest public string SessionId { get; set; } = string.Empty; } -/// MCP resource content with URI, optional MIME type, text or base64 blob, and resource metadata. +/// Indicates whether an in-flight sampling execution with the given requestId was found and cancelled. [Experimental(Diagnostics.Experimental)] -public sealed class McpResourceContent +public sealed class McpCancelSamplingExecutionResult { - /// Resource-level metadata (CSP, permissions, etc.). - [JsonPropertyName("_meta")] - public IDictionary? Meta { get; set; } - - /// Base64-encoded binary content. - [JsonPropertyName("blob")] - public string? Blob { get; set; } - - /// MIME type of the content. - [JsonPropertyName("mimeType")] - public string? MimeType { get; set; } + /// True if an in-flight execution with the given requestId was found and signalled to cancel. False when no such execution is in flight (already completed, never started, or cancelled by another caller). + [JsonPropertyName("cancelled")] + public bool Cancelled { get; set; } +} - /// Text content (e.g. HTML). - [JsonPropertyName("text")] - public string? Text { get; set; } +/// The requestId previously passed to executeSampling that should be cancelled. +[Experimental(Diagnostics.Experimental)] +internal sealed class McpCancelSamplingExecutionParams +{ + /// The requestId previously passed to executeSampling that should be cancelled. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; - /// The resource URI. - [JsonPropertyName("uri")] - public string Uri { get; set; } = string.Empty; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Resource contents returned by the MCP server. +/// Env-value mode recorded on the session after the update. [Experimental(Diagnostics.Experimental)] -public sealed class McpResourcesReadResult +public sealed class McpSetEnvValueModeResult { - /// Resource contents returned by the server. - [JsonPropertyName("contents")] - public IList Contents { get => field ??= []; set; } + /// Mode recorded on the session after the update. + [JsonPropertyName("mode")] + public McpSetEnvValueModeDetails Mode { get; set; } } -/// MCP server and resource URI to fetch. +/// Mode controlling how MCP server env values are resolved (`direct` or `indirect`). [Experimental(Diagnostics.Experimental)] -internal sealed class McpResourcesReadRequest +internal sealed class McpSetEnvValueModeParams { - /// Name of the MCP server hosting the resource. - [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] - [MinLength(1)] - [JsonPropertyName("serverName")] - public string ServerName { get; set; } = string.Empty; + /// How environment-variable values supplied to MCP servers are resolved. "direct" passes literal string values; "indirect" treats values as references (e.g. names of environment variables on the host) that the runtime resolves before launch. Defaults to the runtime's startup mode; clients that intentionally launch MCP servers with literal values (e.g. CLI prompt mode and ACP) set this to "direct". + [JsonPropertyName("mode")] + public McpSetEnvValueModeDetails Mode { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; - - /// Resource URI. - [JsonPropertyName("uri")] - public string Uri { get; set; } = string.Empty; } -/// Standard MCP resource annotations plus preserved non-standard annotation fields. +/// Indicates whether the auto-managed `github` MCP server was removed (false when nothing to remove). [Experimental(Diagnostics.Experimental)] -public sealed class McpResourceAnnotations +public sealed class McpRemoveGitHubResult { - /// Server-provided non-standard annotation fields preserved from the MCP response. - [JsonPropertyName("additionalProperties")] - public IDictionary? AdditionalProperties { get; set; } - - /// Intended audience roles for this resource. - [JsonPropertyName("audience")] - public IList? Audience { get; set; } - - /// Last-modified timestamp hint. - [JsonPropertyName("lastModified")] - public string? LastModified { get; set; } - - /// Priority hint for model/client use. - [JsonPropertyName("priority")] - public double? Priority { get; set; } + /// True when the auto-managed `github` MCP server was removed; false when no removal happened (e.g. user has explicitly configured a `github` server, or the server was not registered). + [JsonPropertyName("removed")] + public bool Removed { get; set; } } -/// A resource icon descriptor plus preserved non-standard icon fields. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -public sealed class McpResourceIcon +internal sealed class SessionMcpRemoveGitHubRequest { - /// Server-provided non-standard icon fields preserved from the MCP response. - [JsonPropertyName("additionalProperties")] - public IDictionary? AdditionalProperties { get; set; } - - /// Icon MIME type, when known. - [JsonPropertyName("mimeType")] - public string? MimeType { get; set; } - - /// Icon sizes hint. - [JsonPropertyName("sizes")] - public string? Sizes { get; set; } - - /// Icon URI. - [JsonPropertyName("src")] - public string Src { get; set; } = string.Empty; - - /// Theme hint for this icon. - [JsonPropertyName("theme")] - public string? Theme { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// An MCP resource descriptor (spec `Resource`): URI, name, and optional title, description, MIME type, size, icons, annotations, and metadata. Server-provided fields outside the standard descriptor shape are exposed under `additionalProperties`. +/// Result of configuring GitHub MCP. [Experimental(Diagnostics.Experimental)] -public sealed class McpResource +internal sealed class McpConfigureGitHubResult { - /// Resource-level metadata. - [JsonPropertyName("_meta")] - public IDictionary? Meta { get; set; } - - /// Server-provided non-standard descriptor fields preserved from the MCP response. - [JsonPropertyName("additionalProperties")] - public IDictionary? AdditionalProperties { get; set; } - - /// Model/client annotations associated with this resource. - [JsonPropertyName("annotations")] - public McpResourceAnnotations? Annotations { get; set; } - - /// Optional description of what this resource represents. - [JsonPropertyName("description")] - public string? Description { get; set; } - - /// Icons associated with this resource. - [JsonPropertyName("icons")] - public IList? Icons { get; set; } - - /// MIME type of the resource, if known. - [JsonPropertyName("mimeType")] - public string? MimeType { get; set; } - - /// The programmatic name of the resource. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; - - /// Resource size in bytes, when known. - [JsonPropertyName("size")] - public long? Size { get; set; } - - /// Optional human-readable display title. - [JsonPropertyName("title")] - public string? Title { get; set; } - - /// The resource URI (e.g. ui://... or file:///...). - [JsonPropertyName("uri")] - public string Uri { get; set; } = string.Empty; + /// Whether GitHub MCP configuration changed. + [JsonPropertyName("changed")] + public bool Changed { get; set; } } -/// One page of resources advertised by the named MCP server. +/// Opaque auth info used to configure GitHub MCP. [Experimental(Diagnostics.Experimental)] -public sealed class McpResourcesListResult +internal sealed class McpConfigureGitHubRequest { - /// Opaque cursor for the next page, if the server has more resources. - [JsonPropertyName("nextCursor")] - public string? NextCursor { get; set; } - - /// Resources advertised by the server (proxied MCP `resources/list`). - [JsonPropertyName("resources")] - public IList Resources { get => field ??= []; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// MCP server whose resources to enumerate. +/// Server name and optional configuration for an individual MCP server start. Omit `config` for a config-free start-by-name of an already-configured server. [Experimental(Diagnostics.Experimental)] -internal sealed class McpResourcesListRequest +internal sealed class McpStartServerRequest { - /// Opaque MCP pagination cursor from a prior `nextCursor` value. - [JsonPropertyName("cursor")] - public string? Cursor { get; set; } + /// MCP server configuration (stdio process or remote HTTP/SSE). Omit to start the server with its already-registered configuration (config-free start-by-name). + [JsonPropertyName("config")] + public JsonElement? Config { get; set; } - /// Name of the MCP server whose resources to enumerate. - [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] - [MinLength(1)] + /// Name of the MCP server to start. [JsonPropertyName("serverName")] public string ServerName { get; set; } = string.Empty; @@ -7106,72 +7348,41 @@ internal sealed class McpResourcesListRequest public string SessionId { get; set; } = string.Empty; } -/// An MCP resource template descriptor (spec `ResourceTemplate`): an RFC 6570 URI template, name, and optional title, description, MIME type, icons, annotations, and metadata. Server-provided fields outside the standard descriptor shape are exposed under `additionalProperties`. +/// Server name and optional replacement configuration for an individual MCP server restart. Omit `config` for a config-free restart-by-name of an already-configured server. [Experimental(Diagnostics.Experimental)] -public sealed class McpResourceTemplate +internal sealed class McpRestartServerRequest { - /// Resource-template-level metadata. - [JsonPropertyName("_meta")] - public IDictionary? Meta { get; set; } - - /// Server-provided non-standard descriptor fields preserved from the MCP response. - [JsonPropertyName("additionalProperties")] - public IDictionary? AdditionalProperties { get; set; } - - /// Model/client annotations associated with this template. - [JsonPropertyName("annotations")] - public McpResourceAnnotations? Annotations { get; set; } - - /// Optional description of what this template is for. - [JsonPropertyName("description")] - public string? Description { get; set; } - - /// Icons associated with resources matching this template. - [JsonPropertyName("icons")] - public IList? Icons { get; set; } - - /// MIME type for resources matching this template, if uniform. - [JsonPropertyName("mimeType")] - public string? MimeType { get; set; } - - /// The programmatic name of the resource template. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Replacement MCP server configuration (stdio process or remote HTTP/SSE). Omit to restart the server with its already-registered configuration (config-free restart-by-name). + [JsonPropertyName("config")] + public JsonElement? Config { get; set; } - /// Optional human-readable display title. - [JsonPropertyName("title")] - public string? Title { get; set; } + /// Name of the MCP server to restart. + [JsonPropertyName("serverName")] + public string ServerName { get; set; } = string.Empty; - /// An RFC 6570 URI template for constructing resource URIs. - [JsonPropertyName("uriTemplate")] - public string UriTemplate { get; set; } = string.Empty; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// One page of resource templates advertised by the named MCP server. +/// Server name for an individual MCP server stop. [Experimental(Diagnostics.Experimental)] -public sealed class McpResourcesListTemplatesResult +internal sealed class McpStopServerRequest { - /// Opaque cursor for the next page, if the server has more resource templates. - [JsonPropertyName("nextCursor")] - public string? NextCursor { get; set; } + /// Name of the MCP server to stop. + [JsonPropertyName("serverName")] + public string ServerName { get; set; } = string.Empty; - /// Resource templates advertised by the server (proxied MCP `resources/templates/list`). - [JsonPropertyName("resourceTemplates")] - public IList ResourceTemplates { get => field ??= []; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// MCP server whose resource templates to enumerate. +/// Registration parameters for an external MCP client. [Experimental(Diagnostics.Experimental)] -internal sealed class McpResourcesListTemplatesRequest +internal sealed class McpRegisterExternalClientRequest { - /// Opaque MCP pagination cursor from a prior `nextCursor` value. - [JsonPropertyName("cursor")] - public string? Cursor { get; set; } - - /// Name of the MCP server whose resource templates to enumerate. - [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] - [MinLength(1)] + /// Logical server name for the external client. [JsonPropertyName("serverName")] public string ServerName { get; set; } = string.Empty; @@ -7180,2577 +7391,2513 @@ internal sealed class McpResourcesListTemplatesRequest public string SessionId { get; set; } = string.Empty; } -/// Session plugin metadata, with name, marketplace, optional version, and enabled state. +/// Server name identifying the external client to remove. [Experimental(Diagnostics.Experimental)] -public sealed class Plugin +internal sealed class McpUnregisterExternalClientRequest { - /// Whether the plugin is currently enabled. - [JsonPropertyName("enabled")] - public bool Enabled { get; set; } - - /// Marketplace the plugin came from. - [JsonPropertyName("marketplace")] - public string Marketplace { get; set; } = string.Empty; - - /// Plugin name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Server name of the external client to unregister. + [JsonPropertyName("serverName")] + public string ServerName { get; set; } = string.Empty; - /// Installed version. - [JsonPropertyName("version")] - public string? Version { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Plugins installed for the session, with their enabled state and version metadata. +/// Whether the named MCP server is running. [Experimental(Diagnostics.Experimental)] -public sealed class PluginList +public sealed class McpIsServerRunningResult { - /// Installed plugins. - [JsonPropertyName("plugins")] - public IList Plugins { get => field ??= []; set; } + /// True if the server has an active client and transport. + [JsonPropertyName("running")] + public bool Running { get; set; } } -/// Identifies the target session. +/// Server name to check running status for. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionPluginsListRequest +internal sealed class McpIsServerRunningRequest { + /// Name of the MCP server to check. + [JsonPropertyName("serverName")] + public string ServerName { get; set; } = string.Empty; + /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Optional flags controlling which side effects the reload performs. +/// Indicates whether the pending MCP OAuth response was accepted. [Experimental(Diagnostics.Experimental)] -public sealed class PluginsReloadRequest +public sealed class McpOauthHandlePendingResult { - /// When true, skip repo-level hooks during the hook reload. Use before folder trust is confirmed; load them post-trust via `sessions.loadDeferredRepoHooks`. - [JsonPropertyName("deferRepoHooks")] - public bool? DeferRepoHooks { get; set; } - - /// Re-run custom-agent discovery after refreshing plugins. Defaults to true. - [JsonPropertyName("reloadCustomAgents")] - public bool? ReloadCustomAgents { get; set; } - - /// Re-discover and relaunch subprocess extensions (including plugin-shipped extensions) after refreshing plugins. Defaults to true. Has no effect when the session has no active extension controller (e.g. extensions were not requested for the session). - [JsonPropertyName("reloadExtensions")] - public bool? ReloadExtensions { get; set; } - - /// Re-load user, plugin, and (subject to `deferRepoHooks`) repo hooks. Defaults to true. Has no effect when the host has not registered a hook reloader (e.g. remote sessions). - [JsonPropertyName("reloadHooks")] - public bool? ReloadHooks { get; set; } - - /// Reload MCP server connections after refreshing plugins. Defaults to true. - [JsonPropertyName("reloadMcp")] - public bool? ReloadMcp { get; set; } + /// Whether the response was accepted. False if the request was unknown, timed out, or already resolved. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// Optional flags controlling which side effects the reload performs. +/// Host response to the pending OAuth request. +/// Polymorphic base type discriminated by kind. [Experimental(Diagnostics.Experimental)] -internal sealed class PluginsReloadRequestWithSession +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(McpOauthPendingRequestResponseToken), "token")] +[JsonDerivedType(typeof(McpOauthPendingRequestResponseCancelled), "cancelled")] +public partial class McpOauthPendingRequestResponse { - /// When true, skip repo-level hooks during the hook reload. Use before folder trust is confirmed; load them post-trust via `sessions.loadDeferredRepoHooks`. - [JsonPropertyName("deferRepoHooks")] - public bool? DeferRepoHooks { get; set; } + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; +} - /// Re-run custom-agent discovery after refreshing plugins. Defaults to true. - [JsonPropertyName("reloadCustomAgents")] - public bool? ReloadCustomAgents { get; set; } - /// Re-discover and relaunch subprocess extensions (including plugin-shipped extensions) after refreshing plugins. Defaults to true. Has no effect when the session has no active extension controller (e.g. extensions were not requested for the session). - [JsonPropertyName("reloadExtensions")] - public bool? ReloadExtensions { get; set; } +/// The token variant of . +[Experimental(Diagnostics.Experimental)] +public partial class McpOauthPendingRequestResponseToken : McpOauthPendingRequestResponse +{ + /// + [JsonIgnore] + public override string Kind => "token"; - /// Re-load user, plugin, and (subject to `deferRepoHooks`) repo hooks. Defaults to true. Has no effect when the host has not registered a hook reloader (e.g. remote sessions). - [JsonPropertyName("reloadHooks")] - public bool? ReloadHooks { get; set; } + /// Access token acquired by the SDK host. + [JsonPropertyName("accessToken")] + public required string AccessToken { get; set; } - /// Reload MCP server connections after refreshing plugins. Defaults to true. - [JsonPropertyName("reloadMcp")] - public bool? ReloadMcp { get; set; } + /// Token lifetime in seconds, if known. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("expiresIn")] + public long? ExpiresIn { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// OAuth token type. Defaults to Bearer when omitted. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("tokenType")] + public string? TokenType { get; set; } } -/// Short-lived, rotating credential the caller must send on every request, in addition to `apiKey` if one is present. Omitted when the endpoint does not require one. +/// The cancelled variant of . [Experimental(Diagnostics.Experimental)] -public sealed class ProviderSessionToken +public partial class McpOauthPendingRequestResponseCancelled : McpOauthPendingRequestResponse { - /// When the token expires, if known. Callers should refresh by calling `getEndpoint` again before this time, or reactively on any 401/403 response from `baseUrl`. - [JsonPropertyName("expiresAt")] - public DateTimeOffset? ExpiresAt { get; set; } - - /// HTTP header name the token must be sent under. - [JsonPropertyName("header")] - public string Header { get; set; } = string.Empty; - - /// The model the token is bound to, when applicable. When set, the token is only valid for requests against this model. - [JsonPropertyName("model")] - public string? Model { get; set; } - - /// The short-lived token value. - [JsonPropertyName("token")] - public string Token { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "cancelled"; } -/// A snapshot of the provider endpoint the session is currently configured to talk to. +/// Pending MCP OAuth request ID and host-provided token or cancellation response. [Experimental(Diagnostics.Experimental)] -public sealed class ProviderEndpoint +internal sealed class McpOauthHandlePendingRequest { - /// A credential the caller should use with this endpoint. Omitted only when the endpoint accepts unauthenticated requests. - [JsonPropertyName("apiKey")] - public string? ApiKey { get; set; } - - /// Base URL to pass to the LLM client library. - [Url] - [StringSyntax(StringSyntaxAttribute.Uri)] - [JsonPropertyName("baseUrl")] - public string BaseUrl { get; set; } = string.Empty; - - /// HTTP headers the caller must include on every outbound request. - [JsonPropertyName("headers")] - public IDictionary Headers { get => field ??= new Dictionary(); set; } - - /// Short-lived, rotating credential the caller must send on every request, in addition to `apiKey` if one is present. Omitted when the endpoint does not require one. - [JsonPropertyName("sessionToken")] - public ProviderSessionToken? SessionToken { get; set; } - - /// Transport to be used for provider requests. - [JsonPropertyName("transport")] - public ProviderEndpointTransport? Transport { get; set; } + /// OAuth request identifier from the mcp.oauth_required event. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; - /// Provider family. Matches the `type` field of a BYOK provider config. - [JsonPropertyName("type")] - public ProviderEndpointType Type { get; set; } + /// Host response to the pending OAuth request. + [JsonPropertyName("result")] + public McpOauthPendingRequestResponse Result { get => field ??= new(); set; } - /// Wire API to be used, when required for the provider type. - [JsonPropertyName("wireApi")] - public ProviderEndpointWireApi? WireApi { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Optional model identifier to scope the endpoint snapshot to. +/// Identifies the MCP server whose persisted OAuth credentials were updated. [Experimental(Diagnostics.Experimental)] -public sealed class ProviderGetEndpointRequest +internal sealed class McpOauthAuthenticationStateChangedRequest { - /// Model identifier the caller intends to use against the returned endpoint. Used to pick the correct wire shape. Omit to use whichever model the session is currently using. - [JsonPropertyName("modelId")] - public string? ModelId { get; set; } -} + /// Whether the target session must mint a session-scoped access token instead of reusing a shared access token persisted by another session. + [JsonPropertyName("refreshSessionToken")] + public bool? RefreshSessionToken { get; set; } -/// Optional model identifier to scope the endpoint snapshot to. -[Experimental(Diagnostics.Experimental)] -internal sealed class ProviderGetEndpointRequestWithSession -{ - /// Model identifier the caller intends to use against the returned endpoint. Used to pick the correct wire shape. Omit to use whichever model the session is currently using. - [JsonPropertyName("modelId")] - public string? ModelId { get; set; } + /// Name of the MCP server whose OAuth credentials were updated. Omit only when the host cannot identify the server. + [JsonPropertyName("serverName")] + public string? ServerName { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// The selectable model entries synthesized for the models added by this call. +/// OAuth authorization URL the caller should open, or empty when cached tokens already authenticated the server. [Experimental(Diagnostics.Experimental)] -public sealed class ProviderAddResult +public sealed class McpOauthLoginResult { - /// Synthesized selectable model entries for the newly added BYOK models, each under its provider-qualified selection id (`provider/id`). Empty when only providers were added. - [JsonPropertyName("models")] - public IList Models { get => field ??= []; set; } + /// URL the caller should open in a browser to complete OAuth. Omitted when cached tokens were still valid and no browser interaction was needed — the server is already reconnected in that case. When present, the runtime starts the callback listener before returning and continues the flow in the background; completion is signaled via session.mcp_server_status_changed. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] + [JsonPropertyName("authorizationUrl")] + public string? AuthorizationUrl { get; set; } } -/// A BYOK model definition referencing a named provider. +/// Remote MCP server name and optional overrides controlling reauthentication, OAuth client display name, callback success-page copy, and static OAuth client selection. [Experimental(Diagnostics.Experimental)] -public sealed class ProviderModelConfig +internal sealed class McpOauthLoginRequest { - /// Optional capability overrides (vision, tool_calls, reasoning, etc.). - [JsonPropertyName("capabilities")] - public ModelCapabilitiesOverride? Capabilities { get; set; } - - /// Provider-local model id, unique within its provider. The session-wide selection id (shown in the model list and passed to switchTo) is the provider-qualified `provider/id`. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// Optional override for the body text shown on the OAuth loopback callback success page. When omitted, the runtime applies a neutral fallback; callers driving interactive auth should pass surface-specific copy telling the user where to return. + [JsonPropertyName("callbackSuccessMessage")] + public string? CallbackSuccessMessage { get; set; } - /// Maximum context window tokens for the model. - [JsonPropertyName("maxContextWindowTokens")] - public double? MaxContextWindowTokens { get; set; } + /// Optional OAuth client ID override for this login. When set, the runtime uses this pre-registered static client instead of dynamic client registration. + [JsonPropertyName("clientId")] + public string? ClientId { get; set; } - /// Maximum output tokens for the model. - [JsonPropertyName("maxOutputTokens")] - public double? MaxOutputTokens { get; set; } + /// Optional override for the OAuth client display name shown on the consent screen. Applies to newly registered dynamic clients only — existing registrations keep the name they were created with. When omitted, the runtime applies a neutral fallback; callers driving interactive auth should pass their own surface-specific label so the consent screen matches the product the user sees. + [JsonPropertyName("clientName")] + public string? ClientName { get; set; } - /// Maximum prompt/input tokens for the model. - [JsonPropertyName("maxPromptTokens")] - public double? MaxPromptTokens { get; set; } + /// Optional OAuth client secret override for this login. The runtime treats this as an ephemeral host-owned secret, uses it for this authentication attempt and does not persist it. + [JsonPropertyName("clientSecret")] + public string? ClientSecret { get; set; } - /// Well-known base model id used for behavior/capability/config lookup. Defaults to `id`. - [JsonPropertyName("modelId")] - public string? ModelId { get; set; } + /// When true, clears any cached OAuth token for the server and runs a full new authorization. Use when the user explicitly wants to switch accounts or believes their session is stuck. + [JsonPropertyName("forceReauth")] + public bool? ForceReauth { get; set; } - /// Display name for model pickers. Defaults to the provider-qualified selection id (`provider/id`). - [JsonPropertyName("name")] - public string? Name { get; set; } + /// Optional OAuth grant type override for this login. Defaults to the server configuration, or authorization_code when no grant type is specified. + [JsonPropertyName("grantType")] + public McpOauthLoginGrantType? GrantType { get; set; } - /// Name of the NamedProviderConfig that serves this model. - [JsonPropertyName("provider")] - public string Provider { get; set; } = string.Empty; + /// Optional override indicating whether the static OAuth client is public. When false, the runtime treats it as confidential and uses the per-login clientSecret if provided, otherwise retrieving the client secret from the MCP OAuth secret store. + [JsonPropertyName("publicClient")] + public bool? PublicClient { get; set; } - /// The model name sent to the provider API for inference. Defaults to `id`. - [JsonPropertyName("wireModel")] - public string? WireModel { get; set; } -} + /// Name of the remote MCP server to authenticate. + [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] + [MinLength(1)] + [JsonPropertyName("serverName")] + public string ServerName { get; set; } = string.Empty; -/// Azure-specific provider options. -[Experimental(Diagnostics.Experimental)] -public sealed class ProviderConfigAzure -{ - /// API version. When set, uses the versioned deployment route. When omitted, uses the GA versionless v1 route. - [JsonPropertyName("apiVersion")] - public string? ApiVersion { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// A named BYOK provider connection (transport + credentials). +/// Indicates whether the pending MCP OAuth response was accepted. [Experimental(Diagnostics.Experimental)] -public sealed class NamedProviderConfig +public sealed class McpOauthRespondResult { - /// API key. Optional for local providers like Ollama. - [JsonPropertyName("apiKey")] - public string? ApiKey { get; set; } - - /// Azure-specific provider options. - [JsonPropertyName("azure")] - public ProviderConfigAzure? Azure { get; set; } - - /// API endpoint URL. - [JsonPropertyName("baseUrl")] - public string BaseUrl { get; set; } = string.Empty; - - /// Bearer token for authentication. Sets the Authorization header directly. Takes precedence over apiKey when both are set. - [JsonPropertyName("bearerToken")] - public string? BearerToken { get; set; } - - /// When true, the SDK client supplies bearer tokens on demand: the runtime calls the client-session `providerToken.getToken` callback before each request and applies the returned token as an `Authorization: Bearer <token>` header. This is the bearer/OAuth scheme used by Azure AD / managed-identity tokens and provider OAuth access tokens (including Anthropic's), not a provider-specific API-key header such as Anthropic's `x-api-key`. The token-acquiring function itself stays on the SDK side and is never serialized; only this flag crosses the wire. When set alongside `apiKey`/`bearerToken`, the callback takes precedence: the runtime applies the token returned by `providerToken.getToken` as the `Authorization: Bearer` header for each request and does not send the static credential. - [JsonPropertyName("hasBearerTokenProvider")] - public bool? HasBearerTokenProvider { get; set; } - - /// Custom HTTP headers to include in all outbound requests to the provider. - [JsonPropertyName("headers")] - public IDictionary? Headers { get; set; } - - /// Stable identifier referenced by BYOK model definitions. Must not contain '/'. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; - - /// Provider transport. Defaults to "http". - [JsonPropertyName("transport")] - public ProviderConfigTransport? Transport { get; set; } - - /// Provider type. Defaults to "openai" for generic OpenAI-compatible APIs. - [JsonPropertyName("type")] - public ProviderConfigType? Type { get; set; } - - /// Wire API format (openai/azure only). Defaults to "completions". - [JsonPropertyName("wireApi")] - public ProviderConfigWireApi? WireApi { get; set; } + /// Whether the response was accepted. False if the request was unknown, timed out, or already resolved. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// BYOK providers and/or models to add to the session's registry at runtime. Both fields are optional; provide providers, models, or both. +/// Pending MCP OAuth request id to respond to. [Experimental(Diagnostics.Experimental)] -internal sealed class ProviderAddRequest +internal sealed class McpOauthRespondRequest { - /// BYOK model definitions to register. Each must reference a provider that is already registered or included in this same call. Selection ids (`provider/id`) must be unique across the registry. - [JsonPropertyName("models")] - public IList? Models { get; set; } - - /// Named BYOK provider connections to register, additive to any providers already in the registry. Each name must be unique across the registry and must not contain '/'. - [JsonPropertyName("providers")] - public IList? Providers { get; set; } + /// OAuth request identifier from the mcp.oauth_required event. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Indicates whether the session options patch was applied successfully. +/// Indicates whether the pending MCP headers refresh response was accepted. [Experimental(Diagnostics.Experimental)] -public sealed class SessionUpdateOptionsResult +public sealed class McpHeadersHandlePendingHeadersRefreshRequestResult { - /// Number of hooks loaded from installed plugins, returned when installedPlugins is updated. - [JsonPropertyName("pluginHookCount")] - public long? PluginHookCount { get; set; } - - /// Whether the operation succeeded. + /// Whether the response was accepted. False if the request was unknown, timed out, or already resolved. [JsonPropertyName("success")] public bool Success { get; set; } } -/// Source descriptor for a `session.options.update` content-exclusion rule, with source name and type. +/// Host response: supply dynamic headers or decline this refresh. +/// Polymorphic base type discriminated by kind. [Experimental(Diagnostics.Experimental)] -public sealed class OptionsUpdateAdditionalContentExclusionPolicyRuleSource +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(McpHeadersHandlePendingHeadersRefreshRequestHeaders), "headers")] +[JsonDerivedType(typeof(McpHeadersHandlePendingHeadersRefreshRequestNone), "none")] +public partial class McpHeadersHandlePendingHeadersRefreshRequest { - /// Gets or sets the name value. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; +} - /// Gets or sets the type value. - [JsonPropertyName("type")] - public string Type { get; set; } = string.Empty; + +/// The headers variant of . +[Experimental(Diagnostics.Experimental)] +public partial class McpHeadersHandlePendingHeadersRefreshRequestHeaders : McpHeadersHandlePendingHeadersRefreshRequest +{ + /// + [JsonIgnore] + public override string Kind => "headers"; + + /// Headers to overlay onto the MCP request. Dynamic headers override static config headers but do not replace SDK-managed request headers. + [JsonPropertyName("headers")] + public required IDictionary Headers { get; set; } } -/// Single content-exclusion rule supplied to `session.options.update`, with paths, match conditions, and source. +/// The none variant of . [Experimental(Diagnostics.Experimental)] -public sealed class OptionsUpdateAdditionalContentExclusionPolicyRule +public partial class McpHeadersHandlePendingHeadersRefreshRequestNone : McpHeadersHandlePendingHeadersRefreshRequest { - /// Gets or sets the ifAnyMatch value. - [JsonPropertyName("ifAnyMatch")] - public IList? IfAnyMatch { get; set; } + /// + [JsonIgnore] + public override string Kind => "none"; +} - /// Gets or sets the ifNoneMatch value. - [JsonPropertyName("ifNoneMatch")] - public IList? IfNoneMatch { get; set; } +/// MCP headers refresh request id and the host response. +[Experimental(Diagnostics.Experimental)] +internal sealed class McpHeadersHandlePendingHeadersRefreshRequestRequest +{ + /// Headers refresh request identifier from mcp.headers_refresh_required. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; - /// Gets or sets the paths value. - [JsonPropertyName("paths")] - public IList Paths { get => field ??= []; set; } + /// Host response: supply dynamic headers or decline this refresh. + [JsonPropertyName("result")] + public McpHeadersHandlePendingHeadersRefreshRequest Result { get => field ??= new(); set; } - /// Source descriptor for a `session.options.update` content-exclusion rule, with source name and type. - [JsonPropertyName("source")] - public OptionsUpdateAdditionalContentExclusionPolicyRuleSource Source { get => field ??= new(); set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Content-exclusion policy supplied to `session.options.update`, with rules, last-updated data, and scope. +/// MCP Apps resource content with URI, optional MIME type, text or base64 blob, and resource metadata. [Experimental(Diagnostics.Experimental)] -public sealed class OptionsUpdateAdditionalContentExclusionPolicy +public sealed class McpAppsResourceContent { - /// Gets or sets the last_updated_at value. - [JsonPropertyName("last_updated_at")] - public JsonElement LastUpdatedAt { get; set; } + /// Resource-level metadata (CSP, permissions, etc.). + [JsonPropertyName("_meta")] + public IDictionary? Meta { get; set; } - /// Gets or sets the rules value. - [JsonPropertyName("rules")] - public IList Rules { get => field ??= []; set; } + /// Base64-encoded binary content. + [JsonPropertyName("blob")] + public string? Blob { get; set; } - /// Allowed values for the `OptionsUpdateAdditionalContentExclusionPolicyScope` enumeration. - [JsonPropertyName("scope")] - public OptionsUpdateAdditionalContentExclusionPolicyScope Scope { get; set; } + /// MIME type of the content. + [JsonPropertyName("mimeType")] + public string? MimeType { get; set; } + + /// Text content (e.g. HTML). + [JsonPropertyName("text")] + public string? Text { get; set; } + + /// The resource URI (typically ui://...). + [JsonPropertyName("uri")] + public string Uri { get; set; } = string.Empty; } -/// Options scoped to the built-in CAPI (Copilot API) provider. +/// Resource contents returned by the MCP server. [Experimental(Diagnostics.Experimental)] -public sealed class CapiSessionOptions +public sealed class McpAppsReadResourceResult { - /// Whether to use WebSocket transport for the CAPI Responses API. Enabled by default when the model advertises `ws:/responses` support; set to `false` to force the HTTP Responses transport in environments where WebSockets are blocked (e.g. behind a proxy). Setting this to `false` is equivalent to the `COPILOT_CLI_DISABLE_WEBSOCKET_RESPONSES` environment variable. - [JsonPropertyName("enableWebSocketResponses")] - public bool? EnableWebSocketResponses { get; set; } + /// Resource contents returned by the server. + [JsonPropertyName("contents")] + public IList Contents { get => field ??= []; set; } } -/// Installed plugin record for a session, with marketplace, version, install time, enabled state, cache path, and source. +/// MCP server and resource URI to fetch. [Experimental(Diagnostics.Experimental)] -public sealed class SessionInstalledPlugin +internal sealed class McpAppsReadResourceRequest { - /// Path where the plugin is cached locally. - [JsonPropertyName("cache_path")] - public string? CachePath { get; set; } + /// Name of the MCP server hosting the resource. + [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] + [MinLength(1)] + [JsonPropertyName("serverName")] + public string ServerName { get; set; } = string.Empty; - /// Whether the plugin is currently enabled. - [JsonPropertyName("enabled")] - public bool Enabled { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; - /// Installation timestamp (ISO-8601). - [JsonPropertyName("installed_at")] - public string InstalledAt { get; set; } = string.Empty; + /// Resource URI (typically ui://...). + [JsonPropertyName("uri")] + public string Uri { get; set; } = string.Empty; +} - /// Marketplace the plugin came from (empty string for direct repo installs). - [JsonPropertyName("marketplace")] - public string Marketplace { get; set; } = string.Empty; +/// App-callable tools from the named MCP server. +[Experimental(Diagnostics.Experimental)] +public sealed class McpAppsListToolsResult +{ + /// App-callable tools from the server. + [JsonPropertyName("tools")] + public IList> Tools { get => field ??= []; set; } +} - /// Plugin name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; +/// MCP server to list app-callable tools for. +[Experimental(Diagnostics.Experimental)] +internal sealed class McpAppsListToolsRequest +{ + /// **Required.** Server whose ui:// view issued the request. Per SEP-1865 ('callable by the app from this server only'), the call is rejected when this differs from `serverName`, and rejected outright when missing. + [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] + [MinLength(1)] + [JsonPropertyName("originServerName")] + public string OriginServerName { get; set; } = string.Empty; - /// Source descriptor for direct repo installs (when marketplace is empty). - [JsonPropertyName("source")] - public JsonElement? Source { get; set; } + /// MCP server hosting the app. + [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] + [MinLength(1)] + [JsonPropertyName("serverName")] + public string ServerName { get; set; } = string.Empty; - /// Installed version, if known. - [JsonPropertyName("version")] - public string? Version { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Custom model-provider configuration (BYOK). +/// MCP server, tool name, and arguments to invoke from an MCP App view. [Experimental(Diagnostics.Experimental)] -public sealed class ProviderConfig +internal sealed class McpAppsCallToolRequest { - /// API key. Optional for local providers like Ollama. - [JsonPropertyName("apiKey")] - public string? ApiKey { get; set; } - - /// Azure-specific provider options. - [JsonPropertyName("azure")] - public ProviderConfigAzure? Azure { get; set; } - - /// API endpoint URL. - [JsonPropertyName("baseUrl")] - public string BaseUrl { get; set; } = string.Empty; + /// Tool arguments. + [JsonPropertyName("arguments")] + public IDictionary? Arguments { get; set; } - /// Bearer token for authentication. Sets the Authorization header directly. Takes precedence over apiKey when both are set. - [JsonPropertyName("bearerToken")] - public string? BearerToken { get; set; } + /// **Required.** Server whose ui:// view issued the request. Per SEP-1865 ('callable by the app from this server only'), the call is rejected when this differs from `serverName`, and rejected outright when missing. + [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] + [MinLength(1)] + [JsonPropertyName("originServerName")] + public string OriginServerName { get; set; } = string.Empty; - /// When true, the SDK client supplies bearer tokens on demand: the runtime calls the client-session `providerToken.getToken` callback before each request and applies the returned token as an `Authorization: Bearer <token>` header. This is the bearer/OAuth scheme used by Azure AD / managed-identity tokens and provider OAuth access tokens (including Anthropic's), not a provider-specific API-key header such as Anthropic's `x-api-key`. The token-acquiring function itself stays on the SDK side and is never serialized; only this flag crosses the wire. When set alongside `apiKey`/`bearerToken`, the callback takes precedence: the runtime applies the token returned by `providerToken.getToken` as the `Authorization: Bearer` header for each request and does not send the static credential. - [JsonPropertyName("hasBearerTokenProvider")] - public bool? HasBearerTokenProvider { get; set; } + /// MCP server hosting the tool. + [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] + [MinLength(1)] + [JsonPropertyName("serverName")] + public string ServerName { get; set; } = string.Empty; - /// Custom HTTP headers to include in all outbound requests to the provider. - [JsonPropertyName("headers")] - public IDictionary? Headers { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; - /// Maximum context window tokens for the model. - [JsonPropertyName("maxContextWindowTokens")] - public double? MaxContextWindowTokens { get; set; } + /// MCP tool name. + [JsonPropertyName("toolName")] + public string ToolName { get; set; } = string.Empty; +} - /// Maximum output tokens for the model. - [JsonPropertyName("maxOutputTokens")] - public double? MaxOutputTokens { get; set; } +/// Host context advertised to MCP App guests. +[Experimental(Diagnostics.Experimental)] +public sealed class McpAppsSetHostContextDetails +{ + /// Display modes the host supports. + [JsonPropertyName("availableDisplayModes")] + public IList? AvailableDisplayModes { get; set; } - /// Maximum prompt/input tokens for the model. - [JsonPropertyName("maxPromptTokens")] - public double? MaxPromptTokens { get; set; } + /// Current display mode (SEP-1865). + [JsonPropertyName("displayMode")] + public McpAppsSetHostContextDetailsDisplayMode? DisplayMode { get; set; } - /// Well-known model ID used for capability lookup. When set, agent behavior config and token limits are inferred from this model. - [JsonPropertyName("modelId")] - public string? ModelId { get; set; } + /// BCP-47 locale, e.g. 'en-US'. + [JsonPropertyName("locale")] + public string? Locale { get; set; } - /// Provider transport. Defaults to "http". - [JsonPropertyName("transport")] - public ProviderConfigTransport? Transport { get; set; } + /// Platform type for responsive design. + [JsonPropertyName("platform")] + public McpAppsSetHostContextDetailsPlatform? Platform { get; set; } - /// Provider type. Defaults to "openai" for generic OpenAI-compatible APIs. - [JsonPropertyName("type")] - public ProviderConfigType? Type { get; set; } + /// UI theme preference per SEP-1865. + [JsonPropertyName("theme")] + public McpAppsSetHostContextDetailsTheme? Theme { get; set; } - /// Wire API format (openai/azure only). Defaults to "completions". - [JsonPropertyName("wireApi")] - public ProviderConfigWireApi? WireApi { get; set; } + /// IANA timezone, e.g. 'America/New_York'. + [JsonPropertyName("timeZone")] + public string? TimeZone { get; set; } - /// The model identifier sent to the provider API for inference (the "wire" model), as opposed to modelId which is the well-known base. - [JsonPropertyName("wireModel")] - public string? WireModel { get; set; } + /// Host application identifier. + [JsonPropertyName("userAgent")] + public string? UserAgent { get; set; } } -/// macOS seatbelt experimental options. +/// Host context to advertise to MCP App guests. [Experimental(Diagnostics.Experimental)] -public sealed class SandboxConfigUserPolicyExperimentalSeatbelt +internal sealed class McpAppsSetHostContextRequest { - /// Whether the macOS seatbelt profile may access the keychain. - [JsonPropertyName("keychainAccess")] - public bool? KeychainAccess { get; set; } -} + /// Host context advertised to MCP App guests. + [JsonPropertyName("context")] + public McpAppsSetHostContextDetails Context { get => field ??= new(); set; } -/// Platform-specific experimental policy fields. -[Experimental(Diagnostics.Experimental)] -public sealed class SandboxConfigUserPolicyExperimental -{ - /// macOS seatbelt experimental options. - [JsonPropertyName("seatbelt")] - public SandboxConfigUserPolicyExperimentalSeatbelt? Seatbelt { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Filesystem rules to merge into the base policy. +/// Current host context. [Experimental(Diagnostics.Experimental)] -public sealed class SandboxConfigUserPolicyFilesystem +public sealed class McpAppsHostContextDetails { - /// Whether to clear the policy when the session exits. - [JsonPropertyName("clearPolicyOnExit")] - public bool? ClearPolicyOnExit { get; set; } + /// Display modes the host supports. + [JsonPropertyName("availableDisplayModes")] + public IList? AvailableDisplayModes { get; set; } - /// Paths explicitly denied. - [JsonPropertyName("deniedPaths")] - public IList? DeniedPaths { get; set; } + /// Current display mode (SEP-1865). + [JsonPropertyName("displayMode")] + public McpAppsHostContextDetailsDisplayMode? DisplayMode { get; set; } - /// Paths granted read-only access. - [JsonPropertyName("readonlyPaths")] - public IList? ReadonlyPaths { get; set; } + /// BCP-47 locale, e.g. 'en-US'. + [JsonPropertyName("locale")] + public string? Locale { get; set; } - /// Paths granted read/write access. - [JsonPropertyName("readwritePaths")] - public IList? ReadwritePaths { get; set; } + /// Platform type for responsive design. + [JsonPropertyName("platform")] + public McpAppsHostContextDetailsPlatform? Platform { get; set; } + + /// UI theme preference per SEP-1865. + [JsonPropertyName("theme")] + public McpAppsHostContextDetailsTheme? Theme { get; set; } + + /// IANA timezone, e.g. 'America/New_York'. + [JsonPropertyName("timeZone")] + public string? TimeZone { get; set; } + + /// Host application identifier. + [JsonPropertyName("userAgent")] + public string? UserAgent { get; set; } } -/// Network rules to merge into the base policy. +/// Current host context advertised to MCP App guests. [Experimental(Diagnostics.Experimental)] -public sealed class SandboxConfigUserPolicyNetwork +public sealed class McpAppsHostContext { - /// Whether traffic to local/loopback addresses is allowed. - [JsonPropertyName("allowLocalNetwork")] - public bool? AllowLocalNetwork { get; set; } - - /// Whether outbound network traffic is allowed at all. - [JsonPropertyName("allowOutbound")] - public bool? AllowOutbound { get; set; } + /// Current host context. + [JsonPropertyName("context")] + public McpAppsHostContextDetails Context { get => field ??= new(); set; } } -/// macOS seatbelt-specific options. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -public sealed class SandboxConfigUserPolicySeatbelt +internal sealed class SessionMcpAppsGetHostContextRequest { - /// Whether the macOS seatbelt profile may access the keychain. - [JsonPropertyName("keychainAccess")] - public bool? KeychainAccess { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// User-managed sandbox policy fragment merged into the auto-discovered base policy. +/// Capability negotiation snapshot. [Experimental(Diagnostics.Experimental)] -public sealed class SandboxConfigUserPolicy +public sealed class McpAppsDiagnoseCapability { - /// Deprecated legacy location for `seatbelt`; read only when the top-level `seatbelt` is absent. - [JsonPropertyName("experimental")] - public SandboxConfigUserPolicyExperimental? Experimental { get; set; } + /// Whether the runtime advertises `extensions.io.modelcontextprotocol/ui` to MCP servers. + [JsonPropertyName("advertised")] + public bool Advertised { get; set; } - /// Filesystem rules to merge into the base policy. - [JsonPropertyName("filesystem")] - public SandboxConfigUserPolicyFilesystem? Filesystem { get; set; } + /// Whether the MCP_APPS feature flag (or COPILOT_MCP_APPS env override) is on. + [JsonPropertyName("featureFlagEnabled")] + public bool FeatureFlagEnabled { get; set; } - /// Network rules to merge into the base policy. - [JsonPropertyName("network")] - public SandboxConfigUserPolicyNetwork? Network { get; set; } - - /// macOS seatbelt options to merge into the base policy. - [JsonPropertyName("seatbelt")] - public SandboxConfigUserPolicySeatbelt? Seatbelt { get; set; } + /// Whether the session has the `mcp-apps` capability. + [JsonPropertyName("sessionHasMcpApps")] + public bool SessionHasMcpApps { get; set; } } -/// Resolved sandbox configuration. +/// What the server returned for this session. [Experimental(Diagnostics.Experimental)] -public sealed class SandboxConfig +public sealed class McpAppsDiagnoseServer { - /// Whether to auto-add the current working directory to readwritePaths. Default: true. - [JsonPropertyName("addCurrentWorkingDirectory")] - public bool? AddCurrentWorkingDirectory { get; set; } - - /// Whether sandboxing is enabled for the session. - [JsonPropertyName("enabled")] - public bool Enabled { get; set; } + /// Whether the named server is currently connected. + [JsonPropertyName("connected")] + public bool Connected { get; set; } - /// Whether to export `GH_TOKEN` so the `gh` CLI authenticates inside the sandbox without the OS keyring the sandbox blocks. Default: false (opt-in). - [JsonPropertyName("ghAuth")] - public bool? GhAuth { get; set; } + /// Up to 5 tool names with `_meta.ui` for quick inspection. + [JsonPropertyName("sampleToolNames")] + public IList SampleToolNames { get => field ??= []; set; } - /// Whether to inject the Copilot GitHub token as an `http.<host>.extraheader` so authenticated HTTPS git works inside the sandbox without the shell-based credential helper the sandbox blocks. Default: false (opt-in). - [JsonPropertyName("gitAuth")] - public bool? GitAuth { get; set; } + /// Total tools returned by the server's tools/list. + [JsonPropertyName("toolCount")] + public double ToolCount { get; set; } - /// User-managed sandbox policy fragment merged into the auto-discovered base policy. - [JsonPropertyName("userPolicy")] - public SandboxConfigUserPolicy? UserPolicy { get; set; } + /// Tools whose `_meta.ui` is populated (resourceUri and/or visibility set). + [JsonPropertyName("toolsWithUiMeta")] + public double ToolsWithUiMeta { get; set; } } -/// Patch of mutable session options to apply to the running session. +/// Diagnostic snapshot of MCP Apps wiring for the named server. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionUpdateOptionsParams +public sealed class McpAppsDiagnoseResult { - /// Additional content-exclusion policies to merge into the session's policy set. - [Experimental(Diagnostics.Experimental)] - [JsonPropertyName("additionalContentExclusionPolicies")] - public IList? AdditionalContentExclusionPolicies { get; set; } + /// Capability negotiation snapshot. + [JsonPropertyName("capability")] + public McpAppsDiagnoseCapability Capability { get => field ??= new(); set; } - /// Runtime context discriminator (e.g., `cli`, `actions`). - [JsonPropertyName("agentContext")] - public string? AgentContext { get; set; } + /// What the server returned for this session. + [JsonPropertyName("server")] + public McpAppsDiagnoseServer Server { get => field ??= new(); set; } +} - /// Whether to include instructions from every MCP server in the system prompt instead of only allowlisted servers. - [JsonPropertyName("allowAllMcpServerInstructions")] - public bool? AllowAllMcpServerInstructions { get; set; } +/// MCP server to diagnose MCP Apps wiring for. +[Experimental(Diagnostics.Experimental)] +internal sealed class McpAppsDiagnoseRequest +{ + /// MCP server to probe. + [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] + [MinLength(1)] + [JsonPropertyName("serverName")] + public string ServerName { get; set; } = string.Empty; - /// Whether to disable the `ask_user` tool (encourages autonomous behavior). - [JsonPropertyName("askUserDisabled")] - public bool? AskUserDisabled { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Allowlist of tool names available to this session. - [JsonPropertyName("availableTools")] - public IList? AvailableTools { get; set; } +/// MCP resource content with URI, optional MIME type, text or base64 blob, and resource metadata. +[Experimental(Diagnostics.Experimental)] +public sealed class McpResourceContent +{ + /// Resource-level metadata (CSP, permissions, etc.). + [JsonPropertyName("_meta")] + public IDictionary? Meta { get; set; } - /// Options scoped to the built-in CAPI (Copilot API) provider. - [JsonPropertyName("capi")] - public CapiSessionOptions? Capi { get; set; } + /// Base64-encoded binary content. + [JsonPropertyName("blob")] + public string? Blob { get; set; } - /// Identifier of the client driving the session. - [JsonPropertyName("clientName")] - public string? ClientName { get; set; } + /// MIME type of the content. + [JsonPropertyName("mimeType")] + public string? MimeType { get; set; } - /// Whether to include the `Co-authored-by` trailer in commit messages. - [JsonPropertyName("coauthorEnabled")] - public bool? CoauthorEnabled { get; set; } + /// Text content (e.g. HTML). + [JsonPropertyName("text")] + public string? Text { get; set; } - /// Context tier for models with tiered pricing. The session uses this to derive effective `modelCapabilitiesOverrides` so compaction, truncation, token display, and request limits honor the selected tier. - [JsonPropertyName("contextTier")] - public OptionsUpdateContextTier? ContextTier { get; set; } + /// The resource URI. + [JsonPropertyName("uri")] + public string Uri { get; set; } = string.Empty; +} - /// Whether to allow auto-mode continuation across turns. - [JsonPropertyName("continueOnAutoMode")] - public bool? ContinueOnAutoMode { get; set; } +/// Resource contents returned by the MCP server. +[Experimental(Diagnostics.Experimental)] +public sealed class McpResourcesReadResult +{ + /// Resource contents returned by the server. + [JsonPropertyName("contents")] + public IList Contents { get => field ??= []; set; } +} - /// Override URL for the Copilot API endpoint. - [JsonPropertyName("copilotUrl")] - public string? CopilotUrl { get; set; } +/// MCP server and resource URI to fetch. +[Experimental(Diagnostics.Experimental)] +internal sealed class McpResourcesReadRequest +{ + /// Name of the MCP server hosting the resource. + [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] + [MinLength(1)] + [JsonPropertyName("serverName")] + public string ServerName { get; set; } = string.Empty; - /// Whether to default custom agents to local-only execution. - [JsonPropertyName("customAgentsLocalOnly")] - public bool? CustomAgentsLocalOnly { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; - /// Instruction source IDs to exclude from the system prompt. - [JsonPropertyName("disabledInstructionSources")] - public IList? DisabledInstructionSources { get; set; } + /// Resource URI. + [JsonPropertyName("uri")] + public string Uri { get; set; } = string.Empty; +} - /// Skill IDs that should be excluded from this session. - [JsonPropertyName("disabledSkills")] - public IList? DisabledSkills { get; set; } +/// Standard MCP resource annotations plus preserved non-standard annotation fields. +[Experimental(Diagnostics.Experimental)] +public sealed class McpResourceAnnotations +{ + /// Server-provided non-standard annotation fields preserved from the MCP response. + [JsonPropertyName("additionalProperties")] + public IDictionary? AdditionalProperties { get; set; } - /// Whether to enable loading of `.github/hooks/` filesystem hooks. Separate from the SDK callback hook mechanism. - [JsonPropertyName("enableFileHooks")] - public bool? EnableFileHooks { get; set; } + /// Intended audience roles for this resource. + [JsonPropertyName("audience")] + public IList? Audience { get; set; } - /// Whether to enable host git operations (context resolution, child repo scanning, git info in system prompt). - [JsonPropertyName("enableHostGitOperations")] - public bool? EnableHostGitOperations { get; set; } + /// Last-modified timestamp hint. + [JsonPropertyName("lastModified")] + public string? LastModified { get; set; } - /// Whether to discover custom instructions on demand after successful file views (AGENTS.md / CLAUDE.md / .github/copilot-instructions.md surfacing). Combined with `skipCustomInstructions` and the runtime-side `ON_DEMAND_INSTRUCTIONS` feature flag. - [JsonPropertyName("enableOnDemandInstructionDiscovery")] - public bool? EnableOnDemandInstructionDiscovery { get; set; } + /// Priority hint for model/client use. + [JsonPropertyName("priority")] + public double? Priority { get; set; } +} - /// Whether to surface reasoning-summary events from the model. - [JsonPropertyName("enableReasoningSummaries")] - public bool? EnableReasoningSummaries { get; set; } +/// A resource icon descriptor plus preserved non-standard icon fields. +[Experimental(Diagnostics.Experimental)] +public sealed class McpResourceIcon +{ + /// Server-provided non-standard icon fields preserved from the MCP response. + [JsonPropertyName("additionalProperties")] + public IDictionary? AdditionalProperties { get; set; } - /// Whether shell-script safety heuristics are enabled. - [JsonPropertyName("enableScriptSafety")] - public bool? EnableScriptSafety { get; set; } + /// Icon MIME type, when known. + [JsonPropertyName("mimeType")] + public string? MimeType { get; set; } - /// Whether to enable cross-session store writes and reads. - [JsonPropertyName("enableSessionStore")] - public bool? EnableSessionStore { get; set; } + /// Icon sizes hint. + [JsonPropertyName("sizes")] + public string? Sizes { get; set; } - /// Whether to enable skill directory scanning and loading. Falls back to enableConfigDiscovery when unset. - [JsonPropertyName("enableSkills")] - public bool? EnableSkills { get; set; } + /// Icon URI. + [JsonPropertyName("src")] + public string Src { get; set; } = string.Empty; - /// Whether to stream model responses. - [JsonPropertyName("enableStreaming")] - public bool? EnableStreaming { get; set; } + /// Theme hint for this icon. + [JsonPropertyName("theme")] + public string? Theme { get; set; } +} - /// How env values are passed to MCP servers (`direct` inlines literal values; `indirect` resolves at launch). - [JsonPropertyName("envValueMode")] - public OptionsUpdateEnvValueMode? EnvValueMode { get; set; } +/// An MCP resource descriptor (spec `Resource`): URI, name, and optional title, description, MIME type, size, icons, annotations, and metadata. Server-provided fields outside the standard descriptor shape are exposed under `additionalProperties`. +[Experimental(Diagnostics.Experimental)] +public sealed class McpResource +{ + /// Resource-level metadata. + [JsonPropertyName("_meta")] + public IDictionary? Meta { get; set; } - /// Override directory for the session-events log. When unset, the runtime's default events log directory is used. - [JsonPropertyName("eventsLogDirectory")] - public string? EventsLogDirectory { get; set; } + /// Server-provided non-standard descriptor fields preserved from the MCP response. + [JsonPropertyName("additionalProperties")] + public IDictionary? AdditionalProperties { get; set; } - /// Built-in subagent names to exclude from this session. Excluded built-ins are hidden from agent discovery and cannot be dispatched unless a custom agent with the same name is available. - [JsonPropertyName("excludedBuiltinAgents")] - public IList? ExcludedBuiltinAgents { get; set; } + /// Model/client annotations associated with this resource. + [JsonPropertyName("annotations")] + public McpResourceAnnotations? Annotations { get; set; } - /// Denylist of tool names for this session. - [JsonPropertyName("excludedTools")] - public IList? ExcludedTools { get; set; } + /// Optional description of what this resource represents. + [JsonPropertyName("description")] + public string? Description { get; set; } - /// Map of feature-flag IDs to their boolean enabled state. - [JsonPropertyName("featureFlags")] - public IDictionary? FeatureFlags { get; set; } + /// Icons associated with this resource. + [JsonPropertyName("icons")] + public IList? Icons { get; set; } - /// Built-in subagent names to include in this session. When specified, only these built-ins are available, subject to runtime availability and exclusions. Custom agents with the same name remain available. Set to null to remove the allowlist restriction. - [JsonPropertyName("includedBuiltinAgents")] - public IList? IncludedBuiltinAgents { get; set; } + /// MIME type of the resource, if known. + [JsonPropertyName("mimeType")] + public string? MimeType { get; set; } - /// Full set of installed plugins for the session. Replaces the existing list; the runtime invalidates the skills cache only when the list materially changes. - [JsonPropertyName("installedPlugins")] - public IList? InstalledPlugins { get; set; } + /// The programmatic name of the resource. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// Stable integration identifier used for analytics and rate-limit attribution. - [JsonPropertyName("integrationId")] - public string? IntegrationId { get; set; } - - /// Whether experimental capabilities are enabled. - [JsonPropertyName("isExperimentalMode")] - public bool? IsExperimentalMode { get; set; } - - /// Whether interactive shell sessions are logged. - [JsonPropertyName("logInteractiveShells")] - public bool? LogInteractiveShells { get; set; } - - /// Identifier sent to LSP-style integrations. - [JsonPropertyName("lspClientName")] - public string? LspClientName { get; set; } - - /// Whether to expose the `manage_schedule` tool to the agent. The runtime always owns the per-session schedule registry; this flag only controls tool exposure (typically gated to staff users). - [JsonPropertyName("manageScheduleEnabled")] - public bool? ManageScheduleEnabled { get; set; } - - /// Maximum decoded byte size of a single model-facing binary tool result (e.g. an image) persisted inline in session events and re-presented to the model on later turns / resume. Larger results are persisted as a metadata-only marker and shown to the model as a short text note. Defaults to 10 MB. - [JsonPropertyName("maxInlineBinaryBytes")] - public long? MaxInlineBinaryBytes { get; set; } - - /// The model ID to use for assistant turns. - [JsonPropertyName("model")] - public string? Model { get; set; } - - /// Per-property model capability overrides for the selected model. - [JsonPropertyName("modelCapabilitiesOverrides")] - public ModelCapabilitiesOverride? ModelCapabilitiesOverrides { get; set; } - - /// Organization-level custom instructions to inject into the system prompt. - [JsonPropertyName("organizationCustomInstructions")] - public string? OrganizationCustomInstructions { get; set; } + /// Resource size in bytes, when known. + [JsonPropertyName("size")] + public long? Size { get; set; } - /// Custom model-provider configuration (BYOK). - [JsonPropertyName("provider")] - public ProviderConfig? Provider { get; set; } + /// Optional human-readable display title. + [JsonPropertyName("title")] + public string? Title { get; set; } - /// Reasoning effort for the selected model (model-defined enum). - [JsonPropertyName("reasoningEffort")] - public string? ReasoningEffort { get; set; } + /// The resource URI (e.g. ui://... or file:///...). + [JsonPropertyName("uri")] + public string Uri { get; set; } = string.Empty; +} - /// Reasoning summary mode for supported model clients. - [JsonPropertyName("reasoningSummary")] - public OptionsUpdateReasoningSummary? ReasoningSummary { get; set; } +/// One page of resources advertised by the named MCP server. +[Experimental(Diagnostics.Experimental)] +public sealed class McpResourcesListResult +{ + /// Opaque cursor for the next page, if the server has more resources. + [JsonPropertyName("nextCursor")] + public string? NextCursor { get; set; } - /// Whether the session is running in an interactive UI. - [JsonPropertyName("runningInInteractiveMode")] - public bool? RunningInInteractiveMode { get; set; } + /// Resources advertised by the server (proxied MCP `resources/list`). + [JsonPropertyName("resources")] + public IList Resources { get => field ??= []; set; } +} - /// Resolved sandbox configuration. - [JsonPropertyName("sandboxConfig")] - public SandboxConfig? SandboxConfig { get; set; } +/// MCP server whose resources to enumerate. +[Experimental(Diagnostics.Experimental)] +internal sealed class McpResourcesListRequest +{ + /// Opaque MCP pagination cursor from a prior `nextCursor` value. + [JsonPropertyName("cursor")] + public string? Cursor { get; set; } - /// Replaces the session's capability set with the given list. Use to enable or disable capabilities mid-session (e.g., remove `memory` for reproducible scripted runs). Omit the field to leave the existing capability set unchanged. - [JsonPropertyName("sessionCapabilities")] - public IList? SessionCapabilities { get; set; } + /// Name of the MCP server whose resources to enumerate. + [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] + [MinLength(1)] + [JsonPropertyName("serverName")] + public string ServerName { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; +} - /// Optional session limits. Pass null to clear the session limits. - [JsonPropertyName("sessionLimits")] - public SessionLimitsConfig? SessionLimits { get; set; } +/// An MCP resource template descriptor (spec `ResourceTemplate`): an RFC 6570 URI template, name, and optional title, description, MIME type, icons, annotations, and metadata. Server-provided fields outside the standard descriptor shape are exposed under `additionalProperties`. +[Experimental(Diagnostics.Experimental)] +public sealed class McpResourceTemplate +{ + /// Resource-template-level metadata. + [JsonPropertyName("_meta")] + public IDictionary? Meta { get; set; } - /// Shell init profile (`None` or `NonInteractive`). - [JsonPropertyName("shellInitProfile")] - public string? ShellInitProfile { get; set; } + /// Server-provided non-standard descriptor fields preserved from the MCP response. + [JsonPropertyName("additionalProperties")] + public IDictionary? AdditionalProperties { get; set; } - /// Per-shell process flags (e.g., `pwsh` arguments). - [JsonPropertyName("shellProcessFlags")] - public IList? ShellProcessFlags { get; set; } + /// Model/client annotations associated with this template. + [JsonPropertyName("annotations")] + public McpResourceAnnotations? Annotations { get; set; } - /// Additional directories to search for skills. - [JsonPropertyName("skillDirectories")] - public IList? SkillDirectories { get; set; } + /// Optional description of what this template is for. + [JsonPropertyName("description")] + public string? Description { get; set; } - /// Whether to skip loading custom instruction sources. - [JsonPropertyName("skipCustomInstructions")] - public bool? SkipCustomInstructions { get; set; } + /// Icons associated with resources matching this template. + [JsonPropertyName("icons")] + public IList? Icons { get; set; } - /// Whether to skip embedding retrieval pipeline initialization and execution. - [JsonPropertyName("skipEmbeddingRetrieval")] - public bool? SkipEmbeddingRetrieval { get; set; } + /// MIME type for resources matching this template, if uniform. + [JsonPropertyName("mimeType")] + public string? MimeType { get; set; } - /// When true, the selected custom agent's prompt is not injected into the user message (skill context is still injected). Used by automation triggers where the agent prompt is already in the problem statement. - [JsonPropertyName("suppressCustomAgentPrompt")] - public bool? SuppressCustomAgentPrompt { get; set; } + /// The programmatic name of the resource template. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// Controls how availableTools (allowlist) and excludedTools (denylist) combine when both are set. - [JsonPropertyName("toolFilterPrecedence")] - public OptionsUpdateToolFilterPrecedence? ToolFilterPrecedence { get; set; } + /// Optional human-readable display title. + [JsonPropertyName("title")] + public string? Title { get; set; } - /// Optional path for trajectory output. - [JsonPropertyName("trajectoryFile")] - public string? TrajectoryFile { get; set; } + /// An RFC 6570 URI template for constructing resource URIs. + [JsonPropertyName("uriTemplate")] + public string UriTemplate { get; set; } = string.Empty; +} - /// Output verbosity level for supported models. - [JsonPropertyName("verbosity")] - public Verbosity? Verbosity { get; set; } +/// One page of resource templates advertised by the named MCP server. +[Experimental(Diagnostics.Experimental)] +public sealed class McpResourcesListTemplatesResult +{ + /// Opaque cursor for the next page, if the server has more resource templates. + [JsonPropertyName("nextCursor")] + public string? NextCursor { get; set; } - /// Absolute working-directory path for shell tools. - [JsonPropertyName("workingDirectory")] - public string? WorkingDirectory { get; set; } + /// Resource templates advertised by the server (proxied MCP `resources/templates/list`). + [JsonPropertyName("resourceTemplates")] + public IList ResourceTemplates { get => field ??= []; set; } } -/// Parameters for (re)loading the merged LSP configuration set. +/// MCP server whose resource templates to enumerate. [Experimental(Diagnostics.Experimental)] -internal sealed class LspInitializeRequest +internal sealed class McpResourcesListTemplatesRequest { - /// Force re-initialization even when LSP configs were already loaded for the working directory. - [JsonPropertyName("force")] - public bool? Force { get; set; } + /// Opaque MCP pagination cursor from a prior `nextCursor` value. + [JsonPropertyName("cursor")] + public string? Cursor { get; set; } - /// Git root used as the boundary when traversing for project-level LSP configs (supports monorepos). - [JsonPropertyName("gitRoot")] - public string? GitRoot { get; set; } + /// Name of the MCP server whose resource templates to enumerate. + [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] + [MinLength(1)] + [JsonPropertyName("serverName")] + public string ServerName { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; - - /// Working directory used to load project-level LSP configs. Defaults to the session working directory when omitted. - [JsonPropertyName("workingDirectory")] - public string? WorkingDirectory { get; set; } } -/// Discovered extension metadata, including source-qualified ID, name, discovery source, status, and optional process ID. +/// Session plugin metadata, with name, marketplace, optional version, and enabled state. [Experimental(Diagnostics.Experimental)] -public sealed class Extension +public sealed class Plugin { - /// Source-qualified ID (e.g., 'project:my-ext', 'user:auth-helper', 'plugin:my-plugin:my-ext'). - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// Whether the plugin is currently enabled. + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } - /// Extension name (directory name). + /// Marketplace the plugin came from. + [JsonPropertyName("marketplace")] + public string Marketplace { get; set; } = string.Empty; + + /// Plugin name. [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; - /// Process ID if the extension is running. - [JsonPropertyName("pid")] - public long? Pid { get; set; } - - /// Discovery source: project (.github/extensions/), user (~/.copilot/extensions/), plugin (installed plugin), or session (session-state/<id>/extensions/). - [JsonPropertyName("source")] - public ExtensionSource Source { get; set; } - - /// Current status: running, disabled, failed, or starting. - [JsonPropertyName("status")] - public ExtensionStatus Status { get; set; } + /// Installed version. + [JsonPropertyName("version")] + public string? Version { get; set; } } -/// Extensions discovered for the session, with their current status. +/// Plugins installed for the session, with their enabled state and version metadata. [Experimental(Diagnostics.Experimental)] -public sealed class ExtensionList +public sealed class PluginList { - /// Discovered extensions and their current status. - [JsonPropertyName("extensions")] - public IList Extensions { get => field ??= []; set; } + /// Installed plugins. + [JsonPropertyName("plugins")] + public IList Plugins { get => field ??= []; set; } } /// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionExtensionsListRequest +internal sealed class SessionPluginsListRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Source-qualified extension identifier to enable for the session. +/// RPC data type for SessionPluginsReload operations. [Experimental(Diagnostics.Experimental)] -internal sealed class ExtensionsEnableRequest +public sealed class SessionPluginsReloadRequest { - /// Source-qualified extension ID to enable. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// When true, skip repo-level hooks during the hook reload. Use before folder trust is confirmed; load them post-trust via `sessions.loadDeferredRepoHooks`. + [JsonPropertyName("deferRepoHooks")] + public bool? DeferRepoHooks { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Re-run custom-agent discovery after refreshing plugins. Defaults to true. + [JsonPropertyName("reloadCustomAgents")] + public bool? ReloadCustomAgents { get; set; } + + /// Re-discover and relaunch subprocess extensions (including plugin-shipped extensions) after refreshing plugins. Defaults to true. Has no effect when the session has no active extension controller (e.g. extensions were not requested for the session). + [JsonPropertyName("reloadExtensions")] + public bool? ReloadExtensions { get; set; } + + /// Re-load user, plugin, and (subject to `deferRepoHooks`) repo hooks. Defaults to true. Has no effect when the host has not registered a hook reloader (e.g. remote sessions). + [JsonPropertyName("reloadHooks")] + public bool? ReloadHooks { get; set; } + + /// Reload MCP server connections after refreshing plugins. Defaults to true. + [JsonPropertyName("reloadMcp")] + public bool? ReloadMcp { get; set; } } -/// Source-qualified extension identifier to disable for the session. +/// RPC data type for SessionPluginsReloadRequestWithSession operations. [Experimental(Diagnostics.Experimental)] -internal sealed class ExtensionsDisableRequest +internal sealed class SessionPluginsReloadRequestWithSession { - /// Source-qualified extension ID to disable. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; - - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// When true, skip repo-level hooks during the hook reload. Use before folder trust is confirmed; load them post-trust via `sessions.loadDeferredRepoHooks`. + [JsonPropertyName("deferRepoHooks")] + public bool? DeferRepoHooks { get; set; } + + /// Re-run custom-agent discovery after refreshing plugins. Defaults to true. + [JsonPropertyName("reloadCustomAgents")] + public bool? ReloadCustomAgents { get; set; } + + /// Re-discover and relaunch subprocess extensions (including plugin-shipped extensions) after refreshing plugins. Defaults to true. Has no effect when the session has no active extension controller (e.g. extensions were not requested for the session). + [JsonPropertyName("reloadExtensions")] + public bool? ReloadExtensions { get; set; } + + /// Re-load user, plugin, and (subject to `deferRepoHooks`) repo hooks. Defaults to true. Has no effect when the host has not registered a hook reloader (e.g. remote sessions). + [JsonPropertyName("reloadHooks")] + public bool? ReloadHooks { get; set; } + + /// Reload MCP server connections after refreshing plugins. Defaults to true. + [JsonPropertyName("reloadMcp")] + public bool? ReloadMcp { get; set; } -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionExtensionsReloadRequest -{ /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Attachment union accepted by push input, covering files, directories, GitHub objects, blobs, snippets, and extension context. -/// Polymorphic base type discriminated by type. +/// Short-lived, rotating credential the caller must send on every request, in addition to `apiKey` if one is present. Omitted when the endpoint does not require one. [Experimental(Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "type", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(PushAttachmentFile), "file")] -[JsonDerivedType(typeof(PushAttachmentDirectory), "directory")] -[JsonDerivedType(typeof(PushAttachmentSelection), "selection")] -[JsonDerivedType(typeof(PushAttachmentGitHubReference), "github_reference")] -[JsonDerivedType(typeof(PushAttachmentGitHubCommit), "github_commit")] -[JsonDerivedType(typeof(PushAttachmentGitHubRelease), "github_release")] -[JsonDerivedType(typeof(PushAttachmentGitHubActionsJob), "github_actions_job")] -[JsonDerivedType(typeof(PushAttachmentGitHubRepository), "github_repository")] -[JsonDerivedType(typeof(PushAttachmentGitHubFileDiff), "github_file_diff")] -[JsonDerivedType(typeof(PushAttachmentGitHubTreeComparison), "github_tree_comparison")] -[JsonDerivedType(typeof(PushAttachmentGitHubUrl), "github_url")] -[JsonDerivedType(typeof(PushAttachmentGitHubFile), "github_file")] -[JsonDerivedType(typeof(PushAttachmentGitHubSnippet), "github_snippet")] -[JsonDerivedType(typeof(PushAttachmentBlob), "blob")] -[JsonDerivedType(typeof(PushAttachmentExtensionContext), "extension_context")] -public partial class PushAttachment +public sealed class ProviderSessionToken { - /// The type discriminator. - [JsonPropertyName("type")] - public virtual string Type { get; set; } = string.Empty; -} + /// When the token expires, if known. Callers should refresh by calling `getEndpoint` again before this time, or reactively on any 401/403 response from `baseUrl`. + [JsonPropertyName("expiresAt")] + public DateTimeOffset? ExpiresAt { get; set; } + /// HTTP header name the token must be sent under. + [JsonPropertyName("header")] + public string Header { get; set; } = string.Empty; -/// Optional line range to scope the attachment to a specific section of the file. -[Experimental(Diagnostics.Experimental)] -public sealed class PushAttachmentFileLineRange -{ - /// End line number (1-based, inclusive). - [JsonPropertyName("end")] - public long End { get; set; } + /// The model the token is bound to, when applicable. When set, the token is only valid for requests against this model. + [JsonPropertyName("model")] + public string? Model { get; set; } - /// Start line number (1-based). - [JsonPropertyName("start")] - public long Start { get; set; } + /// The short-lived token value. + [JsonPropertyName("token")] + public string Token { get; set; } = string.Empty; } -/// File attachment. -/// The file variant of . +/// A snapshot of the provider endpoint the session is currently configured to talk to. [Experimental(Diagnostics.Experimental)] -public partial class PushAttachmentFile : PushAttachment +public sealed class ProviderEndpoint { - /// - [JsonIgnore] - public override string Type => "file"; + /// A credential the caller should use with this endpoint. Omitted only when the endpoint accepts unauthenticated requests. + [JsonPropertyName("apiKey")] + public string? ApiKey { get; set; } - /// User-facing display name for the attachment. - [JsonPropertyName("displayName")] - public required string DisplayName { get; set; } + /// Base URL to pass to the LLM client library. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] + [JsonPropertyName("baseUrl")] + public string BaseUrl { get; set; } = string.Empty; - /// Optional line range to scope the attachment to a specific section of the file. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("lineRange")] - public PushAttachmentFileLineRange? LineRange { get; set; } + /// HTTP headers the caller must include on every outbound request. + [JsonPropertyName("headers")] + public IDictionary Headers { get => field ??= new Dictionary(); set; } - /// Absolute file path. - [JsonPropertyName("path")] - public required string Path { get; set; } -} + /// Short-lived, rotating credential the caller must send on every request, in addition to `apiKey` if one is present. Omitted when the endpoint does not require one. + [JsonPropertyName("sessionToken")] + public ProviderSessionToken? SessionToken { get; set; } -/// Directory attachment. -/// The directory variant of . -[Experimental(Diagnostics.Experimental)] -public partial class PushAttachmentDirectory : PushAttachment -{ - /// - [JsonIgnore] - public override string Type => "directory"; + /// Transport to be used for provider requests. + [JsonPropertyName("transport")] + public ProviderEndpointTransport? Transport { get; set; } - /// User-facing display name for the attachment. - [JsonPropertyName("displayName")] - public required string DisplayName { get; set; } + /// Provider family. Matches the `type` field of a BYOK provider config. + [JsonPropertyName("type")] + public ProviderEndpointType Type { get; set; } - /// Absolute directory path. - [JsonPropertyName("path")] - public required string Path { get; set; } + /// Wire API to be used, when required for the provider type. + [JsonPropertyName("wireApi")] + public ProviderEndpointWireApi? WireApi { get; set; } } -/// End position of the selection. +/// RPC data type for SessionProviderGetEndpoint operations. [Experimental(Diagnostics.Experimental)] -public sealed class PushAttachmentSelectionDetailsEnd +public sealed class SessionProviderGetEndpointRequest { - /// End character offset within the line (0-based). - [JsonPropertyName("character")] - public long Character { get; set; } - - /// End line number (0-based). - [JsonPropertyName("line")] - public long Line { get; set; } + /// Model identifier the caller intends to use against the returned endpoint. Used to pick the correct wire shape. Omit to use whichever model the session is currently using. + [JsonPropertyName("modelId")] + public string? ModelId { get; set; } } -/// Start position of the selection. +/// RPC data type for SessionProviderGetEndpointRequestWithSession operations. [Experimental(Diagnostics.Experimental)] -public sealed class PushAttachmentSelectionDetailsStart +internal sealed class SessionProviderGetEndpointRequestWithSession { - /// Start character offset within the line (0-based). - [JsonPropertyName("character")] - public long Character { get; set; } + /// Model identifier the caller intends to use against the returned endpoint. Used to pick the correct wire shape. Omit to use whichever model the session is currently using. + [JsonPropertyName("modelId")] + public string? ModelId { get; set; } - /// Start line number (0-based). - [JsonPropertyName("line")] - public long Line { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Position range of the selection within the file. +/// The selectable model entries synthesized for the models added by this call. [Experimental(Diagnostics.Experimental)] -public sealed class PushAttachmentSelectionDetails +public sealed class ProviderAddResult { - /// End position of the selection. - [JsonPropertyName("end")] - public PushAttachmentSelectionDetailsEnd End { get => field ??= new(); set; } - - /// Start position of the selection. - [JsonPropertyName("start")] - public PushAttachmentSelectionDetailsStart Start { get => field ??= new(); set; } + /// Synthesized selectable model entries for the newly added BYOK models, each under its provider-qualified selection id (`provider/id`). Empty when only providers were added. + [JsonPropertyName("models")] + public IList Models { get => field ??= []; set; } } -/// Code selection attachment from an editor. -/// The selection variant of . +/// A BYOK model definition referencing a named provider. [Experimental(Diagnostics.Experimental)] -public partial class PushAttachmentSelection : PushAttachment +public sealed class ProviderModelConfig { - /// - [JsonIgnore] - public override string Type => "selection"; - - /// User-facing display name for the selection. - [JsonPropertyName("displayName")] - public required string DisplayName { get; set; } - - /// Absolute path to the file containing the selection. - [JsonPropertyName("filePath")] - public required string FilePath { get; set; } + /// Optional capability overrides (vision, tool_calls, reasoning, etc.). + [JsonPropertyName("capabilities")] + public ModelCapabilitiesOverride? Capabilities { get; set; } - /// Position range of the selection within the file. - [JsonPropertyName("selection")] - public required PushAttachmentSelectionDetails Selection { get; set; } + /// Provider-local model id, unique within its provider. The session-wide selection id (shown in the model list and passed to switchTo) is the provider-qualified `provider/id`. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; - /// The selected text content. - [JsonPropertyName("text")] - public required string Text { get; set; } -} + /// Maximum context window tokens for the model. + [JsonPropertyName("maxContextWindowTokens")] + public double? MaxContextWindowTokens { get; set; } -/// GitHub issue, pull request, or discussion reference. -/// The github_reference variant of . -[Experimental(Diagnostics.Experimental)] -public partial class PushAttachmentGitHubReference : PushAttachment -{ - /// - [JsonIgnore] - public override string Type => "github_reference"; + /// Maximum output tokens for the model. + [JsonPropertyName("maxOutputTokens")] + public double? MaxOutputTokens { get; set; } - /// Issue, pull request, or discussion number. - [JsonPropertyName("number")] - public required long Number { get; set; } + /// Maximum prompt/input tokens for the model. + [JsonPropertyName("maxPromptTokens")] + public double? MaxPromptTokens { get; set; } - /// Type of GitHub reference. - [JsonPropertyName("referenceType")] - public required PushAttachmentGitHubReferenceType ReferenceType { get; set; } + /// Well-known base model id used for behavior/capability/config lookup. Defaults to `id`. + [JsonPropertyName("modelId")] + public string? ModelId { get; set; } - /// Current state of the referenced item (e.g., open, closed, merged). - [JsonPropertyName("state")] - public required string State { get; set; } + /// Display name for model pickers. Defaults to the provider-qualified selection id (`provider/id`). + [JsonPropertyName("name")] + public string? Name { get; set; } - /// Title of the referenced item. - [JsonPropertyName("title")] - public required string Title { get; set; } + /// Name of the NamedProviderConfig that serves this model. + [JsonPropertyName("provider")] + public string Provider { get; set; } = string.Empty; - /// URL to the referenced item on GitHub. - [JsonPropertyName("url")] - public required string Url { get; set; } + /// The model name sent to the provider API for inference. Defaults to `id`. + [JsonPropertyName("wireModel")] + public string? WireModel { get; set; } } -/// Pointer to a GitHub repository. +/// Azure-specific provider options. [Experimental(Diagnostics.Experimental)] -public sealed class PushGitHubRepoRef +public sealed class ProviderConfigAzure { - /// Numeric GitHub repository id. - [JsonPropertyName("id")] - public long? Id { get; set; } + /// API version. When set, uses the versioned deployment route. When omitted, uses the GA versionless v1 route. + [JsonPropertyName("apiVersion")] + public string? ApiVersion { get; set; } +} - /// Repository name (without owner). - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; - - /// Repository owner login (user or organization). - [JsonPropertyName("owner")] - public string Owner { get; set; } = string.Empty; -} - -/// Pointer to a GitHub commit. -/// The github_commit variant of . +/// A named BYOK provider connection (transport + credentials). [Experimental(Diagnostics.Experimental)] -public partial class PushAttachmentGitHubCommit : PushAttachment +public sealed class NamedProviderConfig { - /// - [JsonIgnore] - public override string Type => "github_commit"; + /// API key. Optional for local providers like Ollama. + [JsonPropertyName("apiKey")] + public string? ApiKey { get; set; } - /// First line of the commit message. - [JsonPropertyName("message")] - public required string Message { get; set; } + /// Azure-specific provider options. + [JsonPropertyName("azure")] + public ProviderConfigAzure? Azure { get; set; } - /// Full commit SHA. - [JsonPropertyName("oid")] - public required string Oid { get; set; } + /// API endpoint URL. + [JsonPropertyName("baseUrl")] + public string BaseUrl { get; set; } = string.Empty; - /// Repository the commit belongs to. - [JsonPropertyName("repo")] - public required PushGitHubRepoRef Repo { get; set; } + /// Bearer token for authentication. Sets the Authorization header directly. Takes precedence over apiKey when both are set. + [JsonPropertyName("bearerToken")] + public string? BearerToken { get; set; } - /// URL to the commit on GitHub. - [JsonPropertyName("url")] - public required string Url { get; set; } -} + /// When true, the SDK client supplies bearer tokens on demand: the runtime calls the client-session `providerToken.getToken` callback before each request and applies the returned token as an `Authorization: Bearer <token>` header. This is the bearer/OAuth scheme used by Azure AD / managed-identity tokens and provider OAuth access tokens (including Anthropic's), not a provider-specific API-key header such as Anthropic's `x-api-key`. The token-acquiring function itself stays on the SDK side and is never serialized; only this flag crosses the wire. When set alongside `apiKey`/`bearerToken`, the callback takes precedence: the runtime applies the token returned by `providerToken.getToken` as the `Authorization: Bearer` header for each request and does not send the static credential. + [JsonPropertyName("hasBearerTokenProvider")] + public bool? HasBearerTokenProvider { get; set; } -/// Pointer to a GitHub release. -/// The github_release variant of . -[Experimental(Diagnostics.Experimental)] -public partial class PushAttachmentGitHubRelease : PushAttachment -{ - /// - [JsonIgnore] - public override string Type => "github_release"; + /// Custom HTTP headers to include in all outbound requests to the provider. + [JsonPropertyName("headers")] + public IDictionary? Headers { get; set; } - /// Human-readable release name. + /// Stable identifier referenced by BYOK model definitions. Must not contain '/'. [JsonPropertyName("name")] - public required string Name { get; set; } + public string Name { get; set; } = string.Empty; - /// Repository the release belongs to. - [JsonPropertyName("repo")] - public required PushGitHubRepoRef Repo { get; set; } + /// Provider transport. Defaults to "http". + [JsonPropertyName("transport")] + public ProviderConfigTransport? Transport { get; set; } - /// Git tag the release is anchored to. - [JsonPropertyName("tagName")] - public required string TagName { get; set; } + /// Provider type. Defaults to "openai" for generic OpenAI-compatible APIs. + [JsonPropertyName("type")] + public ProviderConfigType? Type { get; set; } - /// URL to the release on GitHub. - [JsonPropertyName("url")] - public required string Url { get; set; } + /// Wire API format (openai/azure only). Defaults to "completions". + [JsonPropertyName("wireApi")] + public ProviderConfigWireApi? WireApi { get; set; } } -/// Pointer to a GitHub Actions job. -/// The github_actions_job variant of . +/// BYOK providers and/or models to add to the session's registry at runtime. Both fields are optional; provide providers, models, or both. [Experimental(Diagnostics.Experimental)] -public partial class PushAttachmentGitHubActionsJob : PushAttachment +internal sealed class ProviderAddRequest { - /// - [JsonIgnore] - public override string Type => "github_actions_job"; - - /// Terminal conclusion of the job when finished (e.g., success, failure, cancelled). Absent for in-progress jobs. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("conclusion")] - public string? Conclusion { get; set; } - - /// Job id within the workflow run. - [JsonPropertyName("jobId")] - public required long JobId { get; set; } - - /// Display name of the job. - [JsonPropertyName("jobName")] - public required string JobName { get; set; } - - /// Repository the workflow run belongs to. - [JsonPropertyName("repo")] - public required PushGitHubRepoRef Repo { get; set; } + /// BYOK model definitions to register. Each must reference a provider that is already registered or included in this same call. Selection ids (`provider/id`) must be unique across the registry. + [JsonPropertyName("models")] + public IList? Models { get; set; } - /// URL to the job on GitHub. - [JsonPropertyName("url")] - public required string Url { get; set; } + /// Named BYOK provider connections to register, additive to any providers already in the registry. Each name must be unique across the registry and must not contain '/'. + [JsonPropertyName("providers")] + public IList? Providers { get; set; } - /// Display name of the workflow the job ran in. - [JsonPropertyName("workflowName")] - public required string WorkflowName { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Pointer to a GitHub repository. -/// The github_repository variant of . +/// Indicates whether the session options patch was applied successfully. [Experimental(Diagnostics.Experimental)] -public partial class PushAttachmentGitHubRepository : PushAttachment +public sealed class SessionUpdateOptionsResult { - /// - [JsonIgnore] - public override string Type => "github_repository"; - - /// Short description of the repository. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("description")] - public string? Description { get; set; } - - /// Git ref this attachment is anchored at (branch, tag, or commit). When absent the default branch is implied. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("ref")] - public string? Ref { get; set; } - - /// Repository pointer. - [JsonPropertyName("repo")] - public required PushGitHubRepoRef Repo { get; set; } + /// Number of hooks loaded from installed plugins, returned when installedPlugins is updated. + [JsonPropertyName("pluginHookCount")] + public long? PluginHookCount { get; set; } - /// URL to the repository on GitHub. - [JsonPropertyName("url")] - public required string Url { get; set; } + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// One side of a file diff (head or base). +/// Source descriptor for a `session.options.update` content-exclusion rule, with source name and type. [Experimental(Diagnostics.Experimental)] -public sealed class PushAttachmentGitHubFileDiffSide +public sealed class OptionsUpdateAdditionalContentExclusionPolicyRuleSource { - /// Repository-relative path to the file. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; - - /// Git ref (branch, tag, or commit SHA) the file is read at. - [JsonPropertyName("ref")] - public string Ref { get; set; } = string.Empty; + /// Gets or sets the name value. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// Repository the file lives in. - [JsonPropertyName("repo")] - public PushGitHubRepoRef Repo { get => field ??= new(); set; } + /// Gets or sets the type value. + [JsonPropertyName("type")] + public string Type { get; set; } = string.Empty; } -/// Pointer to a single-file diff. At least one of `head` and `base` must be present. -/// The github_file_diff variant of . +/// Single content-exclusion rule supplied to `session.options.update`, with paths, match conditions, and source. [Experimental(Diagnostics.Experimental)] -public partial class PushAttachmentGitHubFileDiff : PushAttachment +public sealed class OptionsUpdateAdditionalContentExclusionPolicyRule { - /// - [JsonIgnore] - public override string Type => "github_file_diff"; + /// Gets or sets the ifAnyMatch value. + [JsonPropertyName("ifAnyMatch")] + public IList? IfAnyMatch { get; set; } - /// File location on the base side of the diff. Absent for additions. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("base")] - public PushAttachmentGitHubFileDiffSide? Base { get; set; } + /// Gets or sets the ifNoneMatch value. + [JsonPropertyName("ifNoneMatch")] + public IList? IfNoneMatch { get; set; } - /// File location on the head side of the diff. Absent for deletions. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("head")] - public PushAttachmentGitHubFileDiffSide? Head { get; set; } + /// Gets or sets the paths value. + [JsonPropertyName("paths")] + public IList Paths { get => field ??= []; set; } - /// URL to the diff on GitHub (e.g., a commit, compare, or PR-file URL). - [JsonPropertyName("url")] - public required string Url { get; set; } + /// Source descriptor for a `session.options.update` content-exclusion rule, with source name and type. + [JsonPropertyName("source")] + public OptionsUpdateAdditionalContentExclusionPolicyRuleSource Source { get => field ??= new(); set; } } -/// One side of a tree comparison (head or base). +/// Content-exclusion policy supplied to `session.options.update`, with rules, last-updated data, and scope. [Experimental(Diagnostics.Experimental)] -public sealed class PushAttachmentGitHubTreeComparisonSide +public sealed class OptionsUpdateAdditionalContentExclusionPolicy { - /// Repository the revision belongs to. - [JsonPropertyName("repo")] - public PushGitHubRepoRef Repo { get => field ??= new(); set; } + /// Gets or sets the last_updated_at value. + [JsonPropertyName("last_updated_at")] + public JsonElement LastUpdatedAt { get; set; } - /// Git revision (branch, tag, or commit SHA). - [JsonPropertyName("revision")] - public string Revision { get; set; } = string.Empty; + /// Gets or sets the rules value. + [JsonPropertyName("rules")] + public IList Rules { get => field ??= []; set; } + + /// Allowed values for the `OptionsUpdateAdditionalContentExclusionPolicyScope` enumeration. + [JsonPropertyName("scope")] + public OptionsUpdateAdditionalContentExclusionPolicyScope Scope { get; set; } } -/// Pointer to a comparison between two git revisions. -/// The github_tree_comparison variant of . +/// Options scoped to the built-in CAPI (Copilot API) provider. [Experimental(Diagnostics.Experimental)] -public partial class PushAttachmentGitHubTreeComparison : PushAttachment +public sealed class CapiSessionOptions { - /// - [JsonIgnore] - public override string Type => "github_tree_comparison"; - - /// Base side of the comparison. - [JsonPropertyName("base")] - public required PushAttachmentGitHubTreeComparisonSide Base { get; set; } - - /// Head side of the comparison. - [JsonPropertyName("head")] - public required PushAttachmentGitHubTreeComparisonSide Head { get; set; } - - /// URL to the comparison on GitHub. - [JsonPropertyName("url")] - public required string Url { get; set; } + /// Whether to use WebSocket transport for the CAPI Responses API. Enabled by default when the model advertises `ws:/responses` support; set to `false` to force the HTTP Responses transport in environments where WebSockets are blocked (e.g. behind a proxy). Setting this to `false` is equivalent to the `COPILOT_CLI_DISABLE_WEBSOCKET_RESPONSES` environment variable. + [JsonPropertyName("enableWebSocketResponses")] + public bool? EnableWebSocketResponses { get; set; } } -/// Generic GitHub URL reference. -/// The github_url variant of . +/// Installed plugin record for a session, with marketplace, version, install time, enabled state, cache path, and source. [Experimental(Diagnostics.Experimental)] -public partial class PushAttachmentGitHubUrl : PushAttachment +public sealed class SessionInstalledPlugin { - /// - [JsonIgnore] - public override string Type => "github_url"; + /// Path where the plugin is cached locally. + [JsonPropertyName("cache_path")] + public string? CachePath { get; set; } - /// URL to the GitHub resource. - [JsonPropertyName("url")] - public required string Url { get; set; } -} + /// Whether the plugin is currently enabled. + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } -/// Pointer to a file in a GitHub repository at a specific ref. -/// The github_file variant of . -[Experimental(Diagnostics.Experimental)] -public partial class PushAttachmentGitHubFile : PushAttachment -{ - /// - [JsonIgnore] - public override string Type => "github_file"; + /// Installation timestamp (ISO-8601). + [JsonPropertyName("installed_at")] + public string InstalledAt { get; set; } = string.Empty; - /// Repository-relative path to the file. - [JsonPropertyName("path")] - public required string Path { get; set; } + /// Marketplace the plugin came from (empty string for direct repo installs). + [JsonPropertyName("marketplace")] + public string Marketplace { get; set; } = string.Empty; - /// Git ref the file is read at (branch, tag, or commit SHA). - [JsonPropertyName("ref")] - public required string Ref { get; set; } + /// Plugin name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// Repository the file lives in. - [JsonPropertyName("repo")] - public required PushGitHubRepoRef Repo { get; set; } + /// Source descriptor for direct repo installs (when marketplace is empty). + [JsonPropertyName("source")] + public JsonElement? Source { get; set; } - /// URL to the file on GitHub. - [JsonPropertyName("url")] - public required string Url { get; set; } + /// Per-plugin source fingerprint (a SHA-256 hash of the plugin's catalog source spec plus its resolved source subtree — NOT a Git commit SHA) captured at marketplace install/update time. Auto-update compares it against the freshly recomputed fingerprint to detect a content change that does not bump the version. Absent for pre-existing installs and for direct (non-marketplace) installs. + [JsonPropertyName("source_sha")] + public string? SourceSha { get; set; } + + /// Installed version, if known. + [JsonPropertyName("version")] + public string? Version { get; set; } } -/// Pointer to a line range inside a file in a GitHub repository. -/// The github_snippet variant of . +/// Custom model-provider configuration (BYOK). [Experimental(Diagnostics.Experimental)] -public partial class PushAttachmentGitHubSnippet : PushAttachment +public sealed class ProviderConfig { - /// - [JsonIgnore] - public override string Type => "github_snippet"; + /// API key. Optional for local providers like Ollama. + [JsonPropertyName("apiKey")] + public string? ApiKey { get; set; } - /// Line range the snippet covers. - [JsonPropertyName("lineRange")] - public required PushAttachmentFileLineRange LineRange { get; set; } + /// Azure-specific provider options. + [JsonPropertyName("azure")] + public ProviderConfigAzure? Azure { get; set; } - /// Repository-relative path to the file. - [JsonPropertyName("path")] - public required string Path { get; set; } + /// API endpoint URL. + [JsonPropertyName("baseUrl")] + public string BaseUrl { get; set; } = string.Empty; - /// Git ref the file is read at (branch, tag, or commit SHA). - [JsonPropertyName("ref")] - public required string Ref { get; set; } + /// Bearer token for authentication. Sets the Authorization header directly. Takes precedence over apiKey when both are set. + [JsonPropertyName("bearerToken")] + public string? BearerToken { get; set; } - /// Repository the file lives in. - [JsonPropertyName("repo")] - public required PushGitHubRepoRef Repo { get; set; } + /// When true, the SDK client supplies bearer tokens on demand: the runtime calls the client-session `providerToken.getToken` callback before each request and applies the returned token as an `Authorization: Bearer <token>` header. This is the bearer/OAuth scheme used by Azure AD / managed-identity tokens and provider OAuth access tokens (including Anthropic's), not a provider-specific API-key header such as Anthropic's `x-api-key`. The token-acquiring function itself stays on the SDK side and is never serialized; only this flag crosses the wire. When set alongside `apiKey`/`bearerToken`, the callback takes precedence: the runtime applies the token returned by `providerToken.getToken` as the `Authorization: Bearer` header for each request and does not send the static credential. + [JsonPropertyName("hasBearerTokenProvider")] + public bool? HasBearerTokenProvider { get; set; } - /// URL to the snippet on GitHub (with line anchor). - [JsonPropertyName("url")] - public required string Url { get; set; } -} + /// Custom HTTP headers to include in all outbound requests to the provider. + [JsonPropertyName("headers")] + public IDictionary? Headers { get; set; } -/// Blob attachment with inline base64-encoded data. -/// The blob variant of . -[Experimental(Diagnostics.Experimental)] -public partial class PushAttachmentBlob : PushAttachment -{ - /// - [JsonIgnore] - public override string Type => "blob"; + /// Maximum context window tokens for the model. + [JsonPropertyName("maxContextWindowTokens")] + public double? MaxContextWindowTokens { get; set; } - /// Base64-encoded content. - [Base64String] - [JsonPropertyName("data")] - public required string Data { get; set; } + /// Maximum output tokens for the model. + [JsonPropertyName("maxOutputTokens")] + public double? MaxOutputTokens { get; set; } - /// User-facing display name for the attachment. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("displayName")] - public string? DisplayName { get; set; } + /// Maximum prompt/input tokens for the model. + [JsonPropertyName("maxPromptTokens")] + public double? MaxPromptTokens { get; set; } - /// MIME type of the inline data. - [JsonPropertyName("mimeType")] - public required string MimeType { get; set; } -} + /// Well-known model ID used for capability lookup. When set, agent behavior config and token limits are inferred from this model. + [JsonPropertyName("modelId")] + public string? ModelId { get; set; } -/// Slim input shape for extension_context attachments; identity fields are runtime-derived. -/// The extension_context variant of . -[Experimental(Diagnostics.Experimental)] -public partial class PushAttachmentExtensionContext : PushAttachment -{ - /// - [JsonIgnore] - public override string Type => "extension_context"; + /// Provider transport. Defaults to "http". + [JsonPropertyName("transport")] + public ProviderConfigTransport? Transport { get; set; } - /// Caller-supplied JSON payload (required, may be null but not undefined). - [JsonPropertyName("payload")] - public required JsonElement Payload { get; set; } + /// Provider type. Defaults to "openai" for generic OpenAI-compatible APIs. + [JsonPropertyName("type")] + public ProviderConfigType? Type { get; set; } - /// Human-readable composer pill label. - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] - [MinLength(1)] - [JsonPropertyName("title")] - public required string Title { get; set; } + /// Wire API format (openai/azure only). Defaults to "completions". + [JsonPropertyName("wireApi")] + public ProviderConfigWireApi? WireApi { get; set; } + + /// The model identifier sent to the provider API for inference (the "wire" model), as opposed to modelId which is the well-known base. + [JsonPropertyName("wireModel")] + public string? WireModel { get; set; } } -/// Parameters for session.extensions.sendAttachmentsToMessage. +/// Credential-injection capability flags applied while the sandbox is enabled. [Experimental(Diagnostics.Experimental)] -internal sealed class SendAttachmentsToMessageParams +public sealed class SandboxConfigAuth { - /// Attachments to push into the next user-message turn. extension_context entries take the slim shape; standard variants take their full AttachmentSchema shape. - [JsonPropertyName("attachments")] - public IList Attachments { get => field ??= []; set; } + /// Whether to export `GH_TOKEN` so the `gh` CLI authenticates inside the sandbox without the OS keyring the sandbox blocks. Default: false (opt-in). + [JsonPropertyName("gh")] + public bool? Gh { get; set; } - /// Optional canvas instance binding the push for provenance. When supplied, the runtime resolves the canvas, verifies it is owned by the calling extension, and stamps canvasId/instanceId onto each extension_context entry. When omitted, no resolution runs and those fields stay unset on the attachment. - [JsonPropertyName("instanceId")] - public string? InstanceId { get; set; } + /// Whether to inject git credentials as an `http.<url>.extraheader` so authenticated HTTPS git works inside the sandbox without the shell-based credential helper the sandbox blocks. github.com is served by the Copilot token; every other forge (Azure DevOps, GitHub Enterprise Server, GitLab, ...) by a credential the host resolves from the user's own helper before the sandbox is applied. Default: false (opt-in). + [JsonPropertyName("git")] + public bool? Git { get; set; } +} - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; +/// macOS seatbelt experimental options. +[Experimental(Diagnostics.Experimental)] +public sealed class SandboxConfigUserPolicyExperimentalSeatbelt +{ + /// Whether the macOS seatbelt profile may access the keychain. + [JsonPropertyName("keychainAccess")] + public bool? KeychainAccess { get; set; } } -/// Indicates whether the external tool call result was handled successfully. +/// Platform-specific experimental policy fields. [Experimental(Diagnostics.Experimental)] -public sealed class HandlePendingToolCallResult +public sealed class SandboxConfigUserPolicyExperimental { - /// Whether the tool call result was handled successfully. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// macOS seatbelt experimental options. + [JsonPropertyName("seatbelt")] + public SandboxConfigUserPolicyExperimentalSeatbelt? Seatbelt { get; set; } } -/// Pending external tool call request ID, with the tool result or an error describing why it failed. +/// Filesystem rules to merge into the base policy. [Experimental(Diagnostics.Experimental)] -internal sealed class HandlePendingToolCallRequest +public sealed class SandboxConfigUserPolicyFilesystem { - /// Error message if the tool call failed. - [JsonPropertyName("error")] - public string? Error { get; set; } + /// Whether to clear the policy when the session exits. + [JsonPropertyName("clearPolicyOnExit")] + public bool? ClearPolicyOnExit { get; set; } - /// Request ID of the pending tool call. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; + /// Paths explicitly denied. + [JsonPropertyName("deniedPaths")] + public IList? DeniedPaths { get; set; } - /// Tool call result (string or expanded result object). - [JsonPropertyName("result")] - public JsonElement? Result { get; set; } + /// Paths granted read-only access. + [JsonPropertyName("readonlyPaths")] + public IList? ReadonlyPaths { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Paths granted read/write access. + [JsonPropertyName("readwritePaths")] + public IList? ReadwritePaths { get; set; } } -/// Resolve, build, and validate the runtime tool list for this session. Subagent sessions and consumer flows that need an initialized tool set before `send` invoke this. Default base-class implementation is a no-op for sessions that don't support tool validation. +/// HTTP proxy configuration for sandboxed traffic. [Experimental(Diagnostics.Experimental)] -public sealed class ToolsInitializeAndValidateResult +public sealed class SandboxConfigUserPolicyNetworkProxy { -} + /// Optional password for proxy authentication, combined with the URL at spawn time. The persisted value may be a literal password, a `${secret:…}` reference resolved from the OS keychain, or a `${VAR}`/`$VAR` environment reference; it is resolved just before the sandboxed process routes through the proxy. The /sandbox dialog stores a real password in the OS keychain and persists only a `${secret:…}` placeholder (never plaintext in settings.json); the field is masked in the dialog and redacted by /settings show. + [JsonPropertyName("password")] + public string? Password { get; set; } -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionToolsInitializeAndValidateRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Proxy URL (e.g. http://proxy.example.com:8080). The port is optional and defaults to the scheme's standard port when omitted. Credentials must not be embedded here — a `user:pass@` authority is rejected; put them in the separate `username`/`password` fields. A credential-free http:// loopback URL is routed through the localhost proxy automatically; loopback covers localhost and any *.localhost subdomain, the whole 127.0.0.0/8 range, ::1, and IPv4-mapped loopback (::ffff:127.0.0.1). An https:// URL, or one with a username/password set, is used as-is. + [JsonPropertyName("url")] + public string Url { get; set; } = string.Empty; + + /// Optional username for proxy authentication. Combined with the URL (and `password`) into `user:pass@host` when the sandboxed process routes through the proxy. + [JsonPropertyName("username")] + public string? Username { get; set; } } -/// Lightweight metadata for a currently initialized session tool. +/// Network rules to merge into the base policy. [Experimental(Diagnostics.Experimental)] -public sealed class CurrentToolMetadata +public sealed class SandboxConfigUserPolicyNetwork { - /// Whether the tool is loaded on demand via tool search. - [JsonPropertyName("deferLoading")] - public bool? DeferLoading { get; set; } - - /// Tool description. - [JsonPropertyName("description")] - public string Description { get; set; } = string.Empty; - - /// JSON Schema for tool input. - [JsonPropertyName("input_schema")] - public IDictionary? InputSchema { get; set; } - - /// MCP server name for MCP-backed tools. - [JsonPropertyName("mcpServerName")] - public string? McpServerName { get; set; } - - /// Raw MCP tool name for MCP-backed tools. - [JsonPropertyName("mcpToolName")] - public string? McpToolName { get; set; } + /// Whether traffic to local/loopback addresses is allowed. + [JsonPropertyName("allowLocalNetwork")] + public bool? AllowLocalNetwork { get; set; } - /// Model-facing tool name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Whether outbound network traffic is allowed at all. + [JsonPropertyName("allowOutbound")] + public bool? AllowOutbound { get; set; } - /// Optional MCP/config namespaced tool name. - [JsonPropertyName("namespacedName")] - public string? NamespacedName { get; set; } + /// HTTP proxy the sandboxed process routes traffic through. Enforced on Windows and cooperative (honored by well-behaved tools, not strictly enforced) on Linux and macOS. Credentials go in the separate `username`/`password` fields. A credential-free http:// loopback proxy URL is routed through the localhost proxy automatically; an https:// or authenticated loopback URL is used as-is. + [JsonPropertyName("proxy")] + public SandboxConfigUserPolicyNetworkProxy? Proxy { get; set; } } -/// Current lightweight tool metadata snapshot for the session. +/// macOS seatbelt-specific options. [Experimental(Diagnostics.Experimental)] -public sealed class ToolsGetCurrentMetadataResult +public sealed class SandboxConfigUserPolicySeatbelt { - /// Current tool metadata, or null when tools have not been initialized yet. - [JsonPropertyName("tools")] - public IList? Tools { get; set; } -} - -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionToolsGetCurrentMetadataRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Whether the macOS seatbelt profile may access the keychain. + [JsonPropertyName("keychainAccess")] + public bool? KeychainAccess { get; set; } } -/// Empty result after applying subagent settings. +/// User-managed sandbox policy fragment merged into the auto-discovered base policy. [Experimental(Diagnostics.Experimental)] -public sealed class ToolsUpdateSubagentSettingsResult +public sealed class SandboxConfigUserPolicy { -} + /// Deprecated legacy location for `seatbelt`; read only when the top-level `seatbelt` is absent. + [JsonPropertyName("experimental")] + public SandboxConfigUserPolicyExperimental? Experimental { get; set; } -/// Subagent model, reasoning effort, and context tier settings. -[Experimental(Diagnostics.Experimental)] -public sealed class SubagentSettingsEntry -{ - /// Context tier override for matching subagents. - [JsonPropertyName("contextTier")] - public SubagentSettingsEntryContextTier? ContextTier { get; set; } + /// Filesystem rules to merge into the base policy. + [JsonPropertyName("filesystem")] + public SandboxConfigUserPolicyFilesystem? Filesystem { get; set; } - /// Reasoning effort override for matching subagents. - [JsonPropertyName("effortLevel")] - public string? EffortLevel { get; set; } + /// Network rules to merge into the base policy. + [JsonPropertyName("network")] + public SandboxConfigUserPolicyNetwork? Network { get; set; } - /// Model override for matching subagents. - [JsonPropertyName("model")] - public string? Model { get; set; } + /// macOS seatbelt options to merge into the base policy. + [JsonPropertyName("seatbelt")] + public SandboxConfigUserPolicySeatbelt? Seatbelt { get; set; } } -/// Configured per-agent subagent overrides. -public sealed class UpdateSubagentSettingsRequestSubagents +/// Resolved sandbox configuration. +[Experimental(Diagnostics.Experimental)] +public sealed class SandboxConfig { - /// Per-agent settings keyed by subagent agent_type. - [JsonPropertyName("agents")] - public IDictionary? Agents { get; set; } + /// Whether to auto-add the current working directory to readwritePaths. Default: true. + [JsonPropertyName("addCurrentWorkingDirectory")] + public bool? AddCurrentWorkingDirectory { get; set; } - /// Names of subagents the user has turned off; they cannot be dispatched. - [JsonPropertyName("disabledSubagents")] - public IList? DisabledSubagents { get; set; } + /// Whether to auto-grant read access to common developer-tool caches, registries, and toolchains in their default home locations (cargo, go, npm, Maven, and more), plus read-write access to (and, on Unix, up-front creation of) the scratch caches builds write on every run (go-build, ccache, sccache, Gradle caches, Cargo lock/tracker files), so builds work without extra configuration; a relocated CARGO_HOME additionally gets its Cargo lock files granted read-write. Default: true (enabled by default; set to false to opt out). + [JsonPropertyName("allowDevToolAccess")] + public bool? AllowDevToolAccess { get; set; } - /// Maximum number of subagents that can run concurrently; applies to usage-based billing users only. - [JsonPropertyName("maxConcurrency")] - public int? MaxConcurrency { get; set; } + /// Credential-injection capability flags. + [JsonPropertyName("auth")] + public SandboxConfigAuth? Auth { get; set; } - /// Maximum subagent nesting depth; applies to usage-based billing users only. - [JsonPropertyName("maxDepth")] - public int? MaxDepth { get; set; } + /// Whether sandboxing is enabled for the session. + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } + + /// User-managed sandbox policy fragment merged into the auto-discovered base policy. + [JsonPropertyName("userPolicy")] + public SandboxConfigUserPolicy? UserPolicy { get; set; } } -/// Subagent settings to apply to the current session. +/// A host-provided script sourced before each built-in shell command when its shell target matches the active shell. [Experimental(Diagnostics.Experimental)] -internal sealed class UpdateSubagentSettingsRequest +public sealed class ShellInitScript { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Path to the script to source. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; - /// Subagent settings to apply, or null to clear the live session override. - [JsonPropertyName("subagents")] - public UpdateSubagentSettingsRequestSubagents? Subagents { get; set; } + /// Built-in shell that may source this script. + [JsonPropertyName("shell")] + public ShellInitScriptShell Shell { get; set; } } -/// Optional filters controlling which command sources to include in the listing. +/// Per-session settings for built-in shell tools. [Experimental(Diagnostics.Experimental)] -public sealed class CommandsListRequest +public sealed class ShellOptions { - /// Include runtime built-in commands. - [JsonPropertyName("includeBuiltins")] - public bool? IncludeBuiltins { get; set; } + /// Controls automatic non-interactive profile loading where supported. Explicit initScripts are unaffected. + [JsonPropertyName("initProfile")] + public ShellInitProfile? InitProfile { get; set; } - /// Include commands registered by protocol clients, including SDK clients and extensions. - [JsonPropertyName("includeClientCommands")] - public bool? IncludeClientCommands { get; set; } + /// + /// Ordered host-provided script paths sourced before each built-in shell command when the + /// entry's shell target matches the active shell. Use these for rc files, environment setup scripts, + /// or other custom scripts. A script that returns a nonzero status is reported, and later scripts + /// and the user command continue while the shell remains running. Because scripts are sourced into + /// the command shell, `exit`, `exec`, failures under `set -e`, or other shell-terminating behavior + /// can prevent continuation. Script standard output is preserved; Bash script stderr is discarded, + /// PowerShell exception messages are replaced, and runtime-generated failure notices omit + /// configured script paths. When sandboxing is enabled, each script must already be readable under + /// the active sandbox filesystem policy. Pass an empty array to clear the list. + /// + [JsonPropertyName("initScripts")] + public IList? InitScripts { get; set; } - /// Include enabled user-invocable skills and commands. - [JsonPropertyName("includeSkills")] - public bool? IncludeSkills { get; set; } + /// + /// Flags passed to the active built-in shell process on startup, replacing its default flags. + /// When omitted, the built-in Bash shell uses `--norc --noprofile`, + /// and the built-in PowerShell shell uses `-NoProfile -NoLogo`. + /// + [JsonPropertyName("processFlags")] + public IList? ProcessFlags { get; set; } } -/// Optional filters controlling which command sources to include in the listing. +/// Patch of mutable session options to apply to the running session. [Experimental(Diagnostics.Experimental)] -internal sealed class CommandsListRequestWithSession +internal sealed class SessionUpdateOptionsParams { - /// Include runtime built-in commands. - [JsonPropertyName("includeBuiltins")] - public bool? IncludeBuiltins { get; set; } + /// Additional content-exclusion policies to merge into the session's policy set. + [Experimental(Diagnostics.Experimental)] + [JsonPropertyName("additionalContentExclusionPolicies")] + public IList? AdditionalContentExclusionPolicies { get; set; } - /// Include commands registered by protocol clients, including SDK clients and extensions. - [JsonPropertyName("includeClientCommands")] - public bool? IncludeClientCommands { get; set; } + /// Runtime context discriminator (e.g., `cli`, `actions`). + [JsonPropertyName("agentContext")] + public string? AgentContext { get; set; } - /// Include enabled user-invocable skills and commands. - [JsonPropertyName("includeSkills")] - public bool? IncludeSkills { get; set; } + /// Whether to include instructions from every MCP server in the system prompt instead of only allowlisted servers. + [JsonPropertyName("allowAllMcpServerInstructions")] + public bool? AllowAllMcpServerInstructions { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Whether to disable the `ask_user` tool (encourages autonomous behavior). + [JsonPropertyName("askUserDisabled")] + public bool? AskUserDisabled { get; set; } -/// Result of invoking the slash command (text output, prompt to send to the agent, completion, or subcommand selection). -/// Polymorphic base type discriminated by kind. -[Experimental(Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(SlashCommandInvocationResultText), "text")] -[JsonDerivedType(typeof(SlashCommandInvocationResultAgentPrompt), "agent-prompt")] -[JsonDerivedType(typeof(SlashCommandInvocationResultCompleted), "completed")] -[JsonDerivedType(typeof(SlashCommandInvocationResultSelectSubcommand), "select-subcommand")] -public partial class SlashCommandInvocationResult -{ - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; -} + /// Allowlist of tool names available to this session. + [JsonPropertyName("availableTools")] + public IList? AvailableTools { get; set; } + /// Options scoped to the built-in CAPI (Copilot API) provider. + [JsonPropertyName("capi")] + public CapiSessionOptions? Capi { get; set; } -/// Slash-command invocation result containing text output plus Markdown/ANSI rendering flags. -/// The text variant of . -[Experimental(Diagnostics.Experimental)] -public partial class SlashCommandInvocationResultText : SlashCommandInvocationResult -{ - /// - [JsonIgnore] - public override string Kind => "text"; + /// Identifier of the client driving the session. + [JsonPropertyName("clientName")] + public string? ClientName { get; set; } - /// Whether text contains Markdown. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("markdown")] - public bool? Markdown { get; set; } + /// Whether to include the `Co-authored-by` trailer in commit messages. + [JsonPropertyName("coauthorEnabled")] + public bool? CoauthorEnabled { get; set; } - /// Whether ANSI sequences should be preserved. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("preserveAnsi")] - public bool? PreserveAnsi { get; set; } + /// Context tier for models with tiered pricing. The session uses this to derive effective `modelCapabilitiesOverrides` so compaction, truncation, token display, and request limits honor the selected tier. + [JsonPropertyName("contextTier")] + public OptionsUpdateContextTier? ContextTier { get; set; } - /// True when the invocation mutated user runtime settings; consumers caching settings should refresh. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("runtimeSettingsChanged")] - public bool? RuntimeSettingsChanged { get; set; } + /// Whether to allow auto-mode continuation across turns. + [JsonPropertyName("continueOnAutoMode")] + public bool? ContinueOnAutoMode { get; set; } - /// Text output for the client to render. - [JsonPropertyName("text")] - public required string Text { get; set; } -} + /// Override URL for the Copilot API endpoint. + [JsonPropertyName("copilotUrl")] + public string? CopilotUrl { get; set; } -/// Slash-command invocation result that submits an agent prompt, with display prompt, optional mode, optional user-facing notice, and settings-change flag. -/// The agent-prompt variant of . -[Experimental(Diagnostics.Experimental)] -public partial class SlashCommandInvocationResultAgentPrompt : SlashCommandInvocationResult -{ - /// - [JsonIgnore] - public override string Kind => "agent-prompt"; + /// Whether to default custom agents to local-only execution. + [JsonPropertyName("customAgentsLocalOnly")] + public bool? CustomAgentsLocalOnly { get; set; } - /// Prompt text to display to the user. - [JsonPropertyName("displayPrompt")] - public required string DisplayPrompt { get; set; } + /// Instruction source IDs to exclude from the system prompt. + [JsonPropertyName("disabledInstructionSources")] + public IList? DisabledInstructionSources { get; set; } - /// Optional target session mode for the agent prompt. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("mode")] - public SessionMode? Mode { get; set; } + /// Skill IDs that should be excluded from this session. + [JsonPropertyName("disabledSkills")] + public IList? DisabledSkills { get; set; } - /// Optional user-facing notice to show before the prompt is submitted. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("notice")] - public string? Notice { get; set; } + /// Whether to enable loading of `.github/hooks/` filesystem hooks. Separate from the SDK callback hook mechanism. + [JsonPropertyName("enableFileHooks")] + public bool? EnableFileHooks { get; set; } - /// Prompt to submit to the agent. - [JsonPropertyName("prompt")] - public required string Prompt { get; set; } + /// Whether to enable host git operations (context resolution, child repo scanning, git info in system prompt). + [JsonPropertyName("enableHostGitOperations")] + public bool? EnableHostGitOperations { get; set; } - /// True when the invocation mutated user runtime settings; consumers caching settings should refresh. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("runtimeSettingsChanged")] - public bool? RuntimeSettingsChanged { get; set; } -} + /// Whether to discover custom instructions on demand after successful file views (AGENTS.md / CLAUDE.md / .github/copilot-instructions.md surfacing). Combined with `skipCustomInstructions`. + [JsonPropertyName("enableOnDemandInstructionDiscovery")] + public bool? EnableOnDemandInstructionDiscovery { get; set; } -/// Slash-command invocation result indicating completion, with optional message and settings-change flag. -/// The completed variant of . -[Experimental(Diagnostics.Experimental)] -public partial class SlashCommandInvocationResultCompleted : SlashCommandInvocationResult -{ - /// - [JsonIgnore] - public override string Kind => "completed"; + /// Whether to surface reasoning-summary events from the model. + [JsonPropertyName("enableReasoningSummaries")] + public bool? EnableReasoningSummaries { get; set; } - /// Optional user-facing message describing the completed command. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("message")] - public string? Message { get; set; } + /// Whether shell-script safety heuristics are enabled. + [JsonPropertyName("enableScriptSafety")] + public bool? EnableScriptSafety { get; set; } - /// True when the invocation mutated user runtime settings; consumers caching settings should refresh. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("runtimeSettingsChanged")] - public bool? RuntimeSettingsChanged { get; set; } -} + /// Whether to enable cross-session store writes and reads. + [JsonPropertyName("enableSessionStore")] + public bool? EnableSessionStore { get; set; } -/// Selectable slash-command subcommand option with name, description, and optional group label. -[Experimental(Diagnostics.Experimental)] -public sealed class SlashCommandSelectSubcommandOption -{ - /// Human-readable description of the subcommand. - [JsonPropertyName("description")] - public string Description { get; set; } = string.Empty; + /// Whether to enable skill directory scanning and loading. Falls back to enableConfigDiscovery when unset. + [JsonPropertyName("enableSkills")] + public bool? EnableSkills { get; set; } - /// Optional group label for organizing options. - [JsonPropertyName("group")] - public string? Group { get; set; } + /// Whether to stream model responses. + [JsonPropertyName("enableStreaming")] + public bool? EnableStreaming { get; set; } - /// Subcommand name to invoke. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; -} + /// How env values are passed to MCP servers (`direct` inlines literal values; `indirect` resolves at launch). + [JsonPropertyName("envValueMode")] + public OptionsUpdateEnvValueMode? EnvValueMode { get; set; } -/// Slash-command invocation result asking the client to present subcommand options for a parent command. -/// The select-subcommand variant of . -[Experimental(Diagnostics.Experimental)] -public partial class SlashCommandInvocationResultSelectSubcommand : SlashCommandInvocationResult -{ - /// - [JsonIgnore] - public override string Kind => "select-subcommand"; + /// Override directory for the session-events log. When unset, the runtime's default events log directory is used. + [JsonPropertyName("eventsLogDirectory")] + public string? EventsLogDirectory { get; set; } - /// Parent command name that requires subcommand selection. - [JsonPropertyName("command")] - public required string Command { get; set; } + /// Whether subagent callback events should be forwarded into the session event log sink. + [JsonPropertyName("eventsLogIncludesSubagents")] + public bool? EventsLogIncludesSubagents { get; set; } - /// Available subcommand options for the client to present. - [JsonPropertyName("options")] - public required IList Options { get; set; } + /// Built-in subagent names to exclude from this session. Excluded built-ins are hidden from agent discovery and cannot be dispatched unless a custom agent with the same name is available. + [JsonPropertyName("excludedBuiltinAgents")] + public IList? ExcludedBuiltinAgents { get; set; } - /// True when the invocation mutated user runtime settings; consumers caching settings should refresh. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("runtimeSettingsChanged")] - public bool? RuntimeSettingsChanged { get; set; } + /// Denylist of tool names for this session. + [JsonPropertyName("excludedTools")] + public IList? ExcludedTools { get; set; } - /// Human-readable title for the selection UI. - [JsonPropertyName("title")] - public required string Title { get; set; } -} + /// Map of feature-flag IDs to their boolean enabled state. + [JsonPropertyName("featureFlags")] + public IDictionary? FeatureFlags { get; set; } -/// Slash command name and optional raw input string to invoke. -[Experimental(Diagnostics.Experimental)] -internal sealed class CommandsInvokeRequest -{ - /// Raw input after the command name. - [JsonPropertyName("input")] - public string? Input { get; set; } + /// Built-in subagent names to include in this session. When specified, only these built-ins are available, subject to runtime availability and exclusions. Custom agents with the same name remain available. Set to null to remove the allowlist restriction. + [JsonPropertyName("includedBuiltinAgents")] + public IList? IncludedBuiltinAgents { get; set; } - /// Command name. Leading slashes are stripped and the name is matched case-insensitively. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Full set of installed plugins for the session. Replaces the existing list; the runtime invalidates the skills cache only when the list materially changes. + [JsonPropertyName("installedPlugins")] + public IList? InstalledPlugins { get; set; } + + /// Stable integration identifier used for analytics and rate-limit attribution. + [JsonPropertyName("integrationId")] + public string? IntegrationId { get; set; } + + /// Whether experimental capabilities are enabled. + [JsonPropertyName("isExperimentalMode")] + public bool? IsExperimentalMode { get; set; } + + /// Whether interactive shell sessions are logged. + [JsonPropertyName("logInteractiveShells")] + public bool? LogInteractiveShells { get; set; } + + /// Identifier sent to LSP-style integrations. + [JsonPropertyName("lspClientName")] + public string? LspClientName { get; set; } + + /// Whether to expose the `manage_schedule` tool to the agent. The runtime always owns the per-session schedule registry; this flag only controls tool exposure (typically gated to staff users). + [JsonPropertyName("manageScheduleEnabled")] + public bool? ManageScheduleEnabled { get; set; } + + /// Maximum decoded byte size of a single model-facing binary tool result (e.g. an image) persisted inline in session events and re-presented to the model on later turns / resume. Larger results are persisted as a metadata-only marker and shown to the model as a short text note. Defaults to 10 MB. + [JsonPropertyName("maxInlineBinaryBytes")] + public long? MaxInlineBinaryBytes { get; set; } + + /// The model ID to use for assistant turns. + [JsonPropertyName("model")] + public string? Model { get; set; } + + /// Per-property model capability overrides for the selected model. + [JsonPropertyName("modelCapabilitiesOverrides")] + public ModelCapabilitiesOverride? ModelCapabilitiesOverrides { get; set; } + + /// Organization-level custom instructions to inject into the system prompt. + [JsonPropertyName("organizationCustomInstructions")] + public string? OrganizationCustomInstructions { get; set; } + + /// Custom model-provider configuration (BYOK). + [JsonPropertyName("provider")] + public ProviderConfig? Provider { get; set; } + + /// Reasoning effort for the selected model. CAPI values are model-defined and validated against the selected model; BYOK providers may define additional values. When omitted, no effort override is applied. + [JsonPropertyName("reasoningEffort")] + public string? ReasoningEffort { get; set; } + + /// Reasoning summary mode for supported model clients. + [JsonPropertyName("reasoningSummary")] + public OptionsUpdateReasoningSummary? ReasoningSummary { get; set; } + + /// Whether the session is running in an interactive UI. + [JsonPropertyName("runningInInteractiveMode")] + public bool? RunningInInteractiveMode { get; set; } + + /// Resolved sandbox configuration. + [JsonPropertyName("sandboxConfig")] + public SandboxConfig? SandboxConfig { get; set; } + + /// Replaces the session's capability set with the given list. Use to enable or disable capabilities mid-session (e.g., remove `memory` for reproducible scripted runs). Omit the field to leave the existing capability set unchanged. + [JsonPropertyName("sessionCapabilities")] + public IList? SessionCapabilities { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; -} -/// Indicates whether the pending client-handled command was completed successfully. -[Experimental(Diagnostics.Experimental)] -public sealed class CommandsHandlePendingCommandResult -{ - /// Whether the command was handled successfully. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// Optional session limits. Pass null to clear the session limits. + [JsonPropertyName("sessionLimits")] + public SessionLimitsConfig? SessionLimits { get; set; } + + /// Per-session settings for built-in shell tools. + [JsonPropertyName("shell")] + public ShellOptions? Shell { get; set; } + + /// Use shell.initProfile instead. Shell init profile (`None` or `NonInteractive`). + [EditorBrowsable(EditorBrowsableState.Never)] +#if NET5_0_OR_GREATER + [Obsolete("This member is deprecated and will be removed in a future version.", DiagnosticId = "GHCP001")] +#endif + [JsonPropertyName("shellInitProfile")] + public string? ShellInitProfile { get; set; } + + /// PowerShell process flags applied to built-in and user-requested shell commands. + [JsonPropertyName("shellProcessFlags")] + public IList? ShellProcessFlags { get; set; } + + /// Additional directories to search for skills. + [JsonPropertyName("skillDirectories")] + public IList? SkillDirectories { get; set; } + + /// Whether to skip loading custom instruction sources. + [JsonPropertyName("skipCustomInstructions")] + public bool? SkipCustomInstructions { get; set; } + + /// Whether to skip embedding retrieval pipeline initialization and execution. + [JsonPropertyName("skipEmbeddingRetrieval")] + public bool? SkipEmbeddingRetrieval { get; set; } + + /// When true, the selected custom agent's prompt is not injected into the user message (skill context is still injected). Used by automation triggers where the agent prompt is already in the problem statement. + [JsonPropertyName("suppressCustomAgentPrompt")] + public bool? SuppressCustomAgentPrompt { get; set; } + + /// Controls how availableTools (allowlist) and excludedTools (denylist) combine when both are set. + [JsonPropertyName("toolFilterPrecedence")] + public OptionsUpdateToolFilterPrecedence? ToolFilterPrecedence { get; set; } + + /// Optional path for trajectory output. + [JsonPropertyName("trajectoryFile")] + public string? TrajectoryFile { get; set; } + + /// Output verbosity level for supported models. + [JsonPropertyName("verbosity")] + public Verbosity? Verbosity { get; set; } + + /// Absolute working-directory path for shell tools. + [JsonPropertyName("workingDirectory")] + public string? WorkingDirectory { get; set; } } -/// Pending command request ID and an optional error if the client handler failed. +/// Parameters for (re)loading the merged LSP configuration set. [Experimental(Diagnostics.Experimental)] -internal sealed class CommandsHandlePendingCommandRequest +internal sealed class LspInitializeRequest { - /// Error message if the command handler failed. - [JsonPropertyName("error")] - public string? Error { get; set; } + /// Force re-initialization even when LSP configs were already loaded for the working directory. + [JsonPropertyName("force")] + public bool? Force { get; set; } - /// Request ID from the command invocation event. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; + /// Git root used as the boundary when traversing for project-level LSP configs (supports monorepos). + [JsonPropertyName("gitRoot")] + public string? GitRoot { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; + + /// Working directory used to load project-level LSP configs. Defaults to the session working directory when omitted. + [JsonPropertyName("workingDirectory")] + public string? WorkingDirectory { get; set; } } -/// Error message produced while executing the command, if any. +/// Discovered extension metadata, including source-qualified ID, name, discovery source, status, and optional process ID. [Experimental(Diagnostics.Experimental)] -public sealed class ExecuteCommandResult +public sealed class Extension { - /// Error message produced while executing the command, if any. Omitted when the handler succeeded. - [JsonPropertyName("error")] - public string? Error { get; set; } + /// Source-qualified ID (e.g., 'project:my-ext', 'user:auth-helper', 'plugin:my-plugin:my-ext'). + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; + + /// Extension name (directory name). + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Process ID if the extension is running. + [JsonPropertyName("pid")] + public long? Pid { get; set; } + + /// Discovery source: project (.github/extensions/), user (~/.copilot/extensions/), plugin (installed plugin), or session (session-state/<id>/extensions/). + [JsonPropertyName("source")] + public ExtensionSource Source { get; set; } + + /// Current status: running, disabled, failed, or starting. + [JsonPropertyName("status")] + public ExtensionStatus Status { get; set; } } -/// Slash command name and argument string to execute synchronously. +/// Extensions discovered for the session, with their current status. [Experimental(Diagnostics.Experimental)] -internal sealed class ExecuteCommandParams +public sealed class ExtensionList { - /// Argument string to pass to the command (empty string if none). - [JsonPropertyName("args")] - public string Args { get; set; } = string.Empty; - - /// Name of the slash command to invoke (without the leading '/'). - [JsonPropertyName("commandName")] - public string CommandName { get; set; } = string.Empty; + /// Discovered extensions and their current status. + [JsonPropertyName("extensions")] + public IList Extensions { get => field ??= []; set; } +} +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionExtensionsListRequest +{ /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Indicates whether the command was accepted into the local execution queue. +/// Source-qualified extension identifier to enable for the session. [Experimental(Diagnostics.Experimental)] -public sealed class EnqueueCommandResult +internal sealed class ExtensionsEnableRequest { - /// True when the command was accepted into the local execution queue. False when the call targets a session that does not support local command queueing (e.g. remote sessions). - [JsonPropertyName("queued")] - public bool Queued { get; set; } -} - -/// Slash-prefixed command string to enqueue for FIFO processing. -[Experimental(Diagnostics.Experimental)] -internal sealed class EnqueueCommandParams -{ - /// Slash-prefixed command string to enqueue, e.g. '/compact' or '/model gpt-4'. Queued FIFO with any in-flight items; if the session is idle, processing kicks off immediately. - [JsonPropertyName("command")] - public string Command { get; set; } = string.Empty; + /// Source-qualified extension ID to enable. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Indicates whether the queued-command response was matched to a pending request. -[Experimental(Diagnostics.Experimental)] -public sealed class CommandsRespondToQueuedCommandResult -{ - /// Whether a pending queued command with the given request ID was found and resolved. False when the request was already resolved, cancelled, or unknown. - [JsonPropertyName("success")] - public bool Success { get; set; } -} - -/// Result of the queued command execution. -/// Data type discriminated by handled. -[Experimental(Diagnostics.Experimental)] -public partial class QueuedCommandResult -{ - /// The boolean discriminator. - [JsonPropertyName("handled")] - public bool Handled { get; set; } - - /// When true, the runtime will not process subsequent queued commands until a new request comes in. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("stopProcessingQueue")] - public bool? StopProcessingQueue { get; set; } -} - -/// Queued-command request ID and the result indicating whether the host executed it (and whether to stop processing further queued commands). +/// Source-qualified extension identifier to disable for the session. [Experimental(Diagnostics.Experimental)] -internal sealed class CommandsRespondToQueuedCommandRequest +internal sealed class ExtensionsDisableRequest { - /// Request ID from the `command.queued` event the host is responding to. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; - - /// Result of the queued command execution. - [JsonPropertyName("result")] - public QueuedCommandResult Result { get => field ??= new(); set; } + /// Source-qualified extension ID to disable. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Telemetry engagement ID for the session, when available. -[Experimental(Diagnostics.Experimental)] -public sealed class SessionTelemetryEngagement -{ - /// Current telemetry engagement ID, when available. - [JsonPropertyName("engagementId")] - public string? EngagementId { get; set; } -} - /// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionTelemetryGetEngagementIdRequest +internal sealed class SessionExtensionsReloadRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Feature override key/value pairs to attach to subsequent telemetry events from this session. +/// Attachment union accepted by push input, covering files, directories, GitHub objects, blobs, snippets, and extension context. +/// Polymorphic base type discriminated by type. [Experimental(Diagnostics.Experimental)] -internal sealed class TelemetrySetFeatureOverridesRequest +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "type", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(PushAttachmentFile), "file")] +[JsonDerivedType(typeof(PushAttachmentDirectory), "directory")] +[JsonDerivedType(typeof(PushAttachmentSelection), "selection")] +[JsonDerivedType(typeof(PushAttachmentGitHubReference), "github_reference")] +[JsonDerivedType(typeof(PushAttachmentGitHubCommit), "github_commit")] +[JsonDerivedType(typeof(PushAttachmentGitHubRelease), "github_release")] +[JsonDerivedType(typeof(PushAttachmentGitHubActionsJob), "github_actions_job")] +[JsonDerivedType(typeof(PushAttachmentGitHubRepository), "github_repository")] +[JsonDerivedType(typeof(PushAttachmentGitHubFileDiff), "github_file_diff")] +[JsonDerivedType(typeof(PushAttachmentGitHubTreeComparison), "github_tree_comparison")] +[JsonDerivedType(typeof(PushAttachmentGitHubUrl), "github_url")] +[JsonDerivedType(typeof(PushAttachmentGitHubFile), "github_file")] +[JsonDerivedType(typeof(PushAttachmentGitHubSnippet), "github_snippet")] +[JsonDerivedType(typeof(PushAttachmentBlob), "blob")] +[JsonDerivedType(typeof(PushAttachmentExtensionContext), "extension_context")] +public partial class PushAttachment { - /// Override key/value pairs to attach to subsequent telemetry events from this session. Replaces any previously-set overrides. - [JsonPropertyName("features")] - public IDictionary Features { get => field ??= new Dictionary(); set; } - - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// The type discriminator. + [JsonPropertyName("type")] + public virtual string Type { get; set; } = string.Empty; } -/// Transient answer generated from current conversation context. + +/// Optional line range to scope the attachment to a specific section of the file. [Experimental(Diagnostics.Experimental)] -public sealed class UIEphemeralQueryResult +public sealed class PushAttachmentFileLineRange { - /// Full assistant response text. - [JsonPropertyName("answer")] - public string Answer { get; set; } = string.Empty; + /// End line number (1-based, inclusive). + [JsonPropertyName("end")] + public long End { get; set; } + + /// Start line number (1-based). + [JsonPropertyName("start")] + public long Start { get; set; } } -/// Transient question to answer without adding it to conversation history. +/// File attachment. +/// The file variant of . [Experimental(Diagnostics.Experimental)] -internal sealed class UIEphemeralQueryRequest +public partial class PushAttachmentFile : PushAttachment { - /// In-process `AbortSignal` forwarded to the model client to cancel an in-flight request. Marked internal: excluded from the public SDK surface. Replaced by an explicit cancellation token + cancel RPC in the SDK migration. - [JsonInclude] - [JsonPropertyName("abortSignal")] - internal JsonElement? AbortSignal { get; set; } + /// + [JsonIgnore] + public override string Type => "file"; - /// In-process streaming callback `(text) => void` invoked with each token as the model emits it. Marked internal: excluded from the public SDK surface. In a process-separated SDK this is replaced by a streaming RPC that yields chunks and a final answer. - [JsonInclude] - [JsonPropertyName("onChunk")] - internal JsonElement? OnChunk { get; set; } + /// User-facing display name for the attachment. + [JsonPropertyName("displayName")] + public required string DisplayName { get; set; } - /// Question to answer from the current conversation context. - [JsonPropertyName("question")] - public string Question { get; set; } = string.Empty; + /// Optional line range to scope the attachment to a specific section of the file. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("lineRange")] + public PushAttachmentFileLineRange? LineRange { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Absolute file path. + [JsonPropertyName("path")] + public required string Path { get; set; } } -/// The elicitation response (accept with form values, decline, or cancel). +/// Directory attachment. +/// The directory variant of . [Experimental(Diagnostics.Experimental)] -public sealed class UIElicitationResponse +public partial class PushAttachmentDirectory : PushAttachment { - /// The user's response: accept (submitted), decline (rejected), or cancel (dismissed). - [JsonPropertyName("action")] - public UIElicitationResponseAction Action { get; set; } + /// + [JsonIgnore] + public override string Type => "directory"; - /// The form values submitted by the user (present when action is 'accept'). - [JsonPropertyName("content")] - public IDictionary? Content { get; set; } + /// User-facing display name for the attachment. + [JsonPropertyName("displayName")] + public required string DisplayName { get; set; } + + /// Absolute directory path. + [JsonPropertyName("path")] + public required string Path { get; set; } } -/// JSON Schema describing the form fields to present to the user. +/// End position of the selection. [Experimental(Diagnostics.Experimental)] -public sealed class UIElicitationSchema +public sealed class PushAttachmentSelectionDetailsEnd { - /// Form field definitions, keyed by field name. - [JsonPropertyName("properties")] - public IDictionary Properties { get => field ??= new Dictionary(); set; } - - /// List of required field names. - [JsonPropertyName("required")] - public IList? Required { get; set; } + /// End character offset within the line (0-based). + [JsonPropertyName("character")] + public long Character { get; set; } - /// Schema type indicator (always 'object'). - [JsonPropertyName("type")] - public string Type { get; set; } = string.Empty; + /// End line number (0-based). + [JsonPropertyName("line")] + public long Line { get; set; } } -/// Prompt message and JSON schema describing the form fields to elicit from the user. +/// Start position of the selection. [Experimental(Diagnostics.Experimental)] -internal sealed class UIElicitationRequest +public sealed class PushAttachmentSelectionDetailsStart { - /// Message describing what information is needed from the user. - [JsonPropertyName("message")] - public string Message { get; set; } = string.Empty; - - /// JSON Schema describing the form fields to present to the user. - [JsonPropertyName("requestedSchema")] - public UIElicitationSchema RequestedSchema { get => field ??= new(); set; } + /// Start character offset within the line (0-based). + [JsonPropertyName("character")] + public long Character { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Start line number (0-based). + [JsonPropertyName("line")] + public long Line { get; set; } } -/// Indicates whether the elicitation response was accepted; false if it was already resolved by another client. +/// Position range of the selection within the file. [Experimental(Diagnostics.Experimental)] -public sealed class UIElicitationResult +public sealed class PushAttachmentSelectionDetails { - /// Whether the response was accepted. False if the request was already resolved by another client. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// End position of the selection. + [JsonPropertyName("end")] + public PushAttachmentSelectionDetailsEnd End { get => field ??= new(); set; } + + /// Start position of the selection. + [JsonPropertyName("start")] + public PushAttachmentSelectionDetailsStart Start { get => field ??= new(); set; } } -/// Pending elicitation request ID and the user's response (accept/decline/cancel + form values). +/// Code selection attachment from an editor. +/// The selection variant of . [Experimental(Diagnostics.Experimental)] -internal sealed class UIHandlePendingElicitationRequest +public partial class PushAttachmentSelection : PushAttachment { - /// The unique request ID from the elicitation.requested event. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Type => "selection"; - /// The elicitation response (accept with form values, decline, or cancel). - [JsonPropertyName("result")] - public UIElicitationResponse Result { get => field ??= new(); set; } + /// User-facing display name for the selection. + [JsonPropertyName("displayName")] + public required string DisplayName { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Absolute path to the file containing the selection. + [JsonPropertyName("filePath")] + public required string FilePath { get; set; } -/// Indicates whether the pending UI request was resolved by this call. -[Experimental(Diagnostics.Experimental)] -public sealed class UIHandlePendingResult -{ - /// True if the request was still pending and was resolved by this call. False if the request ID was unknown, already resolved by another client (e.g. GitHub), expired, or otherwise no longer pending. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// Position range of the selection within the file. + [JsonPropertyName("selection")] + public required PushAttachmentSelectionDetails Selection { get; set; } + + /// The selected text content. + [JsonPropertyName("text")] + public required string Text { get; set; } } -/// User response for a pending user-input request, with answer text and whether it was typed freeform. +/// GitHub issue, pull request, or discussion reference. +/// The github_reference variant of . [Experimental(Diagnostics.Experimental)] -public sealed class UIUserInputResponse +public partial class PushAttachmentGitHubReference : PushAttachment { - /// The user's answer text. - [JsonPropertyName("answer")] - public string Answer { get; set; } = string.Empty; - - /// True if the user typed a freeform response, false if they selected a presented choice. Used by telemetry to differentiate between free text input and choice selection. - [JsonPropertyName("wasFreeform")] - public bool WasFreeform { get; set; } + /// + [JsonIgnore] + public override string Type => "github_reference"; + + /// Issue, pull request, or discussion number. + [JsonPropertyName("number")] + public required long Number { get; set; } + + /// Type of GitHub reference. + [JsonPropertyName("referenceType")] + public required PushAttachmentGitHubReferenceType ReferenceType { get; set; } + + /// Current state of the referenced item (e.g., open, closed, merged). + [JsonPropertyName("state")] + public required string State { get; set; } + + /// Title of the referenced item. + [JsonPropertyName("title")] + public required string Title { get; set; } + + /// URL to the referenced item on GitHub. + [JsonPropertyName("url")] + public required string Url { get; set; } } -/// Request ID of a pending `user_input.requested` event and the user's response. +/// Pointer to a GitHub repository. [Experimental(Diagnostics.Experimental)] -internal sealed class UIHandlePendingUserInputRequest +public sealed class PushGitHubRepoRef { - /// The unique request ID from the user_input.requested event. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; + /// Numeric GitHub repository id. + [JsonPropertyName("id")] + public long? Id { get; set; } - /// User response for a pending user-input request, with answer text and whether it was typed freeform. - [JsonPropertyName("response")] - public UIUserInputResponse Response { get => field ??= new(); set; } + /// Repository name (without owner). + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Repository owner login (user or organization). + [JsonPropertyName("owner")] + public string Owner { get; set; } = string.Empty; } -/// Optional sampling result payload. Omit to reject/cancel the sampling request without providing a result. +/// Pointer to a GitHub commit. +/// The github_commit variant of . [Experimental(Diagnostics.Experimental)] -public sealed class UIHandlePendingSamplingResponse +public partial class PushAttachmentGitHubCommit : PushAttachment { + /// + [JsonIgnore] + public override string Type => "github_commit"; + + /// First line of the commit message. + [JsonPropertyName("message")] + public required string Message { get; set; } + + /// Full commit SHA. + [JsonPropertyName("oid")] + public required string Oid { get; set; } + + /// Repository the commit belongs to. + [JsonPropertyName("repo")] + public required PushGitHubRepoRef Repo { get; set; } + + /// URL to the commit on GitHub. + [JsonPropertyName("url")] + public required string Url { get; set; } } -/// Request ID of a pending `sampling.requested` event and an optional sampling result payload (omit to reject). +/// Pointer to a GitHub release. +/// The github_release variant of . [Experimental(Diagnostics.Experimental)] -internal sealed class UIHandlePendingSamplingRequest +public partial class PushAttachmentGitHubRelease : PushAttachment { - /// The unique request ID from the sampling.requested event. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Type => "github_release"; - /// Optional sampling result payload. Omit to reject/cancel the sampling request without providing a result. - [JsonPropertyName("response")] - public UIHandlePendingSamplingResponse? Response { get; set; } + /// Human-readable release name. + [JsonPropertyName("name")] + public required string Name { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Repository the release belongs to. + [JsonPropertyName("repo")] + public required PushGitHubRepoRef Repo { get; set; } + + /// Git tag the release is anchored to. + [JsonPropertyName("tagName")] + public required string TagName { get; set; } + + /// URL to the release on GitHub. + [JsonPropertyName("url")] + public required string Url { get; set; } } -/// Request ID of a pending `auto_mode_switch.requested` event and the user's response. +/// Pointer to a GitHub Actions job. +/// The github_actions_job variant of . [Experimental(Diagnostics.Experimental)] -internal sealed class UIHandlePendingAutoModeSwitchRequest +public partial class PushAttachmentGitHubActionsJob : PushAttachment { - /// The unique request ID from the auto_mode_switch.requested event. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Type => "github_actions_job"; - /// User's choice for auto-mode switching: yes (allow this turn), yes_always (allow + persist as setting), or no (decline). - [JsonPropertyName("response")] - public UIAutoModeSwitchResponse Response { get; set; } + /// Terminal conclusion of the job when finished (e.g., success, failure, cancelled). Absent for in-progress jobs. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("conclusion")] + public string? Conclusion { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Job id within the workflow run. + [JsonPropertyName("jobId")] + public required long JobId { get; set; } + + /// Display name of the job. + [JsonPropertyName("jobName")] + public required string JobName { get; set; } + + /// Repository the workflow run belongs to. + [JsonPropertyName("repo")] + public required PushGitHubRepoRef Repo { get; set; } + + /// URL to the job on GitHub. + [JsonPropertyName("url")] + public required string Url { get; set; } + + /// Display name of the workflow the job ran in. + [JsonPropertyName("workflowName")] + public required string WorkflowName { get; set; } } -/// The user's selected action for an exhausted session limit. +/// Pointer to a GitHub repository. +/// The github_repository variant of . [Experimental(Diagnostics.Experimental)] -public sealed class UISessionLimitsExhaustedResponse +public partial class PushAttachmentGitHubRepository : PushAttachment { - /// Action selected by the user. - [JsonPropertyName("action")] - public UISessionLimitsExhaustedResponseAction Action { get; set; } + /// + [JsonIgnore] + public override string Type => "github_repository"; - /// AI Credits to add to the current max when action is 'add'. - [JsonPropertyName("additionalAiCredits")] - public double? AdditionalAiCredits { get; set; } + /// Short description of the repository. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("description")] + public string? Description { get; set; } - /// New absolute max AI Credits when action is 'set'. - [JsonPropertyName("maxAiCredits")] - public double? MaxAiCredits { get; set; } + /// Git ref this attachment is anchored at (branch, tag, or commit). When absent the default branch is implied. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("ref")] + public string? Ref { get; set; } + + /// Repository pointer. + [JsonPropertyName("repo")] + public required PushGitHubRepoRef Repo { get; set; } + + /// URL to the repository on GitHub. + [JsonPropertyName("url")] + public required string Url { get; set; } } -/// Request ID of a pending `session_limits_exhausted.requested` event and the user's selected limit action. +/// One side of a file diff (head or base). [Experimental(Diagnostics.Experimental)] -internal sealed class UIHandlePendingSessionLimitsExhaustedRequest +public sealed class PushAttachmentGitHubFileDiffSide { - /// The unique request ID from the session_limits_exhausted.requested event. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; + /// Repository-relative path to the file. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; - /// The selected session-limit action. - [JsonPropertyName("response")] - public UISessionLimitsExhaustedResponse Response { get => field ??= new(); set; } + /// Git ref (branch, tag, or commit SHA) the file is read at. + [JsonPropertyName("ref")] + public string Ref { get; set; } = string.Empty; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Repository the file lives in. + [JsonPropertyName("repo")] + public PushGitHubRepoRef Repo { get => field ??= new(); set; } } -/// User response for a pending exit-plan-mode request, with approval state, selected action, auto-approve flag, and feedback. +/// Pointer to a single-file diff. At least one of `head` and `base` must be present. +/// The github_file_diff variant of . [Experimental(Diagnostics.Experimental)] -public sealed class UIExitPlanModeResponse +public partial class PushAttachmentGitHubFileDiff : PushAttachment { - /// Whether the plan was approved. - [JsonPropertyName("approved")] - public bool Approved { get; set; } + /// + [JsonIgnore] + public override string Type => "github_file_diff"; - /// Whether subsequent edits should be auto-approved without confirmation. - [JsonPropertyName("autoApproveEdits")] - public bool? AutoApproveEdits { get; set; } + /// File location on the base side of the diff. Absent for additions. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("base")] + public PushAttachmentGitHubFileDiffSide? Base { get; set; } - /// Feedback from the user when they declined the plan or requested changes. - [JsonPropertyName("feedback")] - public string? Feedback { get; set; } + /// File location on the head side of the diff. Absent for deletions. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("head")] + public PushAttachmentGitHubFileDiffSide? Head { get; set; } - /// The action the user selected. Defaults to 'autopilot' when autoApproveEdits is true, otherwise 'interactive'. - [JsonPropertyName("selectedAction")] - public UIExitPlanModeAction? SelectedAction { get; set; } + /// URL to the diff on GitHub (e.g., a commit, compare, or PR-file URL). + [JsonPropertyName("url")] + public required string Url { get; set; } } -/// Request ID of a pending `exit_plan_mode.requested` event and the user's response. +/// One side of a tree comparison (head or base). [Experimental(Diagnostics.Experimental)] -internal sealed class UIHandlePendingExitPlanModeRequest +public sealed class PushAttachmentGitHubTreeComparisonSide { - /// The unique request ID from the exit_plan_mode.requested event. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; - - /// User response for a pending exit-plan-mode request, with approval state, selected action, auto-approve flag, and feedback. - [JsonPropertyName("response")] - public UIExitPlanModeResponse Response { get => field ??= new(); set; } + /// Repository the revision belongs to. + [JsonPropertyName("repo")] + public PushGitHubRepoRef Repo { get => field ??= new(); set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Git revision (branch, tag, or commit SHA). + [JsonPropertyName("revision")] + public string Revision { get; set; } = string.Empty; } -/// Register an in-process handler for `auto_mode_switch.requested` events. The caller still attaches the actual listener via the standard event-subscription mechanism; this registration solely tells the server bridge to skip its own dispatch (so a remote client doesn't race the in-process handler for the same requestId). +/// Pointer to a comparison between two git revisions. +/// The github_tree_comparison variant of . [Experimental(Diagnostics.Experimental)] -public sealed class UIRegisterDirectAutoModeSwitchHandlerResult +public partial class PushAttachmentGitHubTreeComparison : PushAttachment { - /// Opaque handle representing the registration. Pass this same handle to `unregisterDirectAutoModeSwitchHandler` when the in-process handler is no longer active. Multiple registrations are reference-counted; the server bridge will only dispatch auto-mode-switch requests when no handles are active. - [JsonPropertyName("handle")] - public string Handle { get; set; } = string.Empty; -} + /// + [JsonIgnore] + public override string Type => "github_tree_comparison"; -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionUiRegisterDirectAutoModeSwitchHandlerRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Base side of the comparison. + [JsonPropertyName("base")] + public required PushAttachmentGitHubTreeComparisonSide Base { get; set; } -/// Indicates whether the handle was active and the registration count was decremented. -[Experimental(Diagnostics.Experimental)] -public sealed class UIUnregisterDirectAutoModeSwitchHandlerResult -{ - /// True if the handle was active and decremented the counter; false if the handle was unknown. - [JsonPropertyName("unregistered")] - public bool Unregistered { get; set; } + /// Head side of the comparison. + [JsonPropertyName("head")] + public required PushAttachmentGitHubTreeComparisonSide Head { get; set; } + + /// URL to the comparison on GitHub. + [JsonPropertyName("url")] + public required string Url { get; set; } } -/// Opaque handle previously returned by `registerDirectAutoModeSwitchHandler` to release. +/// Generic GitHub URL reference. +/// The github_url variant of . [Experimental(Diagnostics.Experimental)] -internal sealed class UIUnregisterDirectAutoModeSwitchHandlerRequest +public partial class PushAttachmentGitHubUrl : PushAttachment { - /// Handle previously returned by `registerDirectAutoModeSwitchHandler`. - [JsonPropertyName("handle")] - public string Handle { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Type => "github_url"; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// URL to the GitHub resource. + [JsonPropertyName("url")] + public required string Url { get; set; } } -/// Indicates whether the operation succeeded. +/// Pointer to a file in a GitHub repository at a specific ref. +/// The github_file variant of . [Experimental(Diagnostics.Experimental)] -public sealed class PermissionsConfigureResult +public partial class PushAttachmentGitHubFile : PushAttachment { - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } -} + /// + [JsonIgnore] + public override string Type => "github_file"; -/// Source descriptor for a `session.permissions.configure` content-exclusion rule, with source name and type. -[Experimental(Diagnostics.Experimental)] -public sealed class PermissionsConfigureAdditionalContentExclusionPolicyRuleSource -{ - /// Gets or sets the name value. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Repository-relative path to the file. + [JsonPropertyName("path")] + public required string Path { get; set; } - /// Gets or sets the type value. - [JsonPropertyName("type")] - public string Type { get; set; } = string.Empty; + /// Git ref the file is read at (branch, tag, or commit SHA). + [JsonPropertyName("ref")] + public required string Ref { get; set; } + + /// Repository the file lives in. + [JsonPropertyName("repo")] + public required PushGitHubRepoRef Repo { get; set; } + + /// URL to the file on GitHub. + [JsonPropertyName("url")] + public required string Url { get; set; } } -/// Single content-exclusion rule supplied to `session.permissions.configure`, with paths, match conditions, and source. +/// Pointer to a line range inside a file in a GitHub repository. +/// The github_snippet variant of . [Experimental(Diagnostics.Experimental)] -public sealed class PermissionsConfigureAdditionalContentExclusionPolicyRule +public partial class PushAttachmentGitHubSnippet : PushAttachment { - /// Gets or sets the ifAnyMatch value. - [JsonPropertyName("ifAnyMatch")] - public IList? IfAnyMatch { get; set; } + /// + [JsonIgnore] + public override string Type => "github_snippet"; - /// Gets or sets the ifNoneMatch value. - [JsonPropertyName("ifNoneMatch")] - public IList? IfNoneMatch { get; set; } + /// Line range the snippet covers. + [JsonPropertyName("lineRange")] + public required PushAttachmentFileLineRange LineRange { get; set; } - /// Gets or sets the paths value. - [JsonPropertyName("paths")] - public IList Paths { get => field ??= []; set; } + /// Repository-relative path to the file. + [JsonPropertyName("path")] + public required string Path { get; set; } - /// Source descriptor for a `session.permissions.configure` content-exclusion rule, with source name and type. - [JsonPropertyName("source")] - public PermissionsConfigureAdditionalContentExclusionPolicyRuleSource Source { get => field ??= new(); set; } + /// Git ref the file is read at (branch, tag, or commit SHA). + [JsonPropertyName("ref")] + public required string Ref { get; set; } + + /// Repository the file lives in. + [JsonPropertyName("repo")] + public required PushGitHubRepoRef Repo { get; set; } + + /// URL to the snippet on GitHub (with line anchor). + [JsonPropertyName("url")] + public required string Url { get; set; } } -/// Content-exclusion policy supplied to `session.permissions.configure`, with rules, last-updated data, and scope. +/// Blob attachment with inline base64-encoded data. +/// The blob variant of . [Experimental(Diagnostics.Experimental)] -public sealed class PermissionsConfigureAdditionalContentExclusionPolicy +public partial class PushAttachmentBlob : PushAttachment { - /// Gets or sets the last_updated_at value. - [JsonPropertyName("last_updated_at")] - public JsonElement LastUpdatedAt { get; set; } + /// + [JsonIgnore] + public override string Type => "blob"; - /// Gets or sets the rules value. - [JsonPropertyName("rules")] - public IList Rules { get => field ??= []; set; } + /// Base64-encoded content. + [Base64String] + [JsonPropertyName("data")] + public required string Data { get; set; } - /// Allowed values for the `PermissionsConfigureAdditionalContentExclusionPolicyScope` enumeration. - [JsonPropertyName("scope")] - public PermissionsConfigureAdditionalContentExclusionPolicyScope Scope { get; set; } + /// User-facing display name for the attachment. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("displayName")] + public string? DisplayName { get; set; } + + /// MIME type of the inline data. + [JsonPropertyName("mimeType")] + public required string MimeType { get; set; } } -/// If specified, replaces the session's path-permission policy. The runtime constructs the appropriate PathManager based on these inputs (rooted at the session's working directory). Omit to leave the current path policy unchanged. +/// Slim input shape for extension_context attachments; identity fields are runtime-derived. +/// The extension_context variant of . [Experimental(Diagnostics.Experimental)] -public sealed class PermissionPathsConfig +public partial class PushAttachmentExtensionContext : PushAttachment { - /// Additional directories to allow tool access to (in addition to the session's working directory). When `unrestricted` is true, these are still pre-populated on the UnrestrictedPathManager so they remain visible via getDirectories() (e.g. for @-mention completion). - [JsonPropertyName("additionalDirectories")] - public IList? AdditionalDirectories { get; set; } - - /// Whether to include the system temp directory in the allowed list (defaults to true). Ignored when `unrestricted` is true. - [JsonPropertyName("includeTempDirectory")] - public bool? IncludeTempDirectory { get; set; } + /// + [JsonIgnore] + public override string Type => "extension_context"; - /// If true, the runtime allows access to all paths without prompting. Equivalent to constructing an UnrestrictedPathManager. - [JsonPropertyName("unrestricted")] - public bool? Unrestricted { get; set; } + /// Caller-supplied JSON payload (required, may be null but not undefined). + [JsonPropertyName("payload")] + public required JsonElement Payload { get; set; } - /// Workspace root path (special-cased to be allowed even before the directory exists). Ignored when `unrestricted` is true. - [JsonPropertyName("workspacePath")] - public string? WorkspacePath { get; set; } + /// Human-readable composer pill label. + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] + [MinLength(1)] + [JsonPropertyName("title")] + public required string Title { get; set; } } -/// If specified, replaces the session's approved/denied permission rules. Omit to leave the current rules unchanged. +/// Parameters for session.extensions.sendAttachmentsToMessage. [Experimental(Diagnostics.Experimental)] -public sealed class PermissionRulesSet +internal sealed class SendAttachmentsToMessageParams { - /// Rules that auto-approve matching requests. - [JsonPropertyName("approved")] - public IList Approved { get => field ??= []; set; } + /// Attachments to push into the next user-message turn. extension_context entries take the slim shape; standard variants take their full AttachmentSchema shape. + [JsonPropertyName("attachments")] + public IList Attachments { get => field ??= []; set; } - /// Rules that auto-deny matching requests. - [JsonPropertyName("denied")] - public IList Denied { get => field ??= []; set; } + /// Optional canvas instance binding the push for provenance. When supplied, the runtime resolves the canvas, verifies it is owned by the calling extension, and stamps canvasId/instanceId onto each extension_context entry. When omitted, no resolution runs and those fields stay unset on the attachment. + [JsonPropertyName("instanceId")] + public string? InstanceId { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// If specified, replaces the session's URL-permission policy. The runtime constructs a fresh DefaultUrlManager based on these inputs. Omit to leave the current URL policy unchanged. +/// Indicates whether the external tool call result was handled successfully. [Experimental(Diagnostics.Experimental)] -public sealed class PermissionUrlsConfig +public sealed class HandlePendingToolCallResult { - /// Initial list of allowed URL/domain patterns. Patterns may include path components. Ignored when `unrestricted` is true. - [JsonPropertyName("initialAllowed")] - public IList? InitialAllowed { get; set; } - - /// If true, the runtime allows access to all URLs without prompting. Initial allow-list is ignored when this is true. - [JsonPropertyName("unrestricted")] - public bool? Unrestricted { get; set; } + /// Whether the tool call result was handled successfully. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// Patch of permission policy fields to apply (omit a field to leave it unchanged). +/// Pending external tool call request ID, with the tool result or an error describing why it failed. [Experimental(Diagnostics.Experimental)] -internal sealed class PermissionsConfigureParams +internal sealed class HandlePendingToolCallRequest { - /// If specified, replaces the host-supplied GitHub Content Exclusion policies on the session (combined with natively-discovered policies when evaluating tool/file access). Omit to leave the current policies unchanged. - [JsonPropertyName("additionalContentExclusionPolicies")] - public IList? AdditionalContentExclusionPolicies { get; set; } - - /// If specified, sets whether path/URL read permission requests are auto-approved. Omit to leave the current value unchanged. - [JsonPropertyName("approveAllReadPermissionRequests")] - public bool? ApproveAllReadPermissionRequests { get; set; } - - /// If specified, sets whether tool permission requests are auto-approved without prompting. Omit to leave the current value unchanged. - [JsonPropertyName("approveAllToolPermissionRequests")] - public bool? ApproveAllToolPermissionRequests { get; set; } + /// Error message if the tool call failed. + [JsonPropertyName("error")] + public string? Error { get; set; } - /// If specified, replaces the session's path-permission policy. The runtime constructs the appropriate PathManager based on these inputs (rooted at the session's working directory). Omit to leave the current path policy unchanged. - [JsonPropertyName("paths")] - public PermissionPathsConfig? Paths { get; set; } + /// Request ID of the pending tool call. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; - /// If specified, replaces the session's approved/denied permission rules. Omit to leave the current rules unchanged. - [JsonPropertyName("rules")] - public PermissionRulesSet? Rules { get; set; } + /// Tool call result (string or expanded result object). + [JsonPropertyName("result")] + public JsonElement? Result { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; - - /// If specified, replaces the session's URL-permission policy. The runtime constructs a fresh DefaultUrlManager based on these inputs. Omit to leave the current URL policy unchanged. - [JsonPropertyName("urls")] - public PermissionUrlsConfig? Urls { get; set; } } -/// Indicates whether the permission decision was applied; false when the request was already resolved. +/// Resolve, build, and validate the runtime tool list for this session. Subagent sessions and consumer flows that need an initialized tool set before `send` invoke this. Default base-class implementation is a no-op for sessions that don't support tool validation. [Experimental(Diagnostics.Experimental)] -public sealed class PermissionRequestResult +public sealed class ToolsInitializeAndValidateResult { - /// Whether the permission request was handled successfully. - [JsonPropertyName("success")] - public bool Success { get; set; } } -/// The client's response to the pending permission prompt. -/// Polymorphic base type discriminated by kind. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(PermissionDecisionApproveOnce), "approve-once")] -[JsonDerivedType(typeof(PermissionDecisionApproveForSession), "approve-for-session")] -[JsonDerivedType(typeof(PermissionDecisionApproveForLocation), "approve-for-location")] -[JsonDerivedType(typeof(PermissionDecisionApprovePermanently), "approve-permanently")] -[JsonDerivedType(typeof(PermissionDecisionReject), "reject")] -[JsonDerivedType(typeof(PermissionDecisionUserNotAvailable), "user-not-available")] -[JsonDerivedType(typeof(PermissionDecisionApproved), "approved")] -[JsonDerivedType(typeof(PermissionDecisionApprovedForSession), "approved-for-session")] -[JsonDerivedType(typeof(PermissionDecisionApprovedForLocation), "approved-for-location")] -[JsonDerivedType(typeof(PermissionDecisionCancelled), "cancelled")] -[JsonDerivedType(typeof(PermissionDecisionDeniedByRules), "denied-by-rules")] -[JsonDerivedType(typeof(PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser), "denied-no-approval-rule-and-could-not-request-from-user")] -[JsonDerivedType(typeof(PermissionDecisionDeniedInteractivelyByUser), "denied-interactively-by-user")] -[JsonDerivedType(typeof(PermissionDecisionDeniedByContentExclusionPolicy), "denied-by-content-exclusion-policy")] -[JsonDerivedType(typeof(PermissionDecisionDeniedByPermissionRequestHook), "denied-by-permission-request-hook")] -public partial class PermissionDecision +internal sealed class SessionToolsInitializeAndValidateRequest { - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } - -/// Permission-decision request variant to approve only the current permission request. -/// The approve-once variant of . +/// Lightweight metadata for a currently initialized session tool. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveOnce : PermissionDecision +public sealed class CurrentToolMetadata { - /// - [JsonIgnore] - public override string Kind => "approve-once"; -} + /// Whether the tool is loaded on demand via tool search. + [JsonPropertyName("deferLoading")] + public bool? DeferLoading { get; set; } -/// Session-scoped approval to remember (tool prompts only; omitted for path/url prompts). -/// Polymorphic base type discriminated by kind. -[Experimental(Diagnostics.Experimental)] -[JsonPolymorphic( - TypeDiscriminatorPropertyName = "kind", - UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(PermissionDecisionApproveForSessionApprovalCommands), "commands")] -[JsonDerivedType(typeof(PermissionDecisionApproveForSessionApprovalRead), "read")] -[JsonDerivedType(typeof(PermissionDecisionApproveForSessionApprovalWrite), "write")] -[JsonDerivedType(typeof(PermissionDecisionApproveForSessionApprovalMcp), "mcp")] -[JsonDerivedType(typeof(PermissionDecisionApproveForSessionApprovalMcpSampling), "mcp-sampling")] -[JsonDerivedType(typeof(PermissionDecisionApproveForSessionApprovalMemory), "memory")] -[JsonDerivedType(typeof(PermissionDecisionApproveForSessionApprovalCustomTool), "custom-tool")] -[JsonDerivedType(typeof(PermissionDecisionApproveForSessionApprovalExtensionManagement), "extension-management")] -[JsonDerivedType(typeof(PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess), "extension-permission-access")] -public partial class PermissionDecisionApproveForSessionApproval -{ - /// The type discriminator. - [JsonPropertyName("kind")] - public virtual string Kind { get; set; } = string.Empty; -} + /// Tool description. + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; + + /// JSON Schema for tool input. + [JsonPropertyName("input_schema")] + public IDictionary? InputSchema { get; set; } + /// MCP server name for MCP-backed tools. + [JsonPropertyName("mcpServerName")] + public string? McpServerName { get; set; } -/// Session-scoped approval details for specific command identifiers. -/// The commands variant of . -[Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForSessionApprovalCommands : PermissionDecisionApproveForSessionApproval -{ - /// - [JsonIgnore] - public override string Kind => "commands"; + /// Raw MCP tool name for MCP-backed tools. + [JsonPropertyName("mcpToolName")] + public string? McpToolName { get; set; } - /// Command identifiers covered by this approval. - [JsonPropertyName("commandIdentifiers")] - public required IList CommandIdentifiers { get; set; } + /// Model-facing tool name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Optional MCP/config namespaced tool name. + [JsonPropertyName("namespacedName")] + public string? NamespacedName { get; set; } } -/// Session-scoped approval details for read-only filesystem operations. -/// The read variant of . +/// Current lightweight tool metadata snapshot for the session. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForSessionApprovalRead : PermissionDecisionApproveForSessionApproval +public sealed class ToolsGetCurrentMetadataResult { - /// - [JsonIgnore] - public override string Kind => "read"; + /// Current tool metadata, or null when tools have not been initialized yet. + [JsonPropertyName("tools")] + public IList? Tools { get; set; } } -/// Session-scoped approval details for filesystem write operations. -/// The write variant of . +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForSessionApprovalWrite : PermissionDecisionApproveForSessionApproval +internal sealed class SessionToolsGetCurrentMetadataRequest { - /// - [JsonIgnore] - public override string Kind => "write"; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Session-scoped approval details for an MCP server tool, or all tools on the server when `toolName` is null. -/// The mcp variant of . +/// Empty result after applying subagent settings. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForSessionApprovalMcp : PermissionDecisionApproveForSessionApproval +public sealed class ToolsUpdateSubagentSettingsResult { - /// - [JsonIgnore] - public override string Kind => "mcp"; - - /// MCP server name. - [JsonPropertyName("serverName")] - public required string ServerName { get; set; } - - /// MCP tool name, or null to cover every tool on the server. - [JsonPropertyName("toolName")] - public string? ToolName { get; set; } } -/// Session-scoped approval details for MCP sampling requests from a server. -/// The mcp-sampling variant of . +/// Subagent model, reasoning effort, and context tier settings. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForSessionApprovalMcpSampling : PermissionDecisionApproveForSessionApproval +public sealed class SubagentSettingsEntry { - /// - [JsonIgnore] - public override string Kind => "mcp-sampling"; + /// Context tier override for matching subagents. + [JsonPropertyName("contextTier")] + public SubagentSettingsEntryContextTier? ContextTier { get; set; } - /// MCP server name. - [JsonPropertyName("serverName")] - public required string ServerName { get; set; } -} + /// Reasoning effort override for matching subagents. + [JsonPropertyName("effortLevel")] + public string? EffortLevel { get; set; } -/// Session-scoped approval details for writes to long-term memory. -/// The memory variant of . -[Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForSessionApprovalMemory : PermissionDecisionApproveForSessionApproval -{ - /// - [JsonIgnore] - public override string Kind => "memory"; + /// Model override for matching subagents. + [JsonPropertyName("model")] + public string? Model { get; set; } } -/// Session-scoped approval details for a custom tool, keyed by tool name. -/// The custom-tool variant of . -[Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForSessionApprovalCustomTool : PermissionDecisionApproveForSessionApproval +/// Configured per-agent subagent overrides. +public sealed class UpdateSubagentSettingsRequestSubagents { - /// - [JsonIgnore] - public override string Kind => "custom-tool"; + /// Per-agent settings keyed by subagent agent_type. + [JsonPropertyName("agents")] + public IDictionary? Agents { get; set; } - /// Custom tool name. - [JsonPropertyName("toolName")] - public required string ToolName { get; set; } + /// Names of subagents the user has turned off; they cannot be dispatched. + [JsonPropertyName("disabledSubagents")] + public IList? DisabledSubagents { get; set; } + + /// Maximum number of subagents that can run concurrently; applies to usage-based billing users only. + [JsonPropertyName("maxConcurrency")] + public int? MaxConcurrency { get; set; } + + /// Maximum subagent nesting depth; applies to usage-based billing users only. + [JsonPropertyName("maxDepth")] + public int? MaxDepth { get; set; } } -/// Session-scoped approval details for extension-management operations, optionally narrowed by operation. -/// The extension-management variant of . +/// Subagent settings to apply to the current session. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForSessionApprovalExtensionManagement : PermissionDecisionApproveForSessionApproval +internal sealed class UpdateSubagentSettingsRequest { - /// - [JsonIgnore] - public override string Kind => "extension-management"; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; - /// Optional operation identifier; when omitted, the approval covers all extension management operations. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("operation")] - public string? Operation { get; set; } + /// Subagent settings to apply, or null to clear the live session override. + [JsonPropertyName("subagents")] + public UpdateSubagentSettingsRequestSubagents? Subagents { get; set; } } -/// Session-scoped approval details for an extension's permission-gated capability access, keyed by extension name. -/// The extension-permission-access variant of . +/// RPC data type for SessionCommandsList operations. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess : PermissionDecisionApproveForSessionApproval +public sealed class SessionCommandsListRequest { - /// - [JsonIgnore] - public override string Kind => "extension-permission-access"; + /// Include runtime built-in commands. + [JsonPropertyName("includeBuiltins")] + public bool? IncludeBuiltins { get; set; } - /// Extension name. - [JsonPropertyName("extensionName")] - public required string ExtensionName { get; set; } + /// Include commands registered by protocol clients, including SDK clients and extensions. + [JsonPropertyName("includeClientCommands")] + public bool? IncludeClientCommands { get; set; } + + /// Include enabled user-invocable skills and commands. + [JsonPropertyName("includeSkills")] + public bool? IncludeSkills { get; set; } } -/// Permission-decision request variant to approve for the rest of the session, with optional tool approval or URL domain. -/// The approve-for-session variant of . +/// RPC data type for SessionCommandsListRequestWithSession operations. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForSession : PermissionDecision +internal sealed class SessionCommandsListRequestWithSession { - /// - [JsonIgnore] - public override string Kind => "approve-for-session"; + /// Include runtime built-in commands. + [JsonPropertyName("includeBuiltins")] + public bool? IncludeBuiltins { get; set; } - /// Session-scoped approval to remember (tool prompts only; omitted for path/url prompts). - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("approval")] - public PermissionDecisionApproveForSessionApproval? Approval { get; set; } + /// Include commands registered by protocol clients, including SDK clients and extensions. + [JsonPropertyName("includeClientCommands")] + public bool? IncludeClientCommands { get; set; } - /// URL domain to approve for the rest of the session (URL prompts only). - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("domain")] - public string? Domain { get; set; } + /// Include enabled user-invocable skills and commands. + [JsonPropertyName("includeSkills")] + public bool? IncludeSkills { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Approval to persist for this location. +/// Result of invoking the slash command (text output, prompt to send to the agent, completion, or subcommand selection). /// Polymorphic base type discriminated by kind. [Experimental(Diagnostics.Experimental)] [JsonPolymorphic( TypeDiscriminatorPropertyName = "kind", UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(PermissionDecisionApproveForLocationApprovalCommands), "commands")] -[JsonDerivedType(typeof(PermissionDecisionApproveForLocationApprovalRead), "read")] -[JsonDerivedType(typeof(PermissionDecisionApproveForLocationApprovalWrite), "write")] -[JsonDerivedType(typeof(PermissionDecisionApproveForLocationApprovalMcp), "mcp")] -[JsonDerivedType(typeof(PermissionDecisionApproveForLocationApprovalMcpSampling), "mcp-sampling")] -[JsonDerivedType(typeof(PermissionDecisionApproveForLocationApprovalMemory), "memory")] -[JsonDerivedType(typeof(PermissionDecisionApproveForLocationApprovalCustomTool), "custom-tool")] -[JsonDerivedType(typeof(PermissionDecisionApproveForLocationApprovalExtensionManagement), "extension-management")] -[JsonDerivedType(typeof(PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess), "extension-permission-access")] -public partial class PermissionDecisionApproveForLocationApproval +[JsonDerivedType(typeof(SlashCommandInvocationResultText), "text")] +[JsonDerivedType(typeof(SlashCommandInvocationResultAgentPrompt), "agent-prompt")] +[JsonDerivedType(typeof(SlashCommandInvocationResultCompleted), "completed")] +[JsonDerivedType(typeof(SlashCommandInvocationResultSelectSubcommand), "select-subcommand")] +public partial class SlashCommandInvocationResult { /// The type discriminator. [JsonPropertyName("kind")] @@ -9758,593 +9905,579 @@ public partial class PermissionDecisionApproveForLocationApproval } -/// Location-scoped approval details for specific command identifiers. -/// The commands variant of . +/// Slash-command invocation result containing text output plus Markdown/ANSI rendering flags. +/// The text variant of . [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForLocationApprovalCommands : PermissionDecisionApproveForLocationApproval +public partial class SlashCommandInvocationResultText : SlashCommandInvocationResult { /// [JsonIgnore] - public override string Kind => "commands"; + public override string Kind => "text"; - /// Command identifiers covered by this approval. - [JsonPropertyName("commandIdentifiers")] - public required IList CommandIdentifiers { get; set; } + /// Whether text contains Markdown. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("markdown")] + public bool? Markdown { get; set; } + + /// Whether ANSI sequences should be preserved. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("preserveAnsi")] + public bool? PreserveAnsi { get; set; } + + /// True when the invocation mutated user runtime settings; consumers caching settings should refresh. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("runtimeSettingsChanged")] + public bool? RuntimeSettingsChanged { get; set; } + + /// Text output for the client to render. + [JsonPropertyName("text")] + public required string Text { get; set; } } -/// Location-scoped approval details for read-only filesystem operations. -/// The read variant of . +/// Slash-command invocation result that submits an agent prompt, with display prompt, optional mode, optional user-facing notice, and settings-change flag. +/// The agent-prompt variant of . [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForLocationApprovalRead : PermissionDecisionApproveForLocationApproval +public partial class SlashCommandInvocationResultAgentPrompt : SlashCommandInvocationResult { /// [JsonIgnore] - public override string Kind => "read"; + public override string Kind => "agent-prompt"; + + /// Prompt text to display to the user. + [JsonPropertyName("displayPrompt")] + public required string DisplayPrompt { get; set; } + + /// Optional target session mode for the agent prompt. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("mode")] + public SessionMode? Mode { get; set; } + + /// Optional user-facing notice to show before the prompt is submitted. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("notice")] + public string? Notice { get; set; } + + /// Prompt to submit to the agent. + [JsonPropertyName("prompt")] + public required string Prompt { get; set; } + + /// True when the invocation mutated user runtime settings; consumers caching settings should refresh. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("runtimeSettingsChanged")] + public bool? RuntimeSettingsChanged { get; set; } } -/// Location-scoped approval details for filesystem write operations. -/// The write variant of . +/// Slash-command invocation result indicating completion, with optional message and settings-change flag. +/// The completed variant of . [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForLocationApprovalWrite : PermissionDecisionApproveForLocationApproval +public partial class SlashCommandInvocationResultCompleted : SlashCommandInvocationResult { /// [JsonIgnore] - public override string Kind => "write"; + public override string Kind => "completed"; + + /// Optional user-facing message describing the completed command. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("message")] + public string? Message { get; set; } + + /// True when the invocation mutated user runtime settings; consumers caching settings should refresh. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("runtimeSettingsChanged")] + public bool? RuntimeSettingsChanged { get; set; } } -/// Location-scoped approval details for an MCP server tool, or all tools on the server when `toolName` is null. -/// The mcp variant of . +/// Selectable slash-command subcommand option with name, description, and optional group label. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForLocationApprovalMcp : PermissionDecisionApproveForLocationApproval +public sealed class SlashCommandSelectSubcommandOption { - /// - [JsonIgnore] - public override string Kind => "mcp"; + /// Human-readable description of the subcommand. + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; - /// MCP server name. - [JsonPropertyName("serverName")] - public required string ServerName { get; set; } + /// Optional group label for organizing options. + [JsonPropertyName("group")] + public string? Group { get; set; } - /// MCP tool name, or null to cover every tool on the server. - [JsonPropertyName("toolName")] - public string? ToolName { get; set; } + /// Subcommand name to invoke. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; } -/// Location-scoped approval details for MCP sampling requests from a server. -/// The mcp-sampling variant of . +/// Slash-command invocation result asking the client to present subcommand options for a parent command. +/// The select-subcommand variant of . [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForLocationApprovalMcpSampling : PermissionDecisionApproveForLocationApproval +public partial class SlashCommandInvocationResultSelectSubcommand : SlashCommandInvocationResult { /// [JsonIgnore] - public override string Kind => "mcp-sampling"; + public override string Kind => "select-subcommand"; - /// MCP server name. - [JsonPropertyName("serverName")] - public required string ServerName { get; set; } + /// Parent command name that requires subcommand selection. + [JsonPropertyName("command")] + public required string Command { get; set; } + + /// Available subcommand options for the client to present. + [JsonPropertyName("options")] + public required IList Options { get; set; } + + /// True when the invocation mutated user runtime settings; consumers caching settings should refresh. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("runtimeSettingsChanged")] + public bool? RuntimeSettingsChanged { get; set; } + + /// Human-readable title for the selection UI. + [JsonPropertyName("title")] + public required string Title { get; set; } } -/// Location-scoped approval details for writes to long-term memory. -/// The memory variant of . +/// Slash command name and optional raw input string to invoke. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForLocationApprovalMemory : PermissionDecisionApproveForLocationApproval +internal sealed class CommandsInvokeRequest { - /// - [JsonIgnore] - public override string Kind => "memory"; + /// Raw input after the command name. + [JsonPropertyName("input")] + public string? Input { get; set; } + + /// Command name. Leading slashes are stripped and the name is matched case-insensitively. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Location-scoped approval details for a custom tool, keyed by tool name. -/// The custom-tool variant of . +/// Indicates whether the pending client-handled command was completed successfully. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForLocationApprovalCustomTool : PermissionDecisionApproveForLocationApproval +public sealed class CommandsHandlePendingCommandResult { - /// - [JsonIgnore] - public override string Kind => "custom-tool"; - - /// Custom tool name. - [JsonPropertyName("toolName")] - public required string ToolName { get; set; } + /// Whether the command was handled successfully. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// Location-scoped approval details for extension-management operations, optionally narrowed by operation. -/// The extension-management variant of . +/// Pending command request ID and an optional error if the client handler failed. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForLocationApprovalExtensionManagement : PermissionDecisionApproveForLocationApproval +internal sealed class CommandsHandlePendingCommandRequest { - /// - [JsonIgnore] - public override string Kind => "extension-management"; + /// Error message if the command handler failed. + [JsonPropertyName("error")] + public string? Error { get; set; } - /// Optional operation identifier; when omitted, the approval covers all extension management operations. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("operation")] - public string? Operation { get; set; } + /// Request ID from the command invocation event. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Location-scoped approval details for an extension's permission-gated capability access, keyed by extension name. -/// The extension-permission-access variant of . +/// Error message produced while executing the command, if any. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess : PermissionDecisionApproveForLocationApproval +public sealed class ExecuteCommandResult { - /// - [JsonIgnore] - public override string Kind => "extension-permission-access"; - - /// Extension name. - [JsonPropertyName("extensionName")] - public required string ExtensionName { get; set; } + /// Error message produced while executing the command, if any. Omitted when the handler succeeded. + [JsonPropertyName("error")] + public string? Error { get; set; } } -/// Permission-decision request variant to approve and persist a permission for a project location, with approval details and location key. -/// The approve-for-location variant of . +/// Slash command name and argument string to execute synchronously. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproveForLocation : PermissionDecision +internal sealed class ExecuteCommandParams { - /// - [JsonIgnore] - public override string Kind => "approve-for-location"; + /// Argument string to pass to the command (empty string if none). + [JsonPropertyName("args")] + public string Args { get; set; } = string.Empty; - /// Approval to persist for this location. - [JsonPropertyName("approval")] - public required PermissionDecisionApproveForLocationApproval Approval { get; set; } + /// Name of the slash command to invoke (without the leading '/'). + [JsonPropertyName("commandName")] + public string CommandName { get; set; } = string.Empty; - /// Location key (git root or cwd) to persist the approval to. - [JsonPropertyName("locationKey")] - public required string LocationKey { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Permission-decision request variant to permanently approve a URL domain across sessions. -/// The approve-permanently variant of . +/// Indicates whether the command was accepted into the local execution queue. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApprovePermanently : PermissionDecision +public sealed class EnqueueCommandResult { - /// - [JsonIgnore] - public override string Kind => "approve-permanently"; - - /// URL domain to approve permanently. - [JsonPropertyName("domain")] - public required string Domain { get; set; } + /// True when the command was accepted into the local execution queue. False when the call targets a session that does not support local command queueing (e.g. remote sessions). + [JsonPropertyName("queued")] + public bool Queued { get; set; } } -/// Permission-decision request variant to reject a pending permission request, with optional feedback. -/// The reject variant of . +/// Slash-prefixed command string to enqueue for FIFO processing. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionReject : PermissionDecision +internal sealed class EnqueueCommandParams { - /// - [JsonIgnore] - public override string Kind => "reject"; - - /// Optional feedback explaining the rejection. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("feedback")] - public string? Feedback { get; set; } -} + /// Slash-prefixed command string to enqueue, e.g. '/compact' or '/model gpt-4'. Queued FIFO with any in-flight items; if the session is idle, processing kicks off immediately. + [JsonPropertyName("command")] + public string Command { get; set; } = string.Empty; -/// Permission-decision variant indicating no user was available to confirm the request. -/// The user-not-available variant of . -[Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionUserNotAvailable : PermissionDecision -{ - /// - [JsonIgnore] - public override string Kind => "user-not-available"; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Permission-decision variant indicating the request was approved. -/// The approved variant of . +/// Indicates whether the queued-command response was matched to a pending request. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApproved : PermissionDecision +public sealed class CommandsRespondToQueuedCommandResult { - /// - [JsonIgnore] - public override string Kind => "approved"; + /// Whether a pending queued command with the given request ID was found and resolved. False when the request was already resolved, cancelled, or unknown. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// Permission-decision variant indicating approval was remembered for the session, with approval details. -/// The approved-for-session variant of . +/// Result of the queued command execution. +/// Data type discriminated by handled. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApprovedForSession : PermissionDecision +public partial class QueuedCommandResult { - /// - [JsonIgnore] - public override string Kind => "approved-for-session"; + /// The boolean discriminator. + [JsonPropertyName("handled")] + public bool Handled { get; set; } - /// The approval to add as a session-scoped rule. - [JsonPropertyName("approval")] - public required UserToolSessionApproval Approval { get; set; } + /// When true, the runtime will not process subsequent queued commands until a new request comes in. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("stopProcessingQueue")] + public bool? StopProcessingQueue { get; set; } } -/// Permission-decision variant indicating approval was persisted for a project location, with approval details and location key. -/// The approved-for-location variant of . +/// Queued-command request ID and the result indicating whether the host executed it (and whether to stop processing further queued commands). [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionApprovedForLocation : PermissionDecision +internal sealed class CommandsRespondToQueuedCommandRequest { - /// - [JsonIgnore] - public override string Kind => "approved-for-location"; + /// Request ID from the `command.queued` event the host is responding to. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; - /// The approval to persist for this location. - [JsonPropertyName("approval")] - public required UserToolSessionApproval Approval { get; set; } + /// Result of the queued command execution. + [JsonPropertyName("result")] + public QueuedCommandResult Result { get => field ??= new(); set; } - /// The location key (git root or cwd) to persist the approval to. - [JsonPropertyName("locationKey")] - public required string LocationKey { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Permission-decision variant indicating the request was cancelled before use, with an optional reason. -/// The cancelled variant of . +/// Telemetry engagement ID for the session, when available. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionCancelled : PermissionDecision +public sealed class SessionTelemetryEngagement { - /// - [JsonIgnore] - public override string Kind => "cancelled"; + /// Current telemetry engagement ID, when available. + [JsonPropertyName("engagementId")] + public string? EngagementId { get; set; } +} - /// Optional explanation of why the request was cancelled. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("reason")] - public string? Reason { get; set; } +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionTelemetryGetEngagementIdRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Permission-decision variant indicating explicit denial by permission rules, with the matching rules. -/// The denied-by-rules variant of . +/// Feature override key/value pairs to attach to subsequent telemetry events from this session. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionDeniedByRules : PermissionDecision +internal sealed class TelemetrySetFeatureOverridesRequest { - /// - [JsonIgnore] - public override string Kind => "denied-by-rules"; + /// Override key/value pairs to attach to subsequent telemetry events from this session. Replaces any previously-set overrides. + [JsonPropertyName("features")] + public IDictionary Features { get => field ??= new Dictionary(); set; } - /// Rules that denied the request. - [JsonPropertyName("rules")] - public required IList Rules { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Permission-decision variant indicating no approval rule matched and user confirmation was unavailable. -/// The denied-no-approval-rule-and-could-not-request-from-user variant of . +/// Transient answer generated from current conversation context. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser : PermissionDecision +public sealed class UIEphemeralQueryResult { - /// - [JsonIgnore] - public override string Kind => "denied-no-approval-rule-and-could-not-request-from-user"; + /// Full assistant response text. + [JsonPropertyName("answer")] + public string Answer { get; set; } = string.Empty; } -/// Permission-decision variant indicating the user denied an interactive prompt, with optional feedback and force-reject flag. -/// The denied-interactively-by-user variant of . +/// Transient question to answer without adding it to conversation history. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionDeniedInteractivelyByUser : PermissionDecision +internal sealed class UIEphemeralQueryRequest { - /// - [JsonIgnore] - public override string Kind => "denied-interactively-by-user"; - - /// Optional feedback from the user explaining the denial. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("feedback")] - public string? Feedback { get; set; } + /// Question to answer from the current conversation context. + [JsonPropertyName("question")] + public string Question { get; set; } = string.Empty; - /// Whether to force-reject the current agent turn. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("forceReject")] - public bool? ForceReject { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Permission-decision variant indicating denial by content-exclusion policy, with path and message. -/// The denied-by-content-exclusion-policy variant of . +/// The elicitation response (accept with form values, decline, or cancel). [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionDeniedByContentExclusionPolicy : PermissionDecision +public sealed class UIElicitationResponse { - /// - [JsonIgnore] - public override string Kind => "denied-by-content-exclusion-policy"; - - /// Human-readable explanation of why the path was excluded. - [JsonPropertyName("message")] - public required string Message { get; set; } + /// The user's response: accept (submitted), decline (rejected), or cancel (dismissed). + [JsonPropertyName("action")] + public UIElicitationResponseAction Action { get; set; } - /// File path that triggered the exclusion. - [JsonPropertyName("path")] - public required string Path { get; set; } + /// The form values submitted by the user (present when action is 'accept'). + [JsonPropertyName("content")] + public IDictionary? Content { get; set; } } -/// Permission-decision variant indicating denial by a permission request hook, with optional message and interrupt flag. -/// The denied-by-permission-request-hook variant of . +/// JSON Schema describing the form fields to present to the user. [Experimental(Diagnostics.Experimental)] -public partial class PermissionDecisionDeniedByPermissionRequestHook : PermissionDecision +public sealed class UIElicitationSchema { - /// - [JsonIgnore] - public override string Kind => "denied-by-permission-request-hook"; + /// Form field definitions, keyed by field name. + [JsonPropertyName("properties")] + public IDictionary Properties { get => field ??= new Dictionary(); set; } - /// Whether to interrupt the current agent turn. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("interrupt")] - public bool? Interrupt { get; set; } + /// List of required field names. + [JsonPropertyName("required")] + public IList? Required { get; set; } - /// Optional message from the hook explaining the denial. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("message")] - public string? Message { get; set; } + /// Schema type indicator (always 'object'). + [JsonPropertyName("type")] + public string Type { get; set; } = string.Empty; } -/// Pending permission request ID and the decision to apply (approve/reject and scope). +/// Prompt message and JSON schema describing the form fields to elicit from the user. [Experimental(Diagnostics.Experimental)] -internal sealed class PermissionDecisionRequest +internal sealed class UIElicitationRequest { - /// Request ID of the pending permission request. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; + /// Message describing what information is needed from the user. + [JsonPropertyName("message")] + public string Message { get; set; } = string.Empty; - /// The client's response to the pending permission prompt. - [JsonPropertyName("result")] - public PermissionDecision Result { get => field ??= new(); set; } + /// JSON Schema describing the form fields to present to the user. + [JsonPropertyName("requestedSchema")] + public UIElicitationSchema RequestedSchema { get => field ??= new(); set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Pending permission prompt reconstructed from event history, with request ID and user-facing prompt details. +/// Indicates whether the elicitation response was accepted; false if it was already resolved by another client. [Experimental(Diagnostics.Experimental)] -public sealed class PendingPermissionRequest +public sealed class UIElicitationResult { - /// The user-facing permission prompt details (commands, write, read, mcp, url, memory, custom-tool, path, hook). - [JsonPropertyName("request")] - public PermissionPromptRequest Request { get; set; } = null!; - - /// Unique identifier for the pending permission request. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; + /// Whether the response was accepted. False if the request was already resolved by another client. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// List of pending permission requests reconstructed from event history. +/// Pending elicitation request ID and the user's response (accept/decline/cancel + form values). [Experimental(Diagnostics.Experimental)] -public sealed class PendingPermissionRequestList +internal sealed class UIHandlePendingElicitationRequest { - /// Pending permission prompts reconstructed from the session's event history. Equivalent to the set of `permission.requested` events that have not yet been followed by a matching `permission.completed` event. Used by clients (e.g. the CLI) to hydrate UI for prompts that were emitted before the client attached to the session. - [JsonPropertyName("items")] - public IList Items { get => field ??= []; set; } -} + /// The unique request ID from the elicitation.requested event. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; + + /// The elicitation response (accept with form values, decline, or cancel). + [JsonPropertyName("result")] + public UIElicitationResponse Result { get => field ??= new(); set; } -/// No parameters; returns currently-pending permission requests for the session. -[Experimental(Diagnostics.Experimental)] -internal sealed class PermissionsPendingRequestsRequest -{ /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Indicates whether the operation succeeded. +/// Indicates whether the pending UI request was resolved by this call. [Experimental(Diagnostics.Experimental)] -public sealed class PermissionsSetApproveAllResult +public sealed class UIHandlePendingResult { - /// Whether the operation succeeded. + /// True if the request was still pending and was resolved by this call. False if the request ID was unknown, already resolved by another client (e.g. GitHub), expired, or otherwise no longer pending. [JsonPropertyName("success")] public bool Success { get; set; } } -/// Allow-all toggle for tool permission requests, with an optional telemetry source. +/// User response for a pending user-input request, with answer text and whether it was typed freeform. [Experimental(Diagnostics.Experimental)] -internal sealed class PermissionsSetApproveAllRequest +public sealed class UIUserInputResponse { - /// Whether to auto-approve all tool permission requests. - [JsonPropertyName("enabled")] - public bool Enabled { get; set; } + /// The user's answer text. + [JsonPropertyName("answer")] + public string Answer { get; set; } = string.Empty; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; - - /// Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers. - [JsonPropertyName("source")] - public PermissionsSetApproveAllSource? Source { get; set; } -} - -/// Indicates whether the operation succeeded and reports the post-mutation state. -[Experimental(Diagnostics.Experimental)] -public sealed class AllowAllPermissionSetResult -{ - /// Authoritative full allow-all state after the mutation. - [JsonPropertyName("enabled")] - public bool Enabled { get; set; } - - /// Authoritative allow-all mode after the mutation. - [JsonPropertyName("mode")] - public PermissionsAllowAllMode? Mode { get; set; } - - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// True if the user typed a freeform response, false if they selected a presented choice. Used by telemetry to differentiate between free text input and choice selection. + [JsonPropertyName("wasFreeform")] + public bool WasFreeform { get; set; } } -/// Allow-all mode to apply for the session. +/// Request ID of a pending `user_input.requested` event and the user's response. [Experimental(Diagnostics.Experimental)] -internal sealed class PermissionsSetAllowAllRequest +internal sealed class UIHandlePendingUserInputRequest { - /// Legacy full allow-all toggle. Prefer `mode`; when `mode` is omitted, `enabled: true` is treated as `mode: "on"` and any other value is treated as `mode: "off"`. - [JsonPropertyName("enabled")] - public bool? Enabled { get; set; } - - /// Allow-all mode to apply. `on` enables full allow-all; `auto` enables advisory LLM auto-approval; `off` disables both. - [JsonPropertyName("mode")] - public PermissionsAllowAllMode? Mode { get; set; } + /// The unique request ID from the user_input.requested event. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; - /// Optional model id for the `auto` mode auto-approval LLM judging. Only meaningful when `mode` is `auto`; ignored otherwise. When omitted, the session's active model is used. - [JsonPropertyName("model")] - public string? Model { get; set; } + /// User response for a pending user-input request, with answer text and whether it was typed freeform. + [JsonPropertyName("response")] + public UIUserInputResponse Response { get => field ??= new(); set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; - - /// Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers. - [JsonPropertyName("source")] - public PermissionsSetAllowAllSource? Source { get; set; } } -/// Current allow-all permission mode. +/// Optional sampling result payload. Omit to reject/cancel the sampling request without providing a result. [Experimental(Diagnostics.Experimental)] -public sealed class AllowAllPermissionState +public sealed class UIHandlePendingSamplingResponse { - /// Whether full allow-all permissions are currently active. - [JsonPropertyName("enabled")] - public bool Enabled { get; set; } - - /// Current allow-all mode. - [JsonPropertyName("mode")] - public PermissionsAllowAllMode? Mode { get; set; } } -/// No parameters. +/// Request ID of a pending `sampling.requested` event and an optional sampling result payload (omit to reject). [Experimental(Diagnostics.Experimental)] -internal sealed class PermissionsGetAllowAllRequest +internal sealed class UIHandlePendingSamplingRequest { + /// The unique request ID from the sampling.requested event. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; + + /// Optional sampling result payload. Omit to reject/cancel the sampling request without providing a result. + [JsonPropertyName("response")] + public UIHandlePendingSamplingResponse? Response { get; set; } + /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Indicates whether the operation succeeded. -[Experimental(Diagnostics.Experimental)] -public sealed class PermissionsModifyRulesResult -{ - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } -} - -/// Scope and add/remove instructions for modifying session- or location-scoped permission rules. +/// Request ID of a pending `auto_mode_switch.requested` event and the user's response. [Experimental(Diagnostics.Experimental)] -internal sealed class PermissionsModifyRulesParams +internal sealed class UIHandlePendingAutoModeSwitchRequest { - /// Rules to add to the scope. Applied before `remove`/`removeAll`. - [JsonPropertyName("add")] - public IList? Add { get; set; } - - /// Specific rules to remove from the scope. Ignored when `removeAll` is true. - [JsonPropertyName("remove")] - public IList? Remove { get; set; } - - /// When true, removes every rule currently in the scope (after any `add` is applied). Useful for clearing the location scope wholesale. - [JsonPropertyName("removeAll")] - public bool? RemoveAll { get; set; } + /// The unique request ID from the auto_mode_switch.requested event. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; - /// Whether the change applies to ephemeral session-scoped rules (cleared at session end) or to location-scoped rules persisted via the location-permissions config file. - [JsonPropertyName("scope")] - public PermissionsModifyRulesScope Scope { get; set; } + /// User's choice for auto-mode switching: yes (allow this turn), yes_always (allow + persist as setting), or no (decline). + [JsonPropertyName("response")] + public UIAutoModeSwitchResponse Response { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Indicates whether the operation succeeded. +/// The user's selected action for an exhausted session limit. [Experimental(Diagnostics.Experimental)] -public sealed class PermissionsSetRequiredResult +public sealed class UISessionLimitsExhaustedResponse { - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } -} + /// Action selected by the user. + [JsonPropertyName("action")] + public UISessionLimitsExhaustedResponseAction Action { get; set; } -/// Toggles whether permission prompts should be bridged into session events for this client. -[Experimental(Diagnostics.Experimental)] -internal sealed class PermissionsSetRequiredRequest -{ - /// Whether the client wants `permission.requested` events bridged from the session-owned permission service. CLI clients that render prompt UI set this to `true` for as long as their listener is mounted; headless callers leave it unset (the default is `false`). - [JsonPropertyName("required")] - public bool Required { get; set; } + /// AI Credits to add to the current max when action is 'add'. + [JsonPropertyName("additionalAiCredits")] + public double? AdditionalAiCredits { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// New absolute max AI Credits when action is 'set'. + [JsonPropertyName("maxAiCredits")] + public double? MaxAiCredits { get; set; } } -/// Indicates whether the operation succeeded. +/// Request ID of a pending `session_limits_exhausted.requested` event and the user's selected limit action. [Experimental(Diagnostics.Experimental)] -public sealed class PermissionsResetSessionApprovalsResult +internal sealed class UIHandlePendingSessionLimitsExhaustedRequest { - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } -} + /// The unique request ID from the session_limits_exhausted.requested event. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; + + /// The selected session-limit action. + [JsonPropertyName("response")] + public UISessionLimitsExhaustedResponse Response { get => field ??= new(); set; } -/// No parameters; clears all session-scoped tool permission approvals. -[Experimental(Diagnostics.Experimental)] -internal sealed class PermissionsResetSessionApprovalsRequest -{ /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Indicates whether the operation succeeded. +/// User response for a pending exit-plan-mode request, with approval state, selected action, auto-approve flag, and feedback. [Experimental(Diagnostics.Experimental)] -public sealed class PermissionsNotifyPromptShownResult +public sealed class UIExitPlanModeResponse { - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// Whether the plan was approved. + [JsonPropertyName("approved")] + public bool Approved { get; set; } + + /// Whether subsequent edits should be auto-approved without confirmation. + [JsonPropertyName("autoApproveEdits")] + public bool? AutoApproveEdits { get; set; } + + /// When true, the agent is instructed to end its turn without starting implementation so the client can restore the session model and auto-submit a fresh implementation turn on it. Set only when a distinct plan configuration (a different model, reasoning effort, or context tier) actually ran the planning turn. + [JsonPropertyName("deferImplementation")] + public bool? DeferImplementation { get; set; } + + /// Feedback from the user when they declined the plan or requested changes. + [JsonPropertyName("feedback")] + public string? Feedback { get; set; } + + /// The action the user selected. Defaults to 'autopilot' when autoApproveEdits is true, otherwise 'interactive'. + [JsonPropertyName("selectedAction")] + public UIExitPlanModeAction? SelectedAction { get; set; } } -/// Notification payload describing the permission prompt that the client just rendered. +/// Request ID of a pending `exit_plan_mode.requested` event and the user's response. [Experimental(Diagnostics.Experimental)] -internal sealed class PermissionPromptShownNotification +internal sealed class UIHandlePendingExitPlanModeRequest { - /// Human-readable description of the prompt the user is being asked to approve. Used by the runtime to fire the registered `permission_prompt` notification hook (e.g. terminal bell, desktop notification). - [JsonPropertyName("message")] - public string Message { get; set; } = string.Empty; + /// The unique request ID from the exit_plan_mode.requested event. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; + + /// User response for a pending exit-plan-mode request, with approval state, selected action, auto-approve flag, and feedback. + [JsonPropertyName("response")] + public UIExitPlanModeResponse Response { get => field ??= new(); set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Snapshot of the session's allow-listed directories and primary working directory. +/// Register an in-process handler for `auto_mode_switch.requested` events. The caller still attaches the actual listener via the standard event-subscription mechanism; this registration solely tells the server bridge to skip its own dispatch (so a remote client doesn't race the in-process handler for the same requestId). [Experimental(Diagnostics.Experimental)] -public sealed class PermissionPathsList +public sealed class UIRegisterDirectAutoModeSwitchHandlerResult { - /// All directories currently allowed for tool access on this session. - [JsonPropertyName("directories")] - public IList Directories { get => field ??= []; set; } - - /// The primary working directory for this session. - [JsonPropertyName("primary")] - public string Primary { get; set; } = string.Empty; + /// Opaque handle representing the registration. Pass this same handle to `unregisterDirectAutoModeSwitchHandler` when the in-process handler is no longer active. Multiple registrations are reference-counted; the server bridge will only dispatch auto-mode-switch requests when no handles are active. + [JsonPropertyName("handle")] + public string Handle { get; set; } = string.Empty; } -/// No parameters; returns the session's allow-listed directories. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class PermissionsPathsListRequest +internal sealed class SessionUiRegisterDirectAutoModeSwitchHandlerRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Indicates whether the operation succeeded. +/// Indicates whether the handle was active and the registration count was decremented. [Experimental(Diagnostics.Experimental)] -public sealed class PermissionsPathsAddResult +public sealed class UIUnregisterDirectAutoModeSwitchHandlerResult { - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// True if the handle was active and decremented the counter; false if the handle was unknown. + [JsonPropertyName("unregistered")] + public bool Unregistered { get; set; } } -/// Directory path to add to the session's allowed directories. +/// Opaque handle previously returned by `registerDirectAutoModeSwitchHandler` to release. [Experimental(Diagnostics.Experimental)] -internal sealed class PermissionPathsAddParams +internal sealed class UIUnregisterDirectAutoModeSwitchHandlerRequest { - /// Directory to add to the allow-list. The runtime resolves and validates the path before adding. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// Handle previously returned by `registerDirectAutoModeSwitchHandler`. + [JsonPropertyName("handle")] + public string Handle { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] @@ -10353,163 +10486,231 @@ internal sealed class PermissionPathsAddParams /// Indicates whether the operation succeeded. [Experimental(Diagnostics.Experimental)] -public sealed class PermissionsPathsUpdatePrimaryResult +public sealed class PermissionsConfigureResult { /// Whether the operation succeeded. [JsonPropertyName("success")] public bool Success { get; set; } } -/// Directory path to set as the session's new primary working directory. +/// Source descriptor for a `session.permissions.configure` content-exclusion rule, with source name and type. [Experimental(Diagnostics.Experimental)] -internal sealed class PermissionPathsUpdatePrimaryParams +public sealed class PermissionsConfigureAdditionalContentExclusionPolicyRuleSource { - /// Directory to set as the new primary working directory for the session's permission policy. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// Gets or sets the name value. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Gets or sets the type value. + [JsonPropertyName("type")] + public string Type { get; set; } = string.Empty; } -/// Indicates whether the supplied path is within the session's allowed directories. +/// Single content-exclusion rule supplied to `session.permissions.configure`, with paths, match conditions, and source. [Experimental(Diagnostics.Experimental)] -public sealed class PermissionPathsAllowedCheckResult +public sealed class PermissionsConfigureAdditionalContentExclusionPolicyRule { - /// Whether the path is within the session's allowed directories. - [JsonPropertyName("allowed")] - public bool Allowed { get; set; } -} + /// Gets or sets the ifAnyMatch value. + [JsonPropertyName("ifAnyMatch")] + public IList? IfAnyMatch { get; set; } -/// Path to evaluate against the session's allowed directories. + /// Gets or sets the ifNoneMatch value. + [JsonPropertyName("ifNoneMatch")] + public IList? IfNoneMatch { get; set; } + + /// Gets or sets the paths value. + [JsonPropertyName("paths")] + public IList Paths { get => field ??= []; set; } + + /// Source descriptor for a `session.permissions.configure` content-exclusion rule, with source name and type. + [JsonPropertyName("source")] + public PermissionsConfigureAdditionalContentExclusionPolicyRuleSource Source { get => field ??= new(); set; } +} + +/// Content-exclusion policy supplied to `session.permissions.configure`, with rules, last-updated data, and scope. [Experimental(Diagnostics.Experimental)] -internal sealed class PermissionPathsAllowedCheckParams +public sealed class PermissionsConfigureAdditionalContentExclusionPolicy { - /// Path to check against the session's allowed directories. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// Gets or sets the last_updated_at value. + [JsonPropertyName("last_updated_at")] + public JsonElement LastUpdatedAt { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Gets or sets the rules value. + [JsonPropertyName("rules")] + public IList Rules { get => field ??= []; set; } + + /// Allowed values for the `PermissionsConfigureAdditionalContentExclusionPolicyScope` enumeration. + [JsonPropertyName("scope")] + public PermissionsConfigureAdditionalContentExclusionPolicyScope Scope { get; set; } } -/// Indicates whether the supplied path is within the session's workspace directory. +/// If specified, replaces the session's path-permission policy. The runtime constructs the appropriate PathManager based on these inputs (rooted at the session's working directory). Omit to leave the current path policy unchanged. [Experimental(Diagnostics.Experimental)] -public sealed class PermissionPathsWorkspaceCheckResult +public sealed class PermissionPathsConfig { - /// Whether the path is within the session workspace directory. - [JsonPropertyName("allowed")] - public bool Allowed { get; set; } + /// Additional directories to allow tool access to (in addition to the session's working directory). When `unrestricted` is true, these are still pre-populated on the UnrestrictedPathManager so they remain visible via getDirectories() (e.g. for @-mention completion). + [JsonPropertyName("additionalDirectories")] + public IList? AdditionalDirectories { get; set; } + + /// Whether to include the system temp directory in the allowed list (defaults to true). Ignored when `unrestricted` is true. + [JsonPropertyName("includeTempDirectory")] + public bool? IncludeTempDirectory { get; set; } + + /// If true, the runtime allows access to all paths without prompting. Equivalent to constructing an UnrestrictedPathManager. + [JsonPropertyName("unrestricted")] + public bool? Unrestricted { get; set; } + + /// Workspace root path (special-cased to be allowed even before the directory exists). Ignored when `unrestricted` is true. + [JsonPropertyName("workspacePath")] + public string? WorkspacePath { get; set; } } -/// Path to evaluate against the session's workspace (primary) directory. +/// If specified, replaces the session's approved/denied permission rules. Omit to leave the current rules unchanged. [Experimental(Diagnostics.Experimental)] -internal sealed class PermissionPathsWorkspaceCheckParams +public sealed class PermissionRulesSet { - /// Path to check against the session workspace directory. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// Rules that auto-approve matching requests. + [JsonPropertyName("approved")] + public IList Approved { get => field ??= []; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Rules that auto-deny matching requests. + [JsonPropertyName("denied")] + public IList Denied { get => field ??= []; set; } } -/// Resolved location-permissions key and type. +/// If specified, replaces the session's URL-permission policy. The runtime constructs a fresh DefaultUrlManager based on these inputs. Omit to leave the current URL policy unchanged. [Experimental(Diagnostics.Experimental)] -public sealed class PermissionLocationResolveResult +public sealed class PermissionUrlsConfig { - /// Location key used in the location-permissions store. - [JsonPropertyName("locationKey")] - public string LocationKey { get; set; } = string.Empty; + /// Initial list of allowed URL/domain patterns. Patterns may include path components. Ignored when `unrestricted` is true. + [JsonPropertyName("initialAllowed")] + public IList? InitialAllowed { get; set; } - /// Whether the location is a git repo or directory. - [JsonPropertyName("locationType")] - public PermissionLocationType LocationType { get; set; } + /// If true, the runtime allows access to all URLs without prompting. Initial allow-list is ignored when this is true. + [JsonPropertyName("unrestricted")] + public bool? Unrestricted { get; set; } } -/// Working directory to resolve into a location-permissions key. +/// Patch of permission policy fields to apply (omit a field to leave it unchanged). [Experimental(Diagnostics.Experimental)] -internal sealed class PermissionLocationResolveParams +internal sealed class PermissionsConfigureParams { + /// If specified, replaces the host-supplied GitHub Content Exclusion policies on the session (combined with natively-discovered policies when evaluating tool/file access). Omit to leave the current policies unchanged. + [JsonPropertyName("additionalContentExclusionPolicies")] + public IList? AdditionalContentExclusionPolicies { get; set; } + + /// If specified, sets whether path/URL read permission requests are auto-approved. Omit to leave the current value unchanged. + [JsonPropertyName("approveAllReadPermissionRequests")] + public bool? ApproveAllReadPermissionRequests { get; set; } + + /// If specified, sets whether tool permission requests are auto-approved without prompting. Omit to leave the current value unchanged. + [JsonPropertyName("approveAllToolPermissionRequests")] + public bool? ApproveAllToolPermissionRequests { get; set; } + + /// If specified, replaces the session's path-permission policy. The runtime constructs the appropriate PathManager based on these inputs (rooted at the session's working directory). Omit to leave the current path policy unchanged. + [JsonPropertyName("paths")] + public PermissionPathsConfig? Paths { get; set; } + + /// If specified, replaces the session's approved/denied permission rules. Omit to leave the current rules unchanged. + [JsonPropertyName("rules")] + public PermissionRulesSet? Rules { get; set; } + /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; - /// Working directory whose permission location should be resolved. - [JsonPropertyName("workingDirectory")] - public string WorkingDirectory { get; set; } = string.Empty; + /// If specified, replaces the session's URL-permission policy. The runtime constructs a fresh DefaultUrlManager based on these inputs. Omit to leave the current URL policy unchanged. + [JsonPropertyName("urls")] + public PermissionUrlsConfig? Urls { get; set; } } -/// Summary of persisted location permissions applied to the session. +/// Indicates whether the permission decision was applied; false when the request was already resolved. [Experimental(Diagnostics.Experimental)] -public sealed class PermissionLocationApplyResult +public sealed class PermissionRequestResult { - /// Number of persisted allowed directories added to the live path manager. - [JsonPropertyName("appliedDirectoryCount")] - public long AppliedDirectoryCount { get; set; } - - /// Number of location-scoped rules added to the live permission service. - [JsonPropertyName("appliedRuleCount")] - public long AppliedRuleCount { get; set; } - - /// Location-scoped rules applied to the live permission service. - [JsonPropertyName("appliedRules")] - public IList AppliedRules { get => field ??= []; set; } + /// Whether the permission request was handled successfully. + [JsonPropertyName("success")] + public bool Success { get; set; } +} - /// Whether a different location was applied since the previous apply call. - [JsonPropertyName("changed")] - public bool Changed { get; set; } +/// Optional informational context describing how and where the permission decision was made. This does not affect permission behavior. +[Experimental(Diagnostics.Experimental)] +public sealed class PermissionDecisionContext +{ + /// Disposition of the permission request as observed by the responding client. + [JsonPropertyName("outcome")] + public PermissionDecisionOutcome Outcome { get; set; } - /// Location key used in the location-permissions store. - [JsonPropertyName("locationKey")] - public string LocationKey { get; set; } = string.Empty; + /// Controlled reason or actor responsible for the response. + [JsonPropertyName("source")] + public PermissionDecisionSource Source { get; set; } - /// Whether the location is a git repo or directory. - [JsonPropertyName("locationType")] - public PermissionLocationType LocationType { get; set; } + /// Client surface that submitted the response. + [JsonPropertyName("surface")] + public PermissionDecisionSurface Surface { get; set; } } -/// Working directory to load persisted location permissions for. +/// The client's response to the pending permission prompt. +/// Polymorphic base type discriminated by kind. [Experimental(Diagnostics.Experimental)] -internal sealed class PermissionLocationApplyParams +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(PermissionDecisionApproveOnce), "approve-once")] +[JsonDerivedType(typeof(PermissionDecisionApproveForSession), "approve-for-session")] +[JsonDerivedType(typeof(PermissionDecisionApproveForLocation), "approve-for-location")] +[JsonDerivedType(typeof(PermissionDecisionApprovePermanently), "approve-permanently")] +[JsonDerivedType(typeof(PermissionDecisionReject), "reject")] +[JsonDerivedType(typeof(PermissionDecisionUserNotAvailable), "user-not-available")] +[JsonDerivedType(typeof(PermissionDecisionApproved), "approved")] +[JsonDerivedType(typeof(PermissionDecisionApprovedForSession), "approved-for-session")] +[JsonDerivedType(typeof(PermissionDecisionApprovedForLocation), "approved-for-location")] +[JsonDerivedType(typeof(PermissionDecisionCancelled), "cancelled")] +[JsonDerivedType(typeof(PermissionDecisionDeniedByRules), "denied-by-rules")] +[JsonDerivedType(typeof(PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser), "denied-no-approval-rule-and-could-not-request-from-user")] +[JsonDerivedType(typeof(PermissionDecisionDeniedInteractivelyByUser), "denied-interactively-by-user")] +[JsonDerivedType(typeof(PermissionDecisionDeniedByContentExclusionPolicy), "denied-by-content-exclusion-policy")] +[JsonDerivedType(typeof(PermissionDecisionDeniedByPermissionRequestHook), "denied-by-permission-request-hook")] +public partial class PermissionDecision { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; - - /// Working directory whose persisted location permissions should be applied. - [JsonPropertyName("workingDirectory")] - public string WorkingDirectory { get; set; } = string.Empty; + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; } -/// Indicates whether the operation succeeded. + +/// Permission-decision request variant to approve only the current permission request. +/// The approve-once variant of . [Experimental(Diagnostics.Experimental)] -public sealed class PermissionsLocationsAddToolApprovalResult +public partial class PermissionDecisionApproveOnce : PermissionDecision { - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// + [JsonIgnore] + public override string Kind => "approve-once"; + + /// True only when a host surfaced this request to a user who approved it. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("approvedInteractively")] + public bool? ApprovedInteractively { get; set; } } -/// Tool approval to persist and apply. +/// Session-scoped approval to remember (tool prompts only; omitted for path/url prompts). /// Polymorphic base type discriminated by kind. [Experimental(Diagnostics.Experimental)] [JsonPolymorphic( TypeDiscriminatorPropertyName = "kind", UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(PermissionsLocationsAddToolApprovalDetailsCommands), "commands")] -[JsonDerivedType(typeof(PermissionsLocationsAddToolApprovalDetailsRead), "read")] -[JsonDerivedType(typeof(PermissionsLocationsAddToolApprovalDetailsWrite), "write")] -[JsonDerivedType(typeof(PermissionsLocationsAddToolApprovalDetailsMcp), "mcp")] -[JsonDerivedType(typeof(PermissionsLocationsAddToolApprovalDetailsMcpSampling), "mcp-sampling")] -[JsonDerivedType(typeof(PermissionsLocationsAddToolApprovalDetailsMemory), "memory")] -[JsonDerivedType(typeof(PermissionsLocationsAddToolApprovalDetailsCustomTool), "custom-tool")] -[JsonDerivedType(typeof(PermissionsLocationsAddToolApprovalDetailsExtensionManagement), "extension-management")] -[JsonDerivedType(typeof(PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess), "extension-permission-access")] -public partial class PermissionsLocationsAddToolApprovalDetails +[JsonDerivedType(typeof(PermissionDecisionApproveForSessionApprovalCommands), "commands")] +[JsonDerivedType(typeof(PermissionDecisionApproveForSessionApprovalRead), "read")] +[JsonDerivedType(typeof(PermissionDecisionApproveForSessionApprovalWrite), "write")] +[JsonDerivedType(typeof(PermissionDecisionApproveForSessionApprovalMcp), "mcp")] +[JsonDerivedType(typeof(PermissionDecisionApproveForSessionApprovalMcpSampling), "mcp-sampling")] +[JsonDerivedType(typeof(PermissionDecisionApproveForSessionApprovalMemory), "memory")] +[JsonDerivedType(typeof(PermissionDecisionApproveForSessionApprovalCustomTool), "custom-tool")] +[JsonDerivedType(typeof(PermissionDecisionApproveForSessionApprovalExtensionManagement), "extension-management")] +[JsonDerivedType(typeof(PermissionDecisionApproveForSessionApprovalFactory), "factory")] +[JsonDerivedType(typeof(PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess), "extension-permission-access")] +public partial class PermissionDecisionApproveForSessionApproval { /// The type discriminator. [JsonPropertyName("kind")] @@ -10517,10 +10718,10 @@ public partial class PermissionsLocationsAddToolApprovalDetails } -/// Location-persisted tool approval details for specific command identifiers. -/// The commands variant of . +/// Session-scoped approval details for specific command identifiers. +/// The commands variant of . [Experimental(Diagnostics.Experimental)] -public partial class PermissionsLocationsAddToolApprovalDetailsCommands : PermissionsLocationsAddToolApprovalDetails +public partial class PermissionDecisionApproveForSessionApprovalCommands : PermissionDecisionApproveForSessionApproval { /// [JsonIgnore] @@ -10531,30 +10732,30 @@ public partial class PermissionsLocationsAddToolApprovalDetailsCommands : Permis public required IList CommandIdentifiers { get; set; } } -/// Location-persisted tool approval details for read-only filesystem operations. -/// The read variant of . +/// Session-scoped approval details for read-only filesystem operations. +/// The read variant of . [Experimental(Diagnostics.Experimental)] -public partial class PermissionsLocationsAddToolApprovalDetailsRead : PermissionsLocationsAddToolApprovalDetails +public partial class PermissionDecisionApproveForSessionApprovalRead : PermissionDecisionApproveForSessionApproval { /// [JsonIgnore] public override string Kind => "read"; } -/// Location-persisted tool approval details for filesystem write operations. -/// The write variant of . +/// Session-scoped approval details for filesystem write operations. +/// The write variant of . [Experimental(Diagnostics.Experimental)] -public partial class PermissionsLocationsAddToolApprovalDetailsWrite : PermissionsLocationsAddToolApprovalDetails +public partial class PermissionDecisionApproveForSessionApprovalWrite : PermissionDecisionApproveForSessionApproval { /// [JsonIgnore] public override string Kind => "write"; } -/// Location-persisted tool approval details for an MCP server tool, or all tools when `toolName` is null. -/// The mcp variant of . +/// Session-scoped approval details for an MCP server tool, or all tools on the server when `toolName` is null. +/// The mcp variant of . [Experimental(Diagnostics.Experimental)] -public partial class PermissionsLocationsAddToolApprovalDetailsMcp : PermissionsLocationsAddToolApprovalDetails +public partial class PermissionDecisionApproveForSessionApprovalMcp : PermissionDecisionApproveForSessionApproval { /// [JsonIgnore] @@ -10569,10 +10770,10 @@ public partial class PermissionsLocationsAddToolApprovalDetailsMcp : Permissions public string? ToolName { get; set; } } -/// Location-persisted tool approval details for MCP sampling requests from a server. -/// The mcp-sampling variant of . +/// Session-scoped approval details for MCP sampling requests from a server. +/// The mcp-sampling variant of . [Experimental(Diagnostics.Experimental)] -public partial class PermissionsLocationsAddToolApprovalDetailsMcpSampling : PermissionsLocationsAddToolApprovalDetails +public partial class PermissionDecisionApproveForSessionApprovalMcpSampling : PermissionDecisionApproveForSessionApproval { /// [JsonIgnore] @@ -10583,20 +10784,20 @@ public partial class PermissionsLocationsAddToolApprovalDetailsMcpSampling : Per public required string ServerName { get; set; } } -/// Location-persisted tool approval details for writes to long-term memory. -/// The memory variant of . +/// Session-scoped approval details for writes to long-term memory. +/// The memory variant of . [Experimental(Diagnostics.Experimental)] -public partial class PermissionsLocationsAddToolApprovalDetailsMemory : PermissionsLocationsAddToolApprovalDetails +public partial class PermissionDecisionApproveForSessionApprovalMemory : PermissionDecisionApproveForSessionApproval { /// [JsonIgnore] public override string Kind => "memory"; } -/// Location-persisted tool approval details for a custom tool, keyed by tool name. -/// The custom-tool variant of . +/// Session-scoped approval details for a custom tool, keyed by tool name. +/// The custom-tool variant of . [Experimental(Diagnostics.Experimental)] -public partial class PermissionsLocationsAddToolApprovalDetailsCustomTool : PermissionsLocationsAddToolApprovalDetails +public partial class PermissionDecisionApproveForSessionApprovalCustomTool : PermissionDecisionApproveForSessionApproval { /// [JsonIgnore] @@ -10607,10 +10808,10 @@ public partial class PermissionsLocationsAddToolApprovalDetailsCustomTool : Perm public required string ToolName { get; set; } } -/// Location-persisted tool approval details for extension-management operations, optionally narrowed by operation. -/// The extension-management variant of . +/// Session-scoped approval details for extension-management operations, optionally narrowed by operation. +/// The extension-management variant of . [Experimental(Diagnostics.Experimental)] -public partial class PermissionsLocationsAddToolApprovalDetailsExtensionManagement : PermissionsLocationsAddToolApprovalDetails +public partial class PermissionDecisionApproveForSessionApprovalExtensionManagement : PermissionDecisionApproveForSessionApproval { /// [JsonIgnore] @@ -10622,10 +10823,25 @@ public partial class PermissionsLocationsAddToolApprovalDetailsExtensionManageme public string? Operation { get; set; } } -/// Location-persisted tool approval details for an extension's permission-gated capability access, keyed by extension name. -/// The extension-permission-access variant of . +/// Session-scoped factory approval, optionally narrowed by approval key. +/// The factory variant of . [Experimental(Diagnostics.Experimental)] -public partial class PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess : PermissionsLocationsAddToolApprovalDetails +public partial class PermissionDecisionApproveForSessionApprovalFactory : PermissionDecisionApproveForSessionApproval +{ + /// + [JsonIgnore] + public override string Kind => "factory"; + + /// Optional factory operation name or canonical approval key; when omitted, the approval covers all factory operations. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("approvalKey")] + public string? ApprovalKey { get; set; } +} + +/// Session-scoped approval details for an extension's permission-gated capability access, keyed by extension name. +/// The extension-permission-access variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess : PermissionDecisionApproveForSessionApproval { /// [JsonIgnore] @@ -10636,2412 +10852,5893 @@ public partial class PermissionsLocationsAddToolApprovalDetailsExtensionPermissi public required string ExtensionName { get; set; } } -/// Location-scoped tool approval to persist. +/// Permission-decision request variant to approve for the rest of the session, with optional tool approval or URL domain. +/// The approve-for-session variant of . [Experimental(Diagnostics.Experimental)] -internal sealed class PermissionLocationAddToolApprovalParams +public partial class PermissionDecisionApproveForSession : PermissionDecision { - /// Tool approval to persist and apply. - [JsonPropertyName("approval")] - public PermissionsLocationsAddToolApprovalDetails Approval { get => field ??= new(); set; } + /// + [JsonIgnore] + public override string Kind => "approve-for-session"; - /// Location key (git root or cwd) to persist the approval to. - [JsonPropertyName("locationKey")] - public string LocationKey { get; set; } = string.Empty; + /// Session-scoped approval to remember (tool prompts only; omitted for path/url prompts). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("approval")] + public PermissionDecisionApproveForSessionApproval? Approval { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// URL domain to approve for the rest of the session (URL prompts only). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("domain")] + public string? Domain { get; set; } } -/// Folder trust check result. +/// Approval to persist for this location. +/// Polymorphic base type discriminated by kind. [Experimental(Diagnostics.Experimental)] -public sealed class FolderTrustCheckResult +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(PermissionDecisionApproveForLocationApprovalCommands), "commands")] +[JsonDerivedType(typeof(PermissionDecisionApproveForLocationApprovalRead), "read")] +[JsonDerivedType(typeof(PermissionDecisionApproveForLocationApprovalWrite), "write")] +[JsonDerivedType(typeof(PermissionDecisionApproveForLocationApprovalMcp), "mcp")] +[JsonDerivedType(typeof(PermissionDecisionApproveForLocationApprovalMcpSampling), "mcp-sampling")] +[JsonDerivedType(typeof(PermissionDecisionApproveForLocationApprovalMemory), "memory")] +[JsonDerivedType(typeof(PermissionDecisionApproveForLocationApprovalCustomTool), "custom-tool")] +[JsonDerivedType(typeof(PermissionDecisionApproveForLocationApprovalExtensionManagement), "extension-management")] +[JsonDerivedType(typeof(PermissionDecisionApproveForLocationApprovalFactory), "factory")] +[JsonDerivedType(typeof(PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess), "extension-permission-access")] +public partial class PermissionDecisionApproveForLocationApproval { - /// Whether the folder is trusted. - [JsonPropertyName("trusted")] - public bool Trusted { get; set; } + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; } -/// Folder path to check for trust. + +/// Location-scoped approval details for specific command identifiers. +/// The commands variant of . [Experimental(Diagnostics.Experimental)] -internal sealed class FolderTrustCheckParams +public partial class PermissionDecisionApproveForLocationApprovalCommands : PermissionDecisionApproveForLocationApproval { - /// Folder path to check. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "commands"; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Command identifiers covered by this approval. + [JsonPropertyName("commandIdentifiers")] + public required IList CommandIdentifiers { get; set; } } -/// Indicates whether the operation succeeded. +/// Location-scoped approval details for read-only filesystem operations. +/// The read variant of . [Experimental(Diagnostics.Experimental)] -public sealed class PermissionsFolderTrustAddTrustedResult +public partial class PermissionDecisionApproveForLocationApprovalRead : PermissionDecisionApproveForLocationApproval { - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// + [JsonIgnore] + public override string Kind => "read"; } -/// Folder path to add to trusted folders. +/// Location-scoped approval details for filesystem write operations. +/// The write variant of . [Experimental(Diagnostics.Experimental)] -internal sealed class FolderTrustAddParams +public partial class PermissionDecisionApproveForLocationApprovalWrite : PermissionDecisionApproveForLocationApproval { - /// Folder path to mark as trusted. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; - - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "write"; } -/// Indicates whether the operation succeeded. +/// Location-scoped approval details for an MCP server tool, or all tools on the server when `toolName` is null. +/// The mcp variant of . [Experimental(Diagnostics.Experimental)] -public sealed class PermissionsUrlsSetUnrestrictedModeResult +public partial class PermissionDecisionApproveForLocationApprovalMcp : PermissionDecisionApproveForLocationApproval { - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// + [JsonIgnore] + public override string Kind => "mcp"; + + /// MCP server name. + [JsonPropertyName("serverName")] + public required string ServerName { get; set; } + + /// MCP tool name, or null to cover every tool on the server. + [JsonPropertyName("toolName")] + public string? ToolName { get; set; } } -/// Whether the URL-permission policy should run in unrestricted mode. +/// Location-scoped approval details for MCP sampling requests from a server. +/// The mcp-sampling variant of . [Experimental(Diagnostics.Experimental)] -internal sealed class PermissionUrlsSetUnrestrictedModeParams +public partial class PermissionDecisionApproveForLocationApprovalMcpSampling : PermissionDecisionApproveForLocationApproval { - /// Whether to allow access to all URLs without prompting. Toggles the runtime's URL-permission policy in place. - [JsonPropertyName("enabled")] - public bool Enabled { get; set; } + /// + [JsonIgnore] + public override string Kind => "mcp-sampling"; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// MCP server name. + [JsonPropertyName("serverName")] + public required string ServerName { get; set; } } -/// The repository the remote session targets. +/// Location-scoped approval details for writes to long-term memory. +/// The memory variant of . [Experimental(Diagnostics.Experimental)] -public sealed class MetadataSnapshotRemoteMetadataRepository +public partial class PermissionDecisionApproveForLocationApprovalMemory : PermissionDecisionApproveForLocationApproval { - /// The branch the remote session is operating on. - [JsonPropertyName("branch")] - public string Branch { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "memory"; +} - /// The GitHub repository name (without owner). - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; +/// Location-scoped approval details for a custom tool, keyed by tool name. +/// The custom-tool variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionDecisionApproveForLocationApprovalCustomTool : PermissionDecisionApproveForLocationApproval +{ + /// + [JsonIgnore] + public override string Kind => "custom-tool"; - /// The GitHub owner (user or organization) of the target repository. - [JsonPropertyName("owner")] - public string Owner { get; set; } = string.Empty; + /// Custom tool name. + [JsonPropertyName("toolName")] + public required string ToolName { get; set; } } -/// Remote-session-specific metadata. Populated only when `isRemote` is true. Fields are immutable for the lifetime of the session. +/// Location-scoped approval details for extension-management operations, optionally narrowed by operation. +/// The extension-management variant of . [Experimental(Diagnostics.Experimental)] -public sealed class MetadataSnapshotRemoteMetadata +public partial class PermissionDecisionApproveForLocationApprovalExtensionManagement : PermissionDecisionApproveForLocationApproval { - /// The pull request number the remote session is associated with, if any. - [JsonPropertyName("pullRequestNumber")] - public long? PullRequestNumber { get; set; } + /// + [JsonIgnore] + public override string Kind => "extension-management"; - /// The repository the remote session targets. - [JsonPropertyName("repository")] - public MetadataSnapshotRemoteMetadataRepository Repository { get => field ??= new(); set; } + /// Optional operation identifier; when omitted, the approval covers all extension management operations. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("operation")] + public string? Operation { get; set; } +} - /// The original resource identifier (task ID or PR node ID), preserved across event-replay reconstructions. Falls back to `sessionId` when absent. - [JsonPropertyName("resourceId")] - public string? ResourceId { get; set; } +/// Location-scoped factory approval, optionally narrowed by approval key. +/// The factory variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionDecisionApproveForLocationApprovalFactory : PermissionDecisionApproveForLocationApproval +{ + /// + [JsonIgnore] + public override string Kind => "factory"; - /// Whether the remote task originated from Copilot Coding Agent (cca) or a CLI `--remote` invocation. - [JsonPropertyName("taskType")] - public MetadataSnapshotRemoteMetadataTaskType? TaskType { get; set; } + /// Optional factory operation name or canonical approval key; when omitted, the approval covers all factory operations. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("approvalKey")] + public string? ApprovalKey { get; set; } } -/// Public-facing projection of workspace metadata for SDK / TUI consumers. -public sealed class SessionMetadataSnapshotWorkspace +/// Location-scoped approval details for an extension's permission-gated capability access, keyed by extension name. +/// The extension-permission-access variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess : PermissionDecisionApproveForLocationApproval { - /// Branch checked out at session start, if any. - [JsonPropertyName("branch")] - public string? Branch { get; set; } + /// + [JsonIgnore] + public override string Kind => "extension-permission-access"; - /// ISO 8601 timestamp when the workspace was created. - [JsonPropertyName("created_at")] - public DateTimeOffset? CreatedAt { get; set; } + /// Extension name. + [JsonPropertyName("extensionName")] + public required string ExtensionName { get; set; } +} - /// Current working directory at session start. - [JsonPropertyName("cwd")] - public string? Cwd { get; set; } +/// Permission-decision request variant to approve and persist a permission for a project location, with approval details and location key. +/// The approve-for-location variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionDecisionApproveForLocation : PermissionDecision +{ + /// + [JsonIgnore] + public override string Kind => "approve-for-location"; - /// Resolved git root for cwd, if any. - [JsonPropertyName("git_root")] - public string? GitRoot { get; set; } + /// Approval to persist for this location. + [JsonPropertyName("approval")] + public required PermissionDecisionApproveForLocationApproval Approval { get; set; } - /// Repository host type, if known. - [JsonPropertyName("host_type")] - public WorkspaceSummaryHostType? HostType { get; set; } + /// Location key (git root or cwd) to persist the approval to. + [JsonPropertyName("locationKey")] + public required string LocationKey { get; set; } +} - /// Workspace identifier (1:1 with sessionId). - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] - [MinLength(1)] - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; - - /// Display name for the session, if set. - [JsonPropertyName("name")] - public string? Name { get; set; } - - /// Repository identifier in 'owner/repo' or 'org/project/repo' format, if any. - [JsonPropertyName("repository")] - public string? Repository { get; set; } - - /// ISO 8601 timestamp when the workspace was last updated. - [JsonPropertyName("updated_at")] - public DateTimeOffset? UpdatedAt { get; set; } +/// Permission-decision request variant to permanently approve a URL domain across sessions. +/// The approve-permanently variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionDecisionApprovePermanently : PermissionDecision +{ + /// + [JsonIgnore] + public override string Kind => "approve-permanently"; - /// Whether the display name was explicitly set by the user. - [JsonPropertyName("user_named")] - public bool? UserNamed { get; set; } + /// URL domain to approve permanently. + [JsonPropertyName("domain")] + public required string Domain { get; set; } } -/// Point-in-time snapshot of slow-changing session identifier and state fields. +/// Permission-decision request variant to reject a pending permission request, with optional feedback. +/// The reject variant of . [Experimental(Diagnostics.Experimental)] -public sealed class SessionMetadataSnapshot +public partial class PermissionDecisionReject : PermissionDecision { - /// True when the session was detected to be in use by another process at construction time. Local consumers may surface a confirmation prompt before fully attaching. Always false for new sessions. - [JsonPropertyName("alreadyInUse")] - public bool AlreadyInUse { get; set; } - - /// Runtime client name associated with the session (telemetry identifier). - [JsonPropertyName("clientName")] - public string? ClientName { get; set; } - - /// The current agent mode for this session (e.g., 'interactive', 'plan', 'autopilot'). - [JsonPropertyName("currentMode")] - public MetadataSnapshotCurrentMode CurrentMode { get; set; } + /// + [JsonIgnore] + public override string Kind => "reject"; - /// User-provided name supplied at session construction (via `--name`), if any. Immutable after construction. - [JsonPropertyName("initialName")] - public string? InitialName { get; set; } + /// Optional feedback explaining the rejection. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("feedback")] + public string? Feedback { get; set; } +} - /// Whether this is a remote session (i.e., one whose runtime executes elsewhere and is steered through this process). - [JsonPropertyName("isRemote")] - public bool IsRemote { get; set; } +/// Permission-decision variant indicating no user was available to confirm the request. +/// The user-not-available variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionDecisionUserNotAvailable : PermissionDecision +{ + /// + [JsonIgnore] + public override string Kind => "user-not-available"; +} - /// ISO 8601 timestamp of when the session's persisted state was last modified on disk. For new sessions, equals startTime. For resumed sessions, reflects the previous modification time at construction. - [JsonPropertyName("modifiedTime")] - public DateTimeOffset ModifiedTime { get; set; } +/// Permission-decision variant indicating the request was approved. +/// The approved variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionDecisionApproved : PermissionDecision +{ + /// + [JsonIgnore] + public override string Kind => "approved"; +} - /// Remote-session-specific metadata. Populated only when `isRemote` is true. Fields are immutable for the lifetime of the session. - [JsonPropertyName("remoteMetadata")] - public MetadataSnapshotRemoteMetadata? RemoteMetadata { get; set; } +/// Permission-decision variant indicating approval was remembered for the session, with approval details. +/// The approved-for-session variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionDecisionApprovedForSession : PermissionDecision +{ + /// + [JsonIgnore] + public override string Kind => "approved-for-session"; - /// Currently selected model identifier, if any. - [JsonPropertyName("selectedModel")] - public string? SelectedModel { get; set; } + /// The approval to add as a session-scoped rule. + [JsonPropertyName("approval")] + public required UserToolSessionApproval Approval { get; set; } +} - /// The unique identifier of the session. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; +/// Permission-decision variant indicating approval was persisted for a project location, with approval details and location key. +/// The approved-for-location variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionDecisionApprovedForLocation : PermissionDecision +{ + /// + [JsonIgnore] + public override string Kind => "approved-for-location"; - /// Current session limits, or null when no limits are active. - [JsonPropertyName("sessionLimits")] - public SessionLimitsConfig? SessionLimits { get; set; } + /// The approval to persist for this location. + [JsonPropertyName("approval")] + public required UserToolSessionApproval Approval { get; set; } - /// ISO 8601 timestamp of when the session started. - [JsonPropertyName("startTime")] - public DateTimeOffset StartTime { get; set; } + /// The location key (git root or cwd) to persist the approval to. + [JsonPropertyName("locationKey")] + public required string LocationKey { get; set; } +} - /// Short human-readable summary of the session, if known. Omitted when no summary has been generated. - [JsonPropertyName("summary")] - public string? Summary { get; set; } +/// Permission-decision variant indicating the request was cancelled before use, with an optional reason. +/// The cancelled variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionDecisionCancelled : PermissionDecision +{ + /// + [JsonIgnore] + public override string Kind => "cancelled"; - /// Absolute path to the session's current working directory. - [JsonPropertyName("workingDirectory")] - public string WorkingDirectory { get; set; } = string.Empty; + /// Optional explanation of why the request was cancelled. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("reason")] + public string? Reason { get; set; } +} - /// Public-facing workspace metadata for this session, or null if the session has no associated workspace. Excludes runtime-internal fields (GitHub IDs, summary count, internal flags). - [JsonPropertyName("workspace")] - public SessionMetadataSnapshotWorkspace? Workspace { get; set; } +/// Permission-decision variant indicating explicit denial by permission rules, with the matching rules. +/// The denied-by-rules variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionDecisionDeniedByRules : PermissionDecision +{ + /// + [JsonIgnore] + public override string Kind => "denied-by-rules"; - /// Absolute path to the session's workspace directory on disk, or null if the session has no associated workspace. - [JsonPropertyName("workspacePath")] - public string? WorkspacePath { get; set; } + /// Rules that denied the request. + [JsonPropertyName("rules")] + public required IList Rules { get; set; } } -/// Identifies the target session. +/// Permission-decision variant indicating no approval rule matched and user confirmation was unavailable. +/// The denied-no-approval-rule-and-could-not-request-from-user variant of . [Experimental(Diagnostics.Experimental)] -internal sealed class SessionMetadataSnapshotRequest +public partial class PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser : PermissionDecision { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "denied-no-approval-rule-and-could-not-request-from-user"; } -/// Indicates whether the local session is currently processing a turn or background continuation. +/// Permission-decision variant indicating the user denied an interactive prompt, with optional feedback and force-reject flag. +/// The denied-interactively-by-user variant of . [Experimental(Diagnostics.Experimental)] -public sealed class MetadataIsProcessingResult +public partial class PermissionDecisionDeniedInteractivelyByUser : PermissionDecision { - /// Whether the session is currently processing user/agent messages. False for non-local sessions (which don't run a local agentic loop). Reflects an in-flight turn or background continuation. - [JsonPropertyName("processing")] - public bool Processing { get; set; } + /// + [JsonIgnore] + public override string Kind => "denied-interactively-by-user"; + + /// Optional feedback from the user explaining the denial. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("feedback")] + public string? Feedback { get; set; } + + /// Whether to force-reject the current agent turn. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("forceReject")] + public bool? ForceReject { get; set; } } -/// Identifies the target session. +/// Permission-decision variant indicating denial by content-exclusion policy, with path and message. +/// The denied-by-content-exclusion-policy variant of . [Experimental(Diagnostics.Experimental)] -internal sealed class SessionMetadataIsProcessingRequest +public partial class PermissionDecisionDeniedByContentExclusionPolicy : PermissionDecision { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "denied-by-content-exclusion-policy"; + + /// Human-readable explanation of why the path was excluded. + [JsonPropertyName("message")] + public required string Message { get; set; } + + /// File path that triggered the exclusion. + [JsonPropertyName("path")] + public required string Path { get; set; } } -/// Current activity flags for the session. +/// Permission-decision variant indicating denial by a permission request hook, with optional message and interrupt flag. +/// The denied-by-permission-request-hook variant of . [Experimental(Diagnostics.Experimental)] -public sealed class SessionActivity +public partial class PermissionDecisionDeniedByPermissionRequestHook : PermissionDecision { - /// Whether an in-flight operation can currently be aborted. - [JsonPropertyName("abortable")] - public bool Abortable { get; set; } + /// + [JsonIgnore] + public override string Kind => "denied-by-permission-request-hook"; - /// Whether the session currently has active work, including running turns or tasks. - [JsonPropertyName("hasActiveWork")] - public bool HasActiveWork { get; set; } + /// Whether to interrupt the current agent turn. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("interrupt")] + public bool? Interrupt { get; set; } + + /// Optional message from the hook explaining the denial. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("message")] + public string? Message { get; set; } } -/// Identifies the target session. +/// Pending permission request ID and the decision to apply (approve/reject and scope). [Experimental(Diagnostics.Experimental)] -internal sealed class SessionMetadataActivityRequest +internal sealed class PermissionDecisionRequest { + /// Optional informational context describing how and where this response was made. Omit it to preserve legacy behavior without attributing an origin. + [JsonPropertyName("decisionContext")] + public PermissionDecisionContext? DecisionContext { get; set; } + + /// Request ID of the pending permission request. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; + + /// The client's response to the pending permission prompt. + [JsonPropertyName("result")] + public PermissionDecision Result { get => field ??= new(); set; } + /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Token-usage breakdown for the session's current context window. -public sealed class MetadataContextInfoResultContextInfo +/// Pending permission prompt reconstructed from event history, with request ID and user-facing prompt details. +[Experimental(Diagnostics.Experimental)] +public sealed class PendingPermissionRequest { - /// Output reserve plus tokens after the buffer-exhaustion blocking threshold (default 95%). - [JsonPropertyName("bufferTokens")] - public long BufferTokens { get; set; } + /// The user-facing permission prompt details (commands, write, read, mcp, url, memory, custom-tool, path, hook). + [JsonPropertyName("request")] + public PermissionPromptRequest Request { get; set; } = null!; - /// Token count at which background compaction starts (configurable percentage of promptTokenLimit). - [JsonPropertyName("compactionThreshold")] - public long CompactionThreshold { get; set; } - - /// Tokens consumed by user/assistant/tool messages. - [JsonPropertyName("conversationTokens")] - public long ConversationTokens { get; set; } - - /// Prompt token limit plus the model's full output token limit. - [JsonPropertyName("limit")] - public long Limit { get; set; } - - /// Tokens consumed by MCP tool definitions (subset of toolDefinitionsTokens, excludes deferred tools). - [JsonPropertyName("mcpToolsTokens")] - public long McpToolsTokens { get; set; } - - /// The model used for token counting. - [JsonPropertyName("modelName")] - public string ModelName { get; set; } = string.Empty; - - /// Maximum prompt tokens allowed by the model (or DEFAULT_TOKEN_LIMIT if unspecified). - [JsonPropertyName("promptTokenLimit")] - public long PromptTokenLimit { get; set; } - - /// Tokens consumed by the system prompt. - [JsonPropertyName("systemTokens")] - public long SystemTokens { get; set; } - - /// Tokens consumed by tool definitions sent to the model (excludes deferred tools). - [JsonPropertyName("toolDefinitionsTokens")] - public long ToolDefinitionsTokens { get; set; } - - /// Sum of system, conversation and tool-definition tokens. - [JsonPropertyName("totalTokens")] - public long TotalTokens { get; set; } + /// Unique identifier for the pending permission request. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; } -/// Token breakdown for the session's current context window, or null if uninitialized. +/// List of pending permission requests reconstructed from event history. [Experimental(Diagnostics.Experimental)] -public sealed class MetadataContextInfoResult +public sealed class PendingPermissionRequestList { - /// Token breakdown for the current context window, or null if the session has not yet been initialized (no system prompt or tool metadata cached). - [JsonPropertyName("contextInfo")] - public MetadataContextInfoResultContextInfo? ContextInfo { get; set; } + /// Pending permission prompts reconstructed from the session's event history. Equivalent to the set of `permission.requested` events that have not yet been followed by a matching `permission.completed` event. Used by clients (e.g. the CLI) to hydrate UI for prompts that were emitted before the client attached to the session. + [JsonPropertyName("items")] + public IList Items { get => field ??= []; set; } } -/// Model identifier and token limits used to compute the context-info breakdown. +/// No parameters; returns currently-pending permission requests for the session. [Experimental(Diagnostics.Experimental)] -internal sealed class MetadataContextInfoRequest +internal sealed class PermissionsPendingRequestsRequest { - /// Maximum output tokens allowed by the target model. Pass 0 if unknown. - [JsonPropertyName("outputTokenLimit")] - public long OutputTokenLimit { get; set; } - - /// Maximum prompt tokens allowed by the target model. Pass 0 to use the runtime default. - [JsonPropertyName("promptTokenLimit")] - public long PromptTokenLimit { get; set; } - - /// Model identifier used for tokenization. Omit to use the session default. Used both for token counting and to compute display values. - [JsonPropertyName("selectedModel")] - public string? SelectedModel { get; set; } - /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Successful compaction history for the session. -public sealed class MetadataContextAttributionResultContextAttributionCompactions +/// Indicates whether the operation succeeded. +[Experimental(Diagnostics.Experimental)] +public sealed class PermissionsSetApproveAllResult { - /// Number of successful compactions in this session. - [JsonPropertyName("count")] - public long Count { get; set; } + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// RPC data type for MetadataContextAttributionResultContextAttributionEntry operations. -public sealed class MetadataContextAttributionResultContextAttributionEntry +/// Allow-all toggle for tool permission requests, with an optional telemetry source. +[Experimental(Diagnostics.Experimental)] +internal sealed class PermissionsSetApproveAllRequest { - /// Supplementary per-entry metadata (e.g. `messageCount`, `role`, `evictable`, `pluginSource`). Values are stringified; parse as needed and ignore unrecognized keys. - [JsonPropertyName("attributes")] - public IDictionary? Attributes { get; set; } + /// Whether to auto-approve all tool permission requests. + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } - /// Identifier for this entry, formed by joining its `kind` and source name (e.g. `tool:bash`, `skill:tmux`, `toolDefinition:bash`); unique within the snapshot. Use it to match the same entry across snapshots, to correlate with other APIs (skill/agent/MCP registries), and as the `parentId` target for nesting. Distinct from the human-facing `label`. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; - /// Source category for this entry. Not a closed set — tolerate unknown values. Known values today: `skill`, `subagent`, `mcpServer`, `tool`, `system`, `toolDefinition`, `plugin`. - [JsonPropertyName("kind")] - public string Kind { get; set; } = string.Empty; + /// Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers. + [JsonPropertyName("source")] + public PermissionsSetApproveAllSource? Source { get; set; } +} - /// Human-readable display label, e.g. `bash` or `skill: tmux`. Presentation-only; may be localized/reformatted without notice — do not key off it. - [JsonPropertyName("label")] - public string Label { get; set; } = string.Empty; +/// Indicates whether the operation succeeded and reports the post-mutation state. +[Experimental(Diagnostics.Experimental)] +public sealed class AllowAllPermissionSetResult +{ + /// Authoritative full allow-all state after the mutation. + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } - /// Optional `id` of the parent entry: e.g. a `plugin` entry parenting its `skill`/`mcpServer` entries, or the `system` entry parenting `toolDefinition` entries. Omitted for top-level entries. - [JsonPropertyName("parentId")] - public string? ParentId { get; set; } + /// Authoritative allow-all mode after the mutation. + [JsonPropertyName("mode")] + public PermissionsAllowAllMode? Mode { get; set; } - /// Token count currently in context attributable to this entry. - [JsonPropertyName("tokens")] - public long Tokens { get; set; } + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// Per-source token attribution snapshot for the current context window. The heaviest individual messages are available separately via `metadata.getContextHeaviestMessages`. -public sealed class MetadataContextAttributionResultContextAttribution +/// Allow-all mode to apply for the session. +[Experimental(Diagnostics.Experimental)] +internal sealed class PermissionsSetAllowAllRequest { - /// Successful compaction history for the session. - [JsonPropertyName("compactions")] - public MetadataContextAttributionResultContextAttributionCompactions Compactions { get => field ??= new(); set; } + /// Legacy full allow-all toggle. Prefer `mode`; when `mode` is omitted, `enabled: true` is treated as `mode: "on"` and any other value is treated as `mode: "off"`. + [JsonPropertyName("enabled")] + public bool? Enabled { get; set; } - /// Flat list of per-source attribution entries. Group by `kind` and render unrecognized kinds generically. Nesting and rollups are expressed via `parentId`. - [JsonPropertyName("entries")] - public IList Entries { get => field ??= []; set; } + /// Allow-all mode to apply. `on` enables full allow-all; `auto` enables advisory LLM auto-approval; `off` disables both. + [JsonPropertyName("mode")] + public PermissionsAllowAllMode? Mode { get; set; } - /// Total token count of the current context window the entries are measured against (system message + conversation messages + tool definitions — the same total reported by /context). Divide an entry's `tokens` by this to derive its share. - [JsonPropertyName("totalTokens")] - public long TotalTokens { get; set; } + /// Optional model id for the `auto` mode auto-approval LLM judging. Only meaningful when `mode` is `auto`; ignored otherwise. When omitted, the session resolves a default judge model: `gpt-5.5` for CAPI sessions and the session's active model for BYOK sessions. + [JsonPropertyName("model")] + public string? Model { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + + /// Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers. + [JsonPropertyName("source")] + public PermissionsSetAllowAllSource? Source { get; set; } } -/// Per-source attribution breakdown for the session's current context window, or null if uninitialized. +/// Current allow-all permission mode. [Experimental(Diagnostics.Experimental)] -public sealed class MetadataContextAttributionResult +public sealed class AllowAllPermissionState { - /// Per-source context-window attribution, or null if the session has not yet been initialized (no system prompt or tool metadata cached). - [JsonPropertyName("contextAttribution")] - public MetadataContextAttributionResultContextAttribution? ContextAttribution { get; set; } + /// Whether full allow-all permissions are currently active. + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } + + /// Current allow-all mode. + [JsonPropertyName("mode")] + public PermissionsAllowAllMode? Mode { get; set; } } -/// Identifies the target session. +/// No parameters. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionMetadataGetContextAttributionRequest +internal sealed class PermissionsGetAllowAllRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// A single large message currently in context. +/// Indicates whether the operation succeeded. [Experimental(Diagnostics.Experimental)] -public sealed class ContextHeaviestMessage +public sealed class PermissionsModifyRulesResult { - /// Stable identifier for this message within the snapshot. - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } +} - /// Human-readable source label, e.g. `tool: bash` or `skill: tmux`. Presentation-only. - [JsonPropertyName("label")] - public string Label { get; set; } = string.Empty; +/// Scope and add/remove instructions for modifying session- or location-scoped permission rules. +[Experimental(Diagnostics.Experimental)] +internal sealed class PermissionsModifyRulesParams +{ + /// Rules to add to the scope. Applied before `remove`/`removeAll`. + [JsonPropertyName("add")] + public IList? Add { get; set; } - /// Role of the chat message (`user`, `assistant`, or `tool`). - [JsonPropertyName("role")] - public string Role { get; set; } = string.Empty; + /// Specific rules to remove from the scope. Ignored when `removeAll` is true. + [JsonPropertyName("remove")] + public IList? Remove { get; set; } - /// Token count currently in context for this individual message. - [JsonPropertyName("tokens")] - public long Tokens { get; set; } + /// When true, removes every rule currently in the scope (after any `add` is applied). Useful for clearing the location scope wholesale. + [JsonPropertyName("removeAll")] + public bool? RemoveAll { get; set; } + + /// Whether the change applies to ephemeral session-scoped rules (cleared at session end) or to location-scoped rules persisted via the location-permissions config file. + [JsonPropertyName("scope")] + public PermissionsModifyRulesScope Scope { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// The heaviest individual messages in the session's context window, most-expensive first. +/// Indicates whether the operation succeeded. [Experimental(Diagnostics.Experimental)] -public sealed class MetadataContextHeaviestMessagesResult +public sealed class PermissionsSetRequiredResult { - /// Heaviest messages, most-expensive first. - [JsonPropertyName("messages")] - public IList Messages { get => field ??= []; set; } - - /// Total token count of the current context window, so callers can compute each message's share without a second call. - [JsonPropertyName("totalTokens")] - public long TotalTokens { get; set; } + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// Parameters for the heaviest-messages query. +/// Toggles whether permission prompts should be bridged into session events for this client. [Experimental(Diagnostics.Experimental)] -internal sealed class MetadataContextHeaviestMessagesRequest +internal sealed class PermissionsSetRequiredRequest { - /// Maximum number of messages to return, most-expensive first. Omit for the server default. - [JsonPropertyName("limit")] - public long? Limit { get; set; } + /// Whether the client wants `permission.requested` events bridged from the session-owned permission service. CLI clients that render prompt UI set this to `true` for as long as their listener is mounted; headless callers leave it unset (the default is `false`). + [JsonPropertyName("required")] + public bool Required { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Notify the session that its working directory context has changed. Emits a `session.context_changed` event so consumers (telemetry, OTel tracker, ACP, the timeline UI) can react. Use this when the host has detected a cwd/branch/repo change outside the session's normal lifecycle (e.g., after a shell command in interactive mode). For a local session, a report whose `cwd` diverges from the session's current working directory is ignored (the call still succeeds but records nothing and emits no event); move a local session's working directory via `metadata.setWorkingDirectory` instead. +/// Indicates whether the operation succeeded. [Experimental(Diagnostics.Experimental)] -public sealed class MetadataRecordContextChangeResult +public sealed class PermissionsResetSessionApprovalsResult { + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// Updated working directory and git context. Emitted as the new payload of `session.context_changed`. +/// Clears session-scoped tool permission approvals, and optionally the location-scoped ones. [Experimental(Diagnostics.Experimental)] -public sealed class SessionWorkingDirectoryContext +internal sealed class PermissionsResetSessionApprovalsRequest { - /// Merge-base commit SHA (fork point from the remote default branch). - [JsonPropertyName("baseCommit")] - public string? BaseCommit { get; set; } - - /// Current git branch name. - [JsonPropertyName("branch")] - public string? Branch { get; set; } + /// Whether location-scoped approvals are cleared too. Defaults to `true`. + [JsonPropertyName("includeLocation")] + public bool? IncludeLocation { get; set; } - /// Current working directory path. - [JsonPropertyName("cwd")] - public string Cwd { get; set; } = string.Empty; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Root directory of the git repository, resolved via git rev-parse. - [JsonPropertyName("gitRoot")] - public string? GitRoot { get; set; } - - /// Head commit of the current git branch. - [JsonPropertyName("headCommit")] - public string? HeadCommit { get; set; } - - /// Hosting platform type of the repository. - [JsonPropertyName("hostType")] - public SessionWorkingDirectoryContextHostType? HostType { get; set; } - - /// Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps). - [JsonPropertyName("repository")] - public string? Repository { get; set; } - - /// Raw host string from the git remote URL (e.g. "github.com", "dev.azure.com"). - [JsonPropertyName("repositoryHost")] - public string? RepositoryHost { get; set; } +/// Indicates whether the operation succeeded. +[Experimental(Diagnostics.Experimental)] +public sealed class PermissionsNotifyPromptShownResult +{ + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// Updated working-directory/git context to record on the session. +/// Notification payload describing the permission prompt that the client just rendered. [Experimental(Diagnostics.Experimental)] -internal sealed class MetadataRecordContextChangeRequest +internal sealed class PermissionPromptShownNotification { - /// Updated working directory and git context. Emitted as the new payload of `session.context_changed`. - [JsonPropertyName("context")] - public SessionWorkingDirectoryContext Context { get => field ??= new(); set; } + /// Human-readable description of the prompt the user is being asked to approve. Used by the runtime to fire the registered `permission_prompt` notification hook (e.g. terminal bell, desktop notification). + [JsonPropertyName("message")] + public string Message { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Update the session's working directory. Used by the host when the user explicitly changes cwd (e.g., the `/cd` slash command). The host is responsible for any related side-effects (file index, etc.); it does NOT change the process working directory (a session's cwd is per-session, not process-global). For local sessions the runtime validates the target first (an absolute path that exists on disk) and re-bases the permission primary directory; a rejected validation fails the call before anything is mutated, persisted, or emitted. Location-scoped permission rules are then re-keyed to the new directory (best-effort). Remote sessions only record the path. +/// Snapshot of the session's allow-listed directories and primary working directory. [Experimental(Diagnostics.Experimental)] -public sealed class MetadataSetWorkingDirectoryResult +public sealed class PermissionPathsList { - /// Working directory after the update. - [JsonPropertyName("workingDirectory")] - public string WorkingDirectory { get; set; } = string.Empty; + /// All directories currently allowed for tool access on this session. + [JsonPropertyName("directories")] + public IList Directories { get => field ??= []; set; } + + /// The primary working directory for this session. + [JsonPropertyName("primary")] + public string Primary { get; set; } = string.Empty; } -/// Absolute path to set as the session's new working directory. For local sessions the path must be absolute and exist on disk: it is validated before any session state changes, and a failing validation rejects the call with nothing mutated, persisted, or emitted. Remote sessions record the path as-is. +/// No parameters; returns the session's allow-listed directories. [Experimental(Diagnostics.Experimental)] -internal sealed class MetadataSetWorkingDirectoryRequest +internal sealed class PermissionsPathsListRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; +} - /// Absolute path to set as the session's working directory. The runtime updates the session's recorded cwd so subsequent operations (shell tools, file lookups, telemetry) anchor to it. - [JsonPropertyName("workingDirectory")] - public string WorkingDirectory { get; set; } = string.Empty; +/// Indicates whether the operation succeeded. +[Experimental(Diagnostics.Experimental)] +public sealed class PermissionsPathsAddResult +{ + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// Re-tokenize the session's existing messages against `modelId` and return the token totals. Useful for hosts that want an initial estimate of context usage on session resume, before the next agent turn fires `session.context_info_changed` events. Returns zeros for an empty session. +/// Directory path to add to the session's allowed directories. [Experimental(Diagnostics.Experimental)] -public sealed class MetadataRecomputeContextTokensResult +internal sealed class PermissionPathsAddParams { - /// Tokens contributed by user/assistant/tool messages (excludes system/developer prompts). - [JsonPropertyName("messagesTokenCount")] - public long MessagesTokenCount { get; set; } + /// Directory to add to the allow-list. The runtime resolves and validates the path before adding. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; - /// Tokens contributed by system/developer prompt snapshots. - [JsonPropertyName("systemTokenCount")] - public long SystemTokenCount { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Sum of tokens across chat-context and system-context messages currently held by the session. - [JsonPropertyName("totalTokens")] - public long TotalTokens { get; set; } +/// Indicates whether the operation succeeded. +[Experimental(Diagnostics.Experimental)] +public sealed class PermissionsPathsUpdatePrimaryResult +{ + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// Model identifier to use when re-tokenizing the session's existing messages. +/// Directory path to set as the session's new primary working directory. [Experimental(Diagnostics.Experimental)] -internal sealed class MetadataRecomputeContextTokensRequest +internal sealed class PermissionPathsUpdatePrimaryParams { - /// Model identifier used for tokenization. The runtime token-counts both chat-context and system-context messages against this model. - [JsonPropertyName("modelId")] - public string ModelId { get; set; } = string.Empty; + /// Directory to set as the new primary working directory for the session's permission policy. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Availability of built-in job tools surfaced to boundary consumers. +/// Indicates whether the supplied path is within the session's allowed directories. [Experimental(Diagnostics.Experimental)] -public sealed class SessionSettingsBuiltInToolAvailabilitySnapshot +public sealed class PermissionPathsAllowedCheckResult { - /// Gets or sets the createPullRequest value. - [JsonPropertyName("createPullRequest")] - public bool? CreatePullRequest { get; set; } - - /// Gets or sets the reportProgress value. - [JsonPropertyName("reportProgress")] - public bool? ReportProgress { get; set; } + /// Whether the path is within the session's allowed directories. + [JsonPropertyName("allowed")] + public bool Allowed { get; set; } } -/// Redacted job settings for a session. The job nonce is excluded. +/// Path to evaluate against the session's allowed directories. [Experimental(Diagnostics.Experimental)] -public sealed class SessionSettingsJobSnapshot +internal sealed class PermissionPathsAllowedCheckParams { - /// Gets or sets the builtInToolAvailability value. - [JsonPropertyName("builtInToolAvailability")] - public SessionSettingsBuiltInToolAvailabilitySnapshot? BuiltInToolAvailability { get; set; } + /// Path to check against the session's allowed directories. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; - /// Gets or sets the eventType value. - [JsonPropertyName("eventType")] - public string? EventType { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Gets or sets the isTriggerJob value. - [JsonPropertyName("isTriggerJob")] - public bool? IsTriggerJob { get; set; } +/// Indicates whether the supplied path is within the session's workspace directory. +[Experimental(Diagnostics.Experimental)] +public sealed class PermissionPathsWorkspaceCheckResult +{ + /// Whether the path is within the session workspace directory. + [JsonPropertyName("allowed")] + public bool Allowed { get; set; } } -/// Redacted model routing settings for a session. +/// Path to evaluate against the session's workspace (primary) directory. [Experimental(Diagnostics.Experimental)] -public sealed class SessionSettingsModelSnapshot +internal sealed class PermissionPathsWorkspaceCheckParams { - /// Gets or sets the callbackUrl value. - [JsonPropertyName("callbackUrl")] - public string? CallbackUrl { get; set; } + /// Path to check against the session workspace directory. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; - /// Gets or sets the defaultReasoningEffort value. - [JsonPropertyName("defaultReasoningEffort")] - public string? DefaultReasoningEffort { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Gets or sets the instanceId value. - [JsonPropertyName("instanceId")] - public string? InstanceId { get; set; } +/// Resolved location-permissions key and type. +[Experimental(Diagnostics.Experimental)] +public sealed class PermissionLocationResolveResult +{ + /// Location key used in the location-permissions store. + [JsonPropertyName("locationKey")] + public string LocationKey { get; set; } = string.Empty; - /// Gets or sets the model value. - [JsonPropertyName("model")] - public string? Model { get; set; } + /// Whether the location is a git repo or directory. + [JsonPropertyName("locationType")] + public PermissionLocationType LocationType { get; set; } } -/// Online-evaluation settings safe to expose across the SDK boundary. +/// Working directory to resolve into a location-permissions key. [Experimental(Diagnostics.Experimental)] -public sealed class SessionSettingsOnlineEvaluationSnapshot +internal sealed class PermissionLocationResolveParams { - /// Gets or sets the disableOnlineEvaluation value. - [JsonPropertyName("disableOnlineEvaluation")] - public bool? DisableOnlineEvaluation { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; - /// Gets or sets the enableOnlineEvaluationOutputFile value. - [JsonPropertyName("enableOnlineEvaluationOutputFile")] - public bool? EnableOnlineEvaluationOutputFile { get; set; } + /// Working directory whose permission location should be resolved. + [JsonPropertyName("workingDirectory")] + public string WorkingDirectory { get; set; } = string.Empty; } -/// Redacted repository and GitHub host settings for a session. +/// Summary of persisted location permissions applied to the session. [Experimental(Diagnostics.Experimental)] -public sealed class SessionSettingsRepoSnapshot +public sealed class PermissionLocationApplyResult { - /// Gets or sets the branch value. - [JsonPropertyName("branch")] - public string? Branch { get; set; } - - /// Gets or sets the commit value. - [JsonPropertyName("commit")] - public string? Commit { get; set; } + /// Number of persisted allowed directories added to the live path manager. + [JsonPropertyName("appliedDirectoryCount")] + public long AppliedDirectoryCount { get; set; } - /// Gets or sets the host value. - [JsonPropertyName("host")] - public string? Host { get; set; } + /// Number of location-scoped rules added to the live permission service. + [JsonPropertyName("appliedRuleCount")] + public long AppliedRuleCount { get; set; } - /// Gets or sets the hostProtocol value. - [JsonPropertyName("hostProtocol")] - public string? HostProtocol { get; set; } + /// Location-scoped rules applied to the live permission service. + [JsonPropertyName("appliedRules")] + public IList AppliedRules { get => field ??= []; set; } - /// Gets or sets the id value. - [JsonPropertyName("id")] - public double? Id { get; set; } + /// Whether a different location was applied since the previous apply call. + [JsonPropertyName("changed")] + public bool Changed { get; set; } - /// Gets or sets the name value. - [JsonPropertyName("name")] - public string? Name { get; set; } + /// Location key used in the location-permissions store. + [JsonPropertyName("locationKey")] + public string LocationKey { get; set; } = string.Empty; - /// Gets or sets the ownerId value. - [JsonPropertyName("ownerId")] - public double? OwnerId { get; set; } + /// Whether the location is a git repo or directory. + [JsonPropertyName("locationType")] + public PermissionLocationType LocationType { get; set; } +} - /// Gets or sets the ownerName value. - [JsonPropertyName("ownerName")] - public string? OwnerName { get; set; } +/// Working directory to load persisted location permissions for. +[Experimental(Diagnostics.Experimental)] +internal sealed class PermissionLocationApplyParams +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; - /// Gets or sets the prCommitCount value. - [JsonPropertyName("prCommitCount")] - public double? PrCommitCount { get; set; } + /// Working directory whose persisted location permissions should be applied. + [JsonPropertyName("workingDirectory")] + public string WorkingDirectory { get; set; } = string.Empty; +} - /// Gets or sets the readWrite value. - [JsonPropertyName("readWrite")] - public bool? ReadWrite { get; set; } - - /// Gets or sets the secretScanningUrl value. - [JsonPropertyName("secretScanningUrl")] - public string? SecretScanningUrl { get; set; } - - /// Gets or sets the serverUrl value. - [JsonPropertyName("serverUrl")] - public string? ServerUrl { get; set; } +/// Indicates whether the operation succeeded. +[Experimental(Diagnostics.Experimental)] +public sealed class PermissionsLocationsAddToolApprovalResult +{ + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// Redacted validation and memory-tool settings for a session. +/// Tool approval to persist and apply. +/// Polymorphic base type discriminated by kind. [Experimental(Diagnostics.Experimental)] -public sealed class SessionSettingsValidationSnapshot +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(PermissionsLocationsAddToolApprovalDetailsCommands), "commands")] +[JsonDerivedType(typeof(PermissionsLocationsAddToolApprovalDetailsRead), "read")] +[JsonDerivedType(typeof(PermissionsLocationsAddToolApprovalDetailsWrite), "write")] +[JsonDerivedType(typeof(PermissionsLocationsAddToolApprovalDetailsMcp), "mcp")] +[JsonDerivedType(typeof(PermissionsLocationsAddToolApprovalDetailsMcpSampling), "mcp-sampling")] +[JsonDerivedType(typeof(PermissionsLocationsAddToolApprovalDetailsMemory), "memory")] +[JsonDerivedType(typeof(PermissionsLocationsAddToolApprovalDetailsCustomTool), "custom-tool")] +[JsonDerivedType(typeof(PermissionsLocationsAddToolApprovalDetailsExtensionManagement), "extension-management")] +[JsonDerivedType(typeof(PermissionsLocationsAddToolApprovalDetailsFactory), "factory")] +[JsonDerivedType(typeof(PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess), "extension-permission-access")] +public partial class PermissionsLocationsAddToolApprovalDetails { - /// Gets or sets the advisoryEnabled value. - [JsonPropertyName("advisoryEnabled")] - public bool? AdvisoryEnabled { get; set; } + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; +} - /// Gets or sets the codeqlEnabled value. - [JsonPropertyName("codeqlEnabled")] - public bool? CodeqlEnabled { get; set; } - /// Gets or sets the codeReviewEnabled value. - [JsonPropertyName("codeReviewEnabled")] - public bool? CodeReviewEnabled { get; set; } +/// Location-persisted tool approval details for specific command identifiers. +/// The commands variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionsLocationsAddToolApprovalDetailsCommands : PermissionsLocationsAddToolApprovalDetails +{ + /// + [JsonIgnore] + public override string Kind => "commands"; - /// Gets or sets the codeReviewModel value. - [JsonPropertyName("codeReviewModel")] - public string? CodeReviewModel { get; set; } + /// Command identifiers covered by this approval. + [JsonPropertyName("commandIdentifiers")] + public required IList CommandIdentifiers { get; set; } +} - /// Gets or sets the dependabotTimeout value. - [JsonPropertyName("dependabotTimeout")] - public double? DependabotTimeout { get; set; } +/// Location-persisted tool approval details for read-only filesystem operations. +/// The read variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionsLocationsAddToolApprovalDetailsRead : PermissionsLocationsAddToolApprovalDetails +{ + /// + [JsonIgnore] + public override string Kind => "read"; +} - /// Gets or sets the memoryStoreEnabled value. - [JsonPropertyName("memoryStoreEnabled")] - public bool? MemoryStoreEnabled { get; set; } +/// Location-persisted tool approval details for filesystem write operations. +/// The write variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionsLocationsAddToolApprovalDetailsWrite : PermissionsLocationsAddToolApprovalDetails +{ + /// + [JsonIgnore] + public override string Kind => "write"; +} - /// Gets or sets the memoryVoteEnabled value. - [JsonPropertyName("memoryVoteEnabled")] - public bool? MemoryVoteEnabled { get; set; } +/// Location-persisted tool approval details for an MCP server tool, or all tools when `toolName` is null. +/// The mcp variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionsLocationsAddToolApprovalDetailsMcp : PermissionsLocationsAddToolApprovalDetails +{ + /// + [JsonIgnore] + public override string Kind => "mcp"; - /// Gets or sets the secretScanningEnabled value. - [JsonPropertyName("secretScanningEnabled")] - public bool? SecretScanningEnabled { get; set; } + /// MCP server name. + [JsonPropertyName("serverName")] + public required string ServerName { get; set; } - /// Gets or sets the timeout value. - [JsonPropertyName("timeout")] - public double? Timeout { get; set; } + /// MCP tool name, or null to cover every tool on the server. + [JsonPropertyName("toolName")] + public string? ToolName { get; set; } } -/// Redacted, serializable view of session runtime settings for SDK boundary consumers. Secrets and raw feature flags are intentionally excluded. +/// Location-persisted tool approval details for MCP sampling requests from a server. +/// The mcp-sampling variant of . [Experimental(Diagnostics.Experimental)] -internal sealed class SessionSettingsSnapshot +public partial class PermissionsLocationsAddToolApprovalDetailsMcpSampling : PermissionsLocationsAddToolApprovalDetails { - /// Gets or sets the clientName value. - [JsonPropertyName("clientName")] - public string? ClientName { get; set; } - - /// Gets or sets the job value. - [JsonPropertyName("job")] - public SessionSettingsJobSnapshot Job { get => field ??= new(); set; } - - /// Gets or sets the model value. - [JsonPropertyName("model")] - public SessionSettingsModelSnapshot Model { get => field ??= new(); set; } + /// + [JsonIgnore] + public override string Kind => "mcp-sampling"; - /// Gets or sets the onlineEvaluation value. - [JsonPropertyName("onlineEvaluation")] - public SessionSettingsOnlineEvaluationSnapshot OnlineEvaluation { get => field ??= new(); set; } + /// MCP server name. + [JsonPropertyName("serverName")] + public required string ServerName { get; set; } +} - /// Gets or sets the repo value. - [JsonPropertyName("repo")] - public SessionSettingsRepoSnapshot Repo { get => field ??= new(); set; } +/// Location-persisted tool approval details for writes to long-term memory. +/// The memory variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionsLocationsAddToolApprovalDetailsMemory : PermissionsLocationsAddToolApprovalDetails +{ + /// + [JsonIgnore] + public override string Kind => "memory"; +} - /// Gets or sets the startTimeMs value. - [JsonPropertyName("startTimeMs")] - public double? StartTimeMs { get; set; } +/// Location-persisted tool approval details for a custom tool, keyed by tool name. +/// The custom-tool variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionsLocationsAddToolApprovalDetailsCustomTool : PermissionsLocationsAddToolApprovalDetails +{ + /// + [JsonIgnore] + public override string Kind => "custom-tool"; - /// Gets or sets the timeoutMs value. - [JsonPropertyName("timeoutMs")] - public double? TimeoutMs { get; set; } + /// Custom tool name. + [JsonPropertyName("toolName")] + public required string ToolName { get; set; } +} - /// Gets or sets the validation value. - [JsonPropertyName("validation")] - public SessionSettingsValidationSnapshot Validation { get => field ??= new(); set; } +/// Location-persisted tool approval details for extension-management operations, optionally narrowed by operation. +/// The extension-management variant of . +[Experimental(Diagnostics.Experimental)] +public partial class PermissionsLocationsAddToolApprovalDetailsExtensionManagement : PermissionsLocationsAddToolApprovalDetails +{ + /// + [JsonIgnore] + public override string Kind => "extension-management"; - /// Gets or sets the version value. - [JsonPropertyName("version")] - public string? Version { get; set; } + /// Optional operation identifier; when omitted, the approval covers all extension management operations. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("operation")] + public string? Operation { get; set; } } -/// Identifies the target session. +/// Location-persisted factory approval, optionally narrowed by approval key. +/// The factory variant of . [Experimental(Diagnostics.Experimental)] -internal sealed class SessionSettingsSnapshotRequest +public partial class PermissionsLocationsAddToolApprovalDetailsFactory : PermissionsLocationsAddToolApprovalDetails { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// + [JsonIgnore] + public override string Kind => "factory"; + + /// Optional factory operation name or canonical approval key; when omitted, the approval covers all factory operations. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("approvalKey")] + public string? ApprovalKey { get; set; } } -/// Result of evaluating a Rust-owned settings predicate. +/// Location-persisted tool approval details for an extension's permission-gated capability access, keyed by extension name. +/// The extension-permission-access variant of . [Experimental(Diagnostics.Experimental)] -internal sealed class SessionSettingsEvaluatePredicateResult +public partial class PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess : PermissionsLocationsAddToolApprovalDetails { - /// Gets or sets the enabled value. - [JsonPropertyName("enabled")] - public bool Enabled { get; set; } + /// + [JsonIgnore] + public override string Kind => "extension-permission-access"; + + /// Extension name. + [JsonPropertyName("extensionName")] + public required string ExtensionName { get; set; } } -/// Named Rust-owned settings predicate to evaluate for this session. +/// Location-scoped tool approval to persist. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionSettingsEvaluatePredicateRequest +internal sealed class PermissionLocationAddToolApprovalParams { - /// Predicate name. The runtime owns the raw feature-flag names and composition logic. - [JsonPropertyName("name")] - public SessionSettingsPredicateName Name { get; set; } + /// Tool approval to persist and apply. + [JsonPropertyName("approval")] + public PermissionsLocationsAddToolApprovalDetails Approval { get => field ??= new(); set; } + + /// Location key (git root or cwd) to persist the approval to. + [JsonPropertyName("locationKey")] + public string LocationKey { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; - - /// Tool name for tool-scoped predicates such as trivial-change handling. - [JsonPropertyName("toolName")] - public string? ToolName { get; set; } } -/// Identifier of the spawned process, used to correlate streamed output and exit notifications. +/// Folder trust check result. [Experimental(Diagnostics.Experimental)] -public sealed class ShellExecResult +public sealed class FolderTrustCheckResult { - /// Unique identifier for tracking streamed output. - [JsonPropertyName("processId")] - public string ProcessId { get; set; } = string.Empty; + /// Whether the folder is trusted. + [JsonPropertyName("trusted")] + public bool Trusted { get; set; } } -/// Shell command to run, with optional working directory and timeout in milliseconds. +/// Folder path to check for trust. [Experimental(Diagnostics.Experimental)] -internal sealed class ShellExecRequest +internal sealed class FolderTrustCheckParams { - /// Shell command to execute. - [JsonPropertyName("command")] - public string Command { get; set; } = string.Empty; - - /// Working directory (defaults to session working directory). - [JsonPropertyName("cwd")] - public string? Cwd { get; set; } + /// Folder path to check. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; - - /// Timeout in milliseconds (default: 30000). - [JsonConverter(typeof(MillisecondsTimeSpanConverter))] - [JsonPropertyName("timeout")] - public TimeSpan? Timeout { get; set; } } -/// Indicates whether the signal was delivered; false if the process was unknown or already exited. +/// Indicates whether the operation succeeded. [Experimental(Diagnostics.Experimental)] -public sealed class ShellKillResult +public sealed class PermissionsFolderTrustAddTrustedResult { - /// Whether the signal was sent successfully. - [JsonPropertyName("killed")] - public bool Killed { get; set; } + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } } -/// Identifier of a process previously returned by "shell.exec" and the signal to send. +/// Folder path to add to trusted folders. [Experimental(Diagnostics.Experimental)] -internal sealed class ShellKillRequest +internal sealed class FolderTrustAddParams { - /// Process identifier returned by shell.exec. - [JsonPropertyName("processId")] - public string ProcessId { get; set; } = string.Empty; + /// Folder path to mark as trusted. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; - - /// Signal to send (default: SIGTERM). - [JsonPropertyName("signal")] - public ShellKillSignal? Signal { get; set; } } -/// Result of a user-requested shell command. +/// Indicates whether the operation succeeded. [Experimental(Diagnostics.Experimental)] -public sealed class UserRequestedShellCommandResult +public sealed class PermissionsUrlsSetUnrestrictedModeResult { - /// Error output when the execution failed. - [JsonPropertyName("error")] - public string? Error { get; set; } - - /// Process exit code, when available. - [JsonPropertyName("exitCode")] - public long? ExitCode { get; set; } - - /// Captured command output. - [JsonPropertyName("output")] - public string Output { get; set; } = string.Empty; - - /// Whether the command completed successfully. + /// Whether the operation succeeded. [JsonPropertyName("success")] public bool Success { get; set; } - - /// Tool call id emitted for the shell execution. - [JsonPropertyName("toolCallId")] - public string ToolCallId { get; set; } = string.Empty; } -/// User-requested shell command and cancellation handle. +/// Whether the URL-permission policy should run in unrestricted mode. [Experimental(Diagnostics.Experimental)] -internal sealed class ShellExecuteUserRequestedRequest +internal sealed class PermissionUrlsSetUnrestrictedModeParams { - /// Shell command to execute. - [JsonPropertyName("command")] - public string Command { get; set; } = string.Empty; - - /// Caller-provided cancellation handle for this execution. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; + /// Whether to allow access to all URLs without prompting. Toggles the runtime's URL-permission policy in place. + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Cancellation result for a user-requested shell command. +/// The repository the remote session targets. [Experimental(Diagnostics.Experimental)] -public sealed class CancelUserRequestedShellCommandResult +public sealed class MetadataSnapshotRemoteMetadataRepository { - /// Whether an in-flight execution was found and signalled to cancel. - [JsonPropertyName("cancelled")] - public bool Cancelled { get; set; } + /// The branch the remote session is operating on. + [JsonPropertyName("branch")] + public string Branch { get; set; } = string.Empty; + + /// The GitHub repository name (without owner). + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// The GitHub owner (user or organization) of the target repository. + [JsonPropertyName("owner")] + public string Owner { get; set; } = string.Empty; } -/// User-requested shell execution cancellation handle. +/// Remote-session-specific metadata. Populated only when `isRemote` is true. Fields are immutable for the lifetime of the session. [Experimental(Diagnostics.Experimental)] -internal sealed class ShellCancelUserRequestedRequest +public sealed class MetadataSnapshotRemoteMetadata { - /// Request ID previously passed to executeUserRequested. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; + /// The pull request number the remote session is associated with, if any. + [JsonPropertyName("pullRequestNumber")] + public long? PullRequestNumber { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// The repository the remote session targets. + [JsonPropertyName("repository")] + public MetadataSnapshotRemoteMetadataRepository Repository { get => field ??= new(); set; } + + /// The original resource identifier (task ID or PR node ID), preserved across event-replay reconstructions. Falls back to `sessionId` when absent. + [JsonPropertyName("resourceId")] + public string? ResourceId { get; set; } + + /// Whether the remote task originated from Copilot Coding Agent (cca) or a CLI `--remote` invocation. + [JsonPropertyName("taskType")] + public MetadataSnapshotRemoteMetadataTaskType? TaskType { get; set; } } -/// Post-compaction context window usage breakdown. -[Experimental(Diagnostics.Experimental)] -public sealed class HistoryCompactContextWindow +/// Public-facing projection of workspace metadata for SDK / TUI consumers. +public sealed class SessionMetadataSnapshotWorkspace { - /// Token count from non-system messages (user, assistant, tool). - [JsonPropertyName("conversationTokens")] - public long? ConversationTokens { get; set; } + /// Branch checked out at session start, if any. + [JsonPropertyName("branch")] + public string? Branch { get; set; } - /// Current total tokens in the context window (system + conversation + tool definitions). - [JsonPropertyName("currentTokens")] - public long CurrentTokens { get; set; } + /// ISO 8601 timestamp when the workspace was created. + [JsonPropertyName("created_at")] + public DateTimeOffset? CreatedAt { get; set; } - /// Current number of messages in the conversation. - [JsonPropertyName("messagesLength")] - public long MessagesLength { get; set; } + /// Current working directory at session start. + [JsonPropertyName("cwd")] + public string? Cwd { get; set; } - /// Token count from system message(s). - [JsonPropertyName("systemTokens")] - public long? SystemTokens { get; set; } + /// Resolved git root for cwd, if any. + [JsonPropertyName("git_root")] + public string? GitRoot { get; set; } - /// Maximum token count for the model's context window. - [JsonPropertyName("tokenLimit")] - public long TokenLimit { get; set; } + /// Repository host type, if known. + [JsonPropertyName("host_type")] + public WorkspaceSummaryHostType? HostType { get; set; } - /// Token count from tool definitions. - [JsonPropertyName("toolDefinitionsTokens")] - public long? ToolDefinitionsTokens { get; set; } + /// Workspace identifier (1:1 with sessionId). + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] + [MinLength(1)] + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; + + /// Display name for the session, if set. + [JsonPropertyName("name")] + public string? Name { get; set; } + + /// Repository identifier in 'owner/repo' or 'org/project/repo' format, if any. + [JsonPropertyName("repository")] + public string? Repository { get; set; } + + /// ISO 8601 timestamp when the workspace was last updated. + [JsonPropertyName("updated_at")] + public DateTimeOffset? UpdatedAt { get; set; } + + /// Whether the display name was explicitly set by the user. + [JsonPropertyName("user_named")] + public bool? UserNamed { get; set; } } -/// Compaction outcome with the number of tokens and messages removed, summary text, and the resulting context window breakdown. +/// Point-in-time snapshot of slow-changing session identifier and state fields. [Experimental(Diagnostics.Experimental)] -public sealed class HistoryCompactResult +public sealed class SessionMetadataSnapshot { - /// Post-compaction context window usage breakdown. - [JsonPropertyName("contextWindow")] - public HistoryCompactContextWindow? ContextWindow { get; set; } + /// True when the session was detected to be in use by another process at construction time. Local consumers may surface a confirmation prompt before fully attaching. Always false for new sessions. + [JsonPropertyName("alreadyInUse")] + public bool AlreadyInUse { get; set; } - /// Number of messages removed during compaction. - [JsonPropertyName("messagesRemoved")] - public long MessagesRemoved { get; set; } + /// Runtime client name associated with the session (telemetry identifier). + [JsonPropertyName("clientName")] + public string? ClientName { get; set; } - /// Whether compaction completed successfully. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// The current agent mode for this session (e.g., 'interactive', 'plan', 'autopilot'). + [JsonPropertyName("currentMode")] + public MetadataSnapshotCurrentMode CurrentMode { get; set; } - /// Summary text produced by compaction. Omitted when compaction did not produce a summary (e.g. failure path). - [JsonPropertyName("summaryContent")] - public string? SummaryContent { get; set; } + /// User-provided name supplied at session construction (via `--name`), if any. Immutable after construction. + [JsonPropertyName("initialName")] + public string? InitialName { get; set; } - /// Number of tokens freed by compaction. - [JsonPropertyName("tokensRemoved")] - public long TokensRemoved { get; set; } -} + /// Whether this is a remote session (i.e., one whose runtime executes elsewhere and is steered through this process). + [JsonPropertyName("isRemote")] + public bool IsRemote { get; set; } -/// Optional compaction parameters. -[Experimental(Diagnostics.Experimental)] -public sealed class HistoryCompactRequest -{ - /// Optional user-provided instructions to focus the compaction summary. - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] - [MaxLength(4000)] - [JsonPropertyName("customInstructions")] - public string? CustomInstructions { get; set; } -} + /// ISO 8601 timestamp of when the session's persisted state was last modified on disk. For new sessions, equals startTime. For resumed sessions, reflects the previous modification time at construction. + [JsonPropertyName("modifiedTime")] + public DateTimeOffset ModifiedTime { get; set; } -/// Optional compaction parameters. -[Experimental(Diagnostics.Experimental)] -internal sealed class HistoryCompactRequestWithSession -{ - /// Optional user-provided instructions to focus the compaction summary. - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] - [MaxLength(4000)] - [JsonPropertyName("customInstructions")] - public string? CustomInstructions { get; set; } + /// Remote-session-specific metadata. Populated only when `isRemote` is true. Fields are immutable for the lifetime of the session. + [JsonPropertyName("remoteMetadata")] + public MetadataSnapshotRemoteMetadata? RemoteMetadata { get; set; } - /// Target session identifier. + /// Currently selected model identifier, if any. + [JsonPropertyName("selectedModel")] + public string? SelectedModel { get; set; } + + /// The unique identifier of the session. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; + + /// Current session limits, or null when no limits are active. + [JsonPropertyName("sessionLimits")] + public SessionLimitsConfig? SessionLimits { get; set; } + + /// ISO 8601 timestamp of when the session started. + [JsonPropertyName("startTime")] + public DateTimeOffset StartTime { get; set; } + + /// Short human-readable summary of the session, if known. Omitted when no summary has been generated. + [JsonPropertyName("summary")] + public string? Summary { get; set; } + + /// Absolute path to the session's current working directory. + [JsonPropertyName("workingDirectory")] + public string WorkingDirectory { get; set; } = string.Empty; + + /// Public-facing workspace metadata for this session, or null if the session has no associated workspace. Excludes runtime-internal fields (GitHub IDs, summary count, internal flags). + [JsonPropertyName("workspace")] + public SessionMetadataSnapshotWorkspace? Workspace { get; set; } + + /// Absolute path to the session's workspace directory on disk, or null if the session has no associated workspace. + [JsonPropertyName("workspacePath")] + public string? WorkspacePath { get; set; } } -/// Number of events that were removed by the truncation. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -public sealed class HistoryTruncateResult +internal sealed class SessionMetadataSnapshotRequest { - /// Number of events that were removed. - [JsonPropertyName("eventsRemoved")] - public long EventsRemoved { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Identifier of the event to truncate to; this event and all later events are removed. +/// Indicates whether the local session is currently processing a turn or background continuation. [Experimental(Diagnostics.Experimental)] -internal sealed class HistoryTruncateRequest -{ - /// Event ID to truncate to. This event and all events after it are removed from the session. - [JsonPropertyName("eventId")] - public string EventId { get; set; } = string.Empty; - - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} - -/// Indicates whether an in-progress background compaction was cancelled. -[Experimental(Diagnostics.Experimental)] -public sealed class HistoryCancelBackgroundCompactionResult +public sealed class MetadataIsProcessingResult { - /// Whether an in-progress background compaction was cancelled. False when no compaction was running, when the session is remote, or when the underlying processor was unavailable. - [JsonPropertyName("cancelled")] - public bool Cancelled { get; set; } + /// Whether the session is currently processing user/agent messages. False for non-local sessions (which don't run a local agentic loop). Reflects an in-flight turn or background continuation. + [JsonPropertyName("processing")] + public bool Processing { get; set; } } /// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionHistoryCancelBackgroundCompactionRequest +internal sealed class SessionMetadataIsProcessingRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Indicates whether an in-progress manual compaction was aborted. +/// Current activity flags for the session. [Experimental(Diagnostics.Experimental)] -public sealed class HistoryAbortManualCompactionResult +public sealed class SessionActivity { - /// Whether an in-progress manual compaction was aborted. False when no manual compaction was running, when its abort controller was already aborted, or when the session is remote. - [JsonPropertyName("aborted")] - public bool Aborted { get; set; } + /// Whether an in-flight operation can currently be aborted. + [JsonPropertyName("abortable")] + public bool Abortable { get; set; } + + /// Whether the session currently has active work, including running turns or tasks. + [JsonPropertyName("hasActiveWork")] + public bool HasActiveWork { get; set; } } /// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionHistoryAbortManualCompactionRequest +internal sealed class SessionMetadataActivityRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Markdown summary of the conversation context (empty when not available). -[Experimental(Diagnostics.Experimental)] -public sealed class HistorySummarizeForHandoffResult +/// Token-usage breakdown for the session's current context window. +public sealed class MetadataContextInfoResultContextInfo { - /// Markdown summary of the conversation context produced by an LLM. Empty string when there are no messages or when the session does not support local summarization. - [JsonPropertyName("summary")] - public string Summary { get; set; } = string.Empty; -} + /// Output reserve plus tokens after the buffer-exhaustion blocking threshold (default 95%). + [JsonPropertyName("bufferTokens")] + public long BufferTokens { get; set; } -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionHistorySummarizeForHandoffRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Token count at which background compaction starts (configurable percentage of promptTokenLimit). + [JsonPropertyName("compactionThreshold")] + public long CompactionThreshold { get; set; } -/// User-facing pending queue entry, with kind and display text for a queued message, slash command, or model change. -[Experimental(Diagnostics.Experimental)] -public sealed class QueuePendingItems -{ - /// Human-readable text to display for this queue entry in the UI. - [JsonPropertyName("displayText")] - public string DisplayText { get; set; } = string.Empty; + /// Tokens consumed by user/assistant/tool messages. + [JsonPropertyName("conversationTokens")] + public long ConversationTokens { get; set; } - /// Whether this item is a queued user message or a queued slash command / model change. - [JsonPropertyName("kind")] - public QueuePendingItemsKind Kind { get; set; } + /// Prompt token limit plus the model's full output token limit. + [JsonPropertyName("limit")] + public long Limit { get; set; } + + /// Tokens consumed by MCP tool definitions (subset of toolDefinitionsTokens, excludes deferred tools). + [JsonPropertyName("mcpToolsTokens")] + public long McpToolsTokens { get; set; } + + /// The model used for token counting. + [JsonPropertyName("modelName")] + public string ModelName { get; set; } = string.Empty; + + /// Maximum prompt tokens allowed by the model (or DEFAULT_TOKEN_LIMIT if unspecified). + [JsonPropertyName("promptTokenLimit")] + public long PromptTokenLimit { get; set; } + + /// Tokens consumed by the system prompt. + [JsonPropertyName("systemTokens")] + public long SystemTokens { get; set; } + + /// Tokens consumed by tool definitions sent to the model (excludes deferred tools). + [JsonPropertyName("toolDefinitionsTokens")] + public long ToolDefinitionsTokens { get; set; } + + /// Sum of system, conversation and tool-definition tokens. + [JsonPropertyName("totalTokens")] + public long TotalTokens { get; set; } } -/// Snapshot of the session's pending queued items and immediate-steering messages. +/// Token breakdown for the session's current context window, or null if uninitialized. [Experimental(Diagnostics.Experimental)] -public sealed class QueuePendingItemsResult +public sealed class MetadataContextInfoResult { - /// Pending queued items in submission order. Includes user messages, queued slash commands, and queued model changes; omits internal system items. - [JsonPropertyName("items")] - public IList Items { get => field ??= []; set; } - - /// Display text for messages currently in the immediate steering queue (interjections sent during a running turn). - [JsonPropertyName("steeringMessages")] - public IList SteeringMessages { get => field ??= []; set; } + /// Token breakdown for the current context window, or null if the session has not yet been initialized (no system prompt or tool metadata cached). + [JsonPropertyName("contextInfo")] + public MetadataContextInfoResultContextInfo? ContextInfo { get; set; } } -/// Identifies the target session. +/// Model identifier and token limits used to compute the context-info breakdown. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionQueuePendingItemsRequest +internal sealed class MetadataContextInfoRequest { + /// Maximum output tokens allowed by the target model. Pass 0 if unknown. + [JsonPropertyName("outputTokenLimit")] + public long OutputTokenLimit { get; set; } + + /// Maximum prompt tokens allowed by the target model. Pass 0 to use the runtime default. + [JsonPropertyName("promptTokenLimit")] + public long PromptTokenLimit { get; set; } + + /// Model identifier used for tokenization. Omit to use the session default. Used both for token counting and to compute display values. + [JsonPropertyName("selectedModel")] + public string? SelectedModel { get; set; } + /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Indicates whether a user-facing pending item was removed. -[Experimental(Diagnostics.Experimental)] -public sealed class QueueRemoveMostRecentResult +/// The six normalized `/context` header buckets, computed from the same tokenization as `entries` so the two never disagree. Convenience rollups: `freeSpace` and `buffer` describe window capacity rather than occupied context, so the values do not sum to `totalTokens`. +public sealed class MetadataContextAttributionResultContextAttributionCategories { - /// True if a user-facing pending item was removed (LIFO across both queues); false when no removable items remained. - [JsonPropertyName("removed")] - public bool Removed { get; set; } -} + /// Output reserve plus post-blocking-threshold buffer. + [JsonPropertyName("buffer")] + public long Buffer { get; set; } -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionQueueRemoveMostRecentRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Custom-instructions tokens (0 when none are configured). + [JsonPropertyName("customInstructions")] + public long CustomInstructions { get; set; } + + /// Remaining unused window capacity (clamped at 0). + [JsonPropertyName("freeSpace")] + public long FreeSpace { get; set; } + + /// MCP tool-definition tokens. + [JsonPropertyName("mcpTools")] + public long McpTools { get; set; } + + /// Conversation (user/assistant/tool) message tokens. + [JsonPropertyName("messages")] + public long Messages { get; set; } + + /// System prompt tokens, excluding custom instructions. + [JsonPropertyName("systemPrompt")] + public long SystemPrompt { get; set; } + + /// Non-MCP tool-definition tokens. + [JsonPropertyName("systemTools")] + public long SystemTools { get; set; } } -/// Identifies the target session. -[Experimental(Diagnostics.Experimental)] -internal sealed class SessionQueueClearRequest +/// Successful compaction history for the session. +public sealed class MetadataContextAttributionResultContextAttributionCompactions { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Number of successful compactions in this session. + [JsonPropertyName("count")] + public long Count { get; set; } } -/// Batch of session events returned by a read, with cursor and continuation metadata. -[Experimental(Diagnostics.Experimental)] -public sealed class EventsReadResult +/// RPC data type for MetadataContextAttributionResultContextAttributionEntry operations. +public sealed class MetadataContextAttributionResultContextAttributionEntry { - /// Opaque cursor for the next read. Pass back unchanged in the next read.cursor to continue from where this read left off. Always present, even when no events were returned. - [JsonPropertyName("cursor")] - public string Cursor { get; set; } = string.Empty; + /// Supplementary per-entry metadata (e.g. `messageCount`, `role`, `evictable`, `pluginSource`). Values are stringified; parse as needed and ignore unrecognized keys. + [JsonPropertyName("attributes")] + public IDictionary? Attributes { get; set; } - /// Cursor status: 'ok' means the cursor was applied successfully; 'expired' means the cursor referred to an event that no longer exists in history (e.g. truncated or compacted away) and the read started from the beginning of the remaining history. - [JsonPropertyName("cursorStatus")] - public EventsCursorStatus CursorStatus { get; set; } + /// Identifier for this entry, formed by joining its `kind` and source name (e.g. `tool:bash`, `skill:tmux`, `toolDefinition:bash`); unique within the snapshot. Use it to match the same entry across snapshots, to correlate with other APIs (skill/agent/MCP registries), and as the `parentId` target for nesting. Distinct from the human-facing `label`. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; - /// Events are delivered in two batches per read: persisted events first (in append order), then ephemeral events (in seq order). When `waitMs > 0` and the catch-up batches were empty, post-wait events follow the same two-batch ordering. Persisted and ephemeral events do not interleave within a single read. - [JsonPropertyName("events")] - public IList Events { get => field ??= []; set; } + /// Source category for this entry. Not a closed set — tolerate unknown values. Known values today: `skill`, `subagent`, `mcpServer`, `tool`, `system`, `toolDefinition`, `plugin`. + [JsonPropertyName("kind")] + public string Kind { get; set; } = string.Empty; - /// True when the read returned `max` events and more events are available immediately. When false, the next read with a non-zero `waitMs` will block until a new event arrives or the wait expires. - [JsonPropertyName("hasMore")] - public bool HasMore { get; set; } + /// Human-readable display label, e.g. `bash` or `skill: tmux`. Presentation-only; may be localized/reformatted without notice — do not key off it. + [JsonPropertyName("label")] + public string Label { get; set; } = string.Empty; + + /// Optional `id` of the parent entry: e.g. a `plugin` entry parenting its `skill`/`mcpServer` entries, or the `system` entry parenting `toolDefinition` entries. Omitted for top-level entries. + [JsonPropertyName("parentId")] + public string? ParentId { get; set; } + + /// Token count currently in context attributable to this entry. + [JsonPropertyName("tokens")] + public long Tokens { get; set; } } -/// Cursor, batch size, and optional long-poll/filter parameters for reading session events. -[Experimental(Diagnostics.Experimental)] -internal sealed class EventLogReadRequest +/// Per-source token attribution snapshot for the current context window. The heaviest individual messages are available separately via `metadata.getContextHeaviestMessages`. +public sealed class MetadataContextAttributionResultContextAttribution { - /// Agent-scope filter: 'primary' returns only main-agent events plus events whose type starts with 'subagent.' (matching the typed-subscription default behavior); 'all' returns events from all agents (matching wildcard-subscription behavior). Default is 'all' to preserve wildcard semantics for catch-up callers. - [JsonPropertyName("agentScope")] - public EventsAgentScope? AgentScope { get; set; } + /// Output reserve plus the tokens past the buffer-exhaustion blocking threshold. Mirrors `SessionContextInfo.bufferTokens`. + [JsonPropertyName("bufferTokens")] + public long BufferTokens { get; set; } - /// Opaque cursor returned by a previous read. Omit on the first call to start from the beginning of the session's persisted history. - [JsonPropertyName("cursor")] - public string? Cursor { get; set; } + /// The six normalized `/context` header buckets, computed from the same tokenization as `entries` so the two never disagree. Convenience rollups: `freeSpace` and `buffer` describe window capacity rather than occupied context, so the values do not sum to `totalTokens`. + [JsonPropertyName("categories")] + public MetadataContextAttributionResultContextAttributionCategories Categories { get => field ??= new(); set; } - /// Maximum number of events to return in this batch (1–1000, default 200). - [JsonPropertyName("max")] - public long? Max { get; set; } + /// Successful compaction history for the session. + [JsonPropertyName("compactions")] + public MetadataContextAttributionResultContextAttributionCompactions Compactions { get => field ??= new(); set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Token count at which background compaction starts. Mirrors `SessionContextInfo.compactionThreshold`. + [JsonPropertyName("compactionThreshold")] + public long CompactionThreshold { get; set; } - /// Either '*' to receive all event types, or a non-empty list of event types to receive. - [JsonPropertyName("types")] - public JsonElement? Types { get; set; } + /// Flat list of per-source attribution entries. Group by `kind` and render unrecognized kinds generically. Nesting and rollups are expressed via `parentId`. + [JsonPropertyName("entries")] + public IList Entries { get => field ??= []; set; } - /// Milliseconds to wait for new events when the cursor is at the tail of history. 0 (default) returns immediately even if no events are available. Capped at 30000ms. Ephemeral events that arrive during the wait are delivered in this batch but are NOT replayable on a subsequent read (use a non-zero waitMs in your next call to capture future ephemerals as they happen). - [JsonConverter(typeof(MillisecondsTimeSpanConverter))] - [JsonPropertyName("waitMs")] - public TimeSpan? Wait { get; set; } + /// Prompt limit plus the model's output reserve: the full context window `categories.freeSpace` and `categories.buffer` are measured against. Mirrors `SessionContextInfo.limit`. + [JsonPropertyName("limit")] + public long Limit { get; set; } + + /// The concrete model id the entire breakdown was tokenized against (feeds the per-model token multiplier). Under `Auto` (Free/Student) this is the resolved model, not the literal `auto` sentinel, so totals are not undercounted. A single-model approximation of a potentially multi-model Auto session. + [JsonPropertyName("modelId")] + public string ModelId { get; set; } = string.Empty; + + /// How `modelId` was chosen. Not a closed set — tolerate unknown values. Known values today: `autoResolved` (the model Auto resolved to), `selected` (the user's explicitly selected model), `default` (a fallback before any model is known). + [JsonPropertyName("modelSource")] + public string ModelSource { get; set; } = string.Empty; + + /// Maximum prompt tokens the resolved model accepts — the denominator for a `##k/###k` context-usage display. Mirrors `SessionContextInfo.promptTokenLimit`. + [JsonPropertyName("promptTokenLimit")] + public long PromptTokenLimit { get; set; } + + /// Total token count of the current context window the entries are measured against (system message + conversation messages + tool definitions — the same total reported by /context). Divide an entry's `tokens` by this to derive its share. + [JsonPropertyName("totalTokens")] + public long TotalTokens { get; set; } } -/// Snapshot of the current tail cursor without returning any events. Use this when a consumer wants to subscribe to live events going forward without first paginating through the entire persisted history (which would happen if `read` were called without a cursor on a long-lived session). +/// Per-source attribution breakdown for the session's current context window, or null if uninitialized. [Experimental(Diagnostics.Experimental)] -public sealed class EventLogTailResult +public sealed class MetadataContextAttributionResult { - /// Opaque cursor pointing at the current tail of the session's persisted-events history. Pass back to `read` to receive only events that arrive AFTER this snapshot. When the session has no events, this returns the same sentinel as an unset cursor (i.e. equivalent to omitting the cursor on a first read). - [JsonPropertyName("cursor")] - public string Cursor { get; set; } = string.Empty; + /// Per-source context-window attribution, or null if the session has not yet been initialized (no system prompt or tool metadata cached). + [JsonPropertyName("contextAttribution")] + public MetadataContextAttributionResultContextAttribution? ContextAttribution { get; set; } } /// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionEventLogTailRequest +internal sealed class SessionMetadataGetContextAttributionRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Opaque handle representing an event-type interest registration. +/// A single large message currently in context. [Experimental(Diagnostics.Experimental)] -public sealed class RegisterEventInterestResult +public sealed class ContextHeaviestMessage { - /// Opaque handle for this registration. Pass to releaseInterest to release. Each call to registerInterest produces a fresh handle, even when the same eventType is registered multiple times. - [JsonPropertyName("handle")] - public string Handle { get; set; } = string.Empty; -} + /// Stable identifier for this message within the snapshot. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; -/// Event type to register consumer interest for, used by runtime gating logic. -[Experimental(Diagnostics.Experimental)] -internal sealed class RegisterEventInterestParams -{ - /// The event type the consumer wants the runtime to treat as 'observed' for behavior-switching gating. Some runtime code paths inspect whether any consumer is interested in a specific event type and choose a different implementation accordingly (e.g. `mcp.oauth_required`: when interest is registered the runtime delegates interactive OAuth token acquisition to the consumer via `mcp.oauth_required` events; when no interest is registered the runtime still attempts non-interactive reconnect from cached or refreshable tokens, and only marks the server `needs-auth` if usable credentials are unavailable — it does not open a browser or start interactive OAuth without a consumer). SDK clients that long-poll events do NOT automatically appear as listeners to these gating checks — they must explicitly call `registerInterest` for each event type they want the runtime to count as having a consumer. Multiple registrations for the same event type from the same or different consumers are tracked independently and must each be released. See: `mcp.oauth_required`, `sampling.requested`, `auto_mode_switch.requested`, `session_limits_exhausted.requested`, `user_input.requested`, `elicitation.requested`, `command.queued`, `exit_plan_mode.requested`. - [JsonPropertyName("eventType")] - public string EventType { get; set; } = string.Empty; + /// Human-readable source label, e.g. `tool: bash` or `skill: tmux`. Presentation-only. + [JsonPropertyName("label")] + public string Label { get; set; } = string.Empty; - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Role of the chat message (`user`, `assistant`, or `tool`). + [JsonPropertyName("role")] + public string Role { get; set; } = string.Empty; + + /// Token count currently in context for this individual message. + [JsonPropertyName("tokens")] + public long Tokens { get; set; } } -/// Indicates whether the operation succeeded. +/// The heaviest individual messages in the session's context window, most-expensive first. [Experimental(Diagnostics.Experimental)] -public sealed class EventLogReleaseInterestResult +public sealed class MetadataContextHeaviestMessagesResult { - /// Whether the operation succeeded. - [JsonPropertyName("success")] - public bool Success { get; set; } + /// Heaviest messages, most-expensive first. + [JsonPropertyName("messages")] + public IList Messages { get => field ??= []; set; } + + /// Total token count of the current context window, so callers can compute each message's share without a second call. + [JsonPropertyName("totalTokens")] + public long TotalTokens { get; set; } } -/// Opaque handle previously returned by `registerInterest` to release. +/// Parameters for the heaviest-messages query. [Experimental(Diagnostics.Experimental)] -internal sealed class ReleaseEventInterestParams +internal sealed class MetadataContextHeaviestMessagesRequest { - /// Handle returned by a previous `registerInterest` call. Idempotent: releasing an unknown or already-released handle is a no-op (returns success). When the last outstanding handle for an event type is released, the runtime reverts to its 'no consumer' code path for that event type. - [JsonPropertyName("handle")] - public string Handle { get; set; } = string.Empty; + /// Maximum number of messages to return, most-expensive first. Omit for the server default. + [JsonPropertyName("limit")] + public long? Limit { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Aggregated code change metrics. +/// Notify the session that its working directory context has changed. Emits a `session.context_changed` event so consumers (telemetry, OTel tracker, ACP, the timeline UI) can react. Use this when the host has detected a cwd/branch/repo change outside the session's normal lifecycle (e.g., after a shell command in interactive mode). For a local session, a report whose `cwd` diverges from the session's current working directory is ignored (the call still succeeds but records nothing and emits no event); move a local session's working directory via `metadata.setWorkingDirectory` instead. [Experimental(Diagnostics.Experimental)] -public sealed class UsageMetricsCodeChanges +public sealed class MetadataRecordContextChangeResult { - /// Distinct file paths modified during the session. - [JsonPropertyName("filesModified")] - public IList FilesModified { get => field ??= []; set; } - - /// Number of distinct files modified. - [JsonPropertyName("filesModifiedCount")] - public long FilesModifiedCount { get; set; } - - /// Total lines of code added. - [JsonPropertyName("linesAdded")] - public long LinesAdded { get; set; } - - /// Total lines of code removed. - [JsonPropertyName("linesRemoved")] - public long LinesRemoved { get; set; } } -/// Request count and cost metrics for this model. +/// Updated working directory and git context. Emitted as the new payload of `session.context_changed`. [Experimental(Diagnostics.Experimental)] -public sealed class UsageMetricsModelMetricRequests +public sealed class SessionWorkingDirectoryContext { - /// User-initiated premium request cost (with multiplier applied). - [JsonPropertyName("cost")] - public double Cost { get; set; } + /// Merge-base commit SHA (fork point from the remote default branch). + [JsonPropertyName("baseCommit")] + public string? BaseCommit { get; set; } - /// Number of API requests made with this model. - [JsonPropertyName("count")] - public long Count { get; set; } -} + /// Current git branch name. + [JsonPropertyName("branch")] + public string? Branch { get; set; } -/// Per-model token-detail entry containing the accumulated token count for one token type. -[Experimental(Diagnostics.Experimental)] -public sealed class UsageMetricsModelMetricTokenDetail -{ - /// Accumulated token count for this token type. - [JsonPropertyName("tokenCount")] - public long TokenCount { get; set; } -} + /// Current working directory path. + [JsonPropertyName("cwd")] + public string Cwd { get; set; } = string.Empty; -/// Token usage metrics for this model. -[Experimental(Diagnostics.Experimental)] -public sealed class UsageMetricsModelMetricUsage -{ - /// Total tokens read from prompt cache. - [JsonPropertyName("cacheReadTokens")] - public long CacheReadTokens { get; set; } + /// Root directory of the git repository, resolved via git rev-parse. + [JsonPropertyName("gitRoot")] + public string? GitRoot { get; set; } - /// Total tokens written to prompt cache. - [JsonPropertyName("cacheWriteTokens")] - public long CacheWriteTokens { get; set; } + /// Head commit of the current git branch. + [JsonPropertyName("headCommit")] + public string? HeadCommit { get; set; } - /// Total input tokens consumed. - [JsonPropertyName("inputTokens")] - public long InputTokens { get; set; } + /// Hosting platform type of the repository. + [JsonPropertyName("hostType")] + public SessionWorkingDirectoryContextHostType? HostType { get; set; } - /// Total output tokens produced. - [JsonPropertyName("outputTokens")] - public long OutputTokens { get; set; } + /// Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps). + [JsonPropertyName("repository")] + public string? Repository { get; set; } - /// Total output tokens used for reasoning. - [JsonPropertyName("reasoningTokens")] - public long? ReasoningTokens { get; set; } + /// Raw host string from the git remote URL (e.g. "github.com", "dev.azure.com"). + [JsonPropertyName("repositoryHost")] + public string? RepositoryHost { get; set; } } -/// Per-model usage metrics, including request counts/costs, token usage, nano-AI units, and per-token-type details. +/// Updated working-directory/git context to record on the session. [Experimental(Diagnostics.Experimental)] -public sealed class UsageMetricsModelMetric +internal sealed class MetadataRecordContextChangeRequest { - /// Latest known prompt-cache expiration for this model. A timestamp in the past indicates that the observed cache has expired. - [JsonPropertyName("cacheExpiresAt")] - public DateTimeOffset? CacheExpiresAt { get; set; } - - /// Request count and cost metrics for this model. - [JsonPropertyName("requests")] - public UsageMetricsModelMetricRequests Requests { get => field ??= new(); set; } - - /// Token count details per type. - [JsonPropertyName("tokenDetails")] - public IDictionary? TokenDetails { get; set; } - - /// Accumulated nano-AI units cost for this model. - [JsonPropertyName("totalNanoAiu")] - public double? TotalNanoAiu { get; set; } + /// Updated working directory and git context. Emitted as the new payload of `session.context_changed`. + [JsonPropertyName("context")] + public SessionWorkingDirectoryContext Context { get => field ??= new(); set; } - /// Token usage metrics for this model. - [JsonPropertyName("usage")] - public UsageMetricsModelMetricUsage Usage { get => field ??= new(); set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Session-wide token-detail entry containing the accumulated token count for one token type. +/// Update the session's working directory. Used by the host when the user explicitly changes cwd (e.g., the `/cd` slash command). The host is responsible for any related side-effects (file index, etc.); it does NOT change the process working directory (a session's cwd is per-session, not process-global). For local sessions the runtime validates the target first (an absolute path that exists on disk) and re-bases the permission primary directory; a rejected validation fails the call before anything is mutated, persisted, or emitted. Location-scoped permission rules are then re-keyed to the new directory (best-effort). Remote sessions only record the path. [Experimental(Diagnostics.Experimental)] -public sealed class UsageMetricsTokenDetail +public sealed class MetadataSetWorkingDirectoryResult { - /// Accumulated token count for this token type. - [JsonPropertyName("tokenCount")] - public long TokenCount { get; set; } + /// Working directory after the update. + [JsonPropertyName("workingDirectory")] + public string WorkingDirectory { get; set; } = string.Empty; } -/// Accumulated session usage metrics, including premium request cost, token counts, model breakdown, and code-change totals. +/// Absolute path to set as the session's new working directory. For local sessions the path must be absolute and exist on disk: it is validated before any session state changes, and a failing validation rejects the call with nothing mutated, persisted, or emitted. Remote sessions record the path as-is. [Experimental(Diagnostics.Experimental)] -public sealed class UsageGetMetricsResult +internal sealed class MetadataSetWorkingDirectoryRequest { - /// Aggregated code change metrics. - [JsonPropertyName("codeChanges")] - public UsageMetricsCodeChanges CodeChanges { get => field ??= new(); set; } - - /// Currently active model identifier. - [JsonPropertyName("currentModel")] - public string? CurrentModel { get; set; } - - /// Input tokens from the most recent main-agent API call. - [JsonPropertyName("lastCallInputTokens")] - public long LastCallInputTokens { get; set; } - - /// Output tokens from the most recent main-agent API call. - [JsonPropertyName("lastCallOutputTokens")] - public long LastCallOutputTokens { get; set; } - - /// Per-model token and request metrics, keyed by model identifier. - [JsonPropertyName("modelMetrics")] - public IDictionary ModelMetrics { get => field ??= new Dictionary(); set; } - - /// ISO 8601 timestamp when the session started. - [JsonPropertyName("sessionStartTime")] - public DateTimeOffset SessionStartTime { get; set; } - - /// Session-wide per-token-type accumulated token counts. - [JsonPropertyName("tokenDetails")] - public IDictionary? TokenDetails { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; - /// Total time spent in model API calls (milliseconds). - [JsonConverter(typeof(MillisecondsTimeSpanConverter))] - [JsonPropertyName("totalApiDurationMs")] - public TimeSpan TotalApiDuration { get; set; } + /// Absolute path to set as the session's working directory. The runtime updates the session's recorded cwd so subsequent operations (shell tools, file lookups, telemetry) anchor to it. + [JsonPropertyName("workingDirectory")] + public string WorkingDirectory { get; set; } = string.Empty; +} - /// Session-wide accumulated nano-AI units cost. - [JsonPropertyName("totalNanoAiu")] - public double? TotalNanoAiu { get; set; } +/// Re-tokenize the session's existing messages against `modelId` and return the token totals. Useful for hosts that want an initial estimate of context usage on session resume, before the next agent turn fires `session.context_info_changed` events. Returns zeros for an empty session. +[Experimental(Diagnostics.Experimental)] +public sealed class MetadataRecomputeContextTokensResult +{ + /// Tokens contributed by user/assistant/tool messages (excludes system/developer prompts). + [JsonPropertyName("messagesTokenCount")] + public long MessagesTokenCount { get; set; } - /// Total user-initiated premium request cost across all models (may be fractional due to multipliers). - [JsonPropertyName("totalPremiumRequestCost")] - public double TotalPremiumRequestCost { get; set; } + /// Tokens contributed by system/developer prompt snapshots. + [JsonPropertyName("systemTokenCount")] + public long SystemTokenCount { get; set; } - /// Raw count of user-initiated API requests. - [JsonPropertyName("totalUserRequests")] - public long TotalUserRequests { get; set; } + /// Sum of tokens across chat-context and system-context messages currently held by the session. + [JsonPropertyName("totalTokens")] + public long TotalTokens { get; set; } } -/// Identifies the target session. +/// Model identifier to use when re-tokenizing the session's existing messages. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionUsageGetMetricsRequest +internal sealed class MetadataRecomputeContextTokensRequest { + /// Model identifier used for tokenization. The runtime token-counts both chat-context and system-context messages against this model. + [JsonPropertyName("modelId")] + public string ModelId { get; set; } = string.Empty; + /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// GitHub URL for the session and a flag indicating whether remote steering is enabled. +/// Availability of built-in job tools surfaced to boundary consumers. [Experimental(Diagnostics.Experimental)] -public sealed class RemoteEnableResult +public sealed class SessionSettingsBuiltInToolAvailabilitySnapshot { - /// Whether remote steering is enabled. - [JsonPropertyName("remoteSteerable")] - public bool RemoteSteerable { get; set; } + /// Gets or sets the createPullRequest value. + [JsonPropertyName("createPullRequest")] + public bool? CreatePullRequest { get; set; } - /// GitHub frontend URL for this session. - [Url] - [StringSyntax(StringSyntaxAttribute.Uri)] - [JsonPropertyName("url")] - public string? Url { get; set; } + /// Gets or sets the reportProgress value. + [JsonPropertyName("reportProgress")] + public bool? ReportProgress { get; set; } } -/// Optional remote session mode ("off", "export", or "on"); defaults to enabling both export and remote steering. +/// Redacted job settings for a session. The job nonce is excluded. [Experimental(Diagnostics.Experimental)] -internal sealed class RemoteEnableRequest +public sealed class SessionSettingsJobSnapshot { - /// Per-session remote mode. "off" disables remote, "export" exports session events to GitHub without enabling remote steering, "on" enables both export and remote steering. - [JsonPropertyName("mode")] - public RemoteSessionMode? Mode { get; set; } + /// Gets or sets the builtInToolAvailability value. + [JsonPropertyName("builtInToolAvailability")] + public SessionSettingsBuiltInToolAvailabilitySnapshot? BuiltInToolAvailability { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Gets or sets the eventType value. + [JsonPropertyName("eventType")] + public string? EventType { get; set; } + + /// Gets or sets the isTriggerJob value. + [JsonPropertyName("isTriggerJob")] + public bool? IsTriggerJob { get; set; } } -/// Identifies the target session. +/// Redacted model routing settings for a session. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionRemoteDisableRequest +public sealed class SessionSettingsModelSnapshot { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Gets or sets the callbackUrl value. + [JsonPropertyName("callbackUrl")] + public string? CallbackUrl { get; set; } + + /// Gets or sets the defaultReasoningEffort value. + [JsonPropertyName("defaultReasoningEffort")] + public string? DefaultReasoningEffort { get; set; } + + /// Gets or sets the instanceId value. + [JsonPropertyName("instanceId")] + public string? InstanceId { get; set; } + + /// Gets or sets the model value. + [JsonPropertyName("model")] + public string? Model { get; set; } } -/// Persist a steerability change as a `session.remote_steerable_changed` event. Used by the host (CLI / SDK consumer) when it has just finished enabling or disabling steering on a remote exporter that the runtime does not directly own. +/// Online-evaluation settings safe to expose across the SDK boundary. [Experimental(Diagnostics.Experimental)] -public sealed class RemoteNotifySteerableChangedResult +public sealed class SessionSettingsOnlineEvaluationSnapshot { + /// Gets or sets the disableOnlineEvaluation value. + [JsonPropertyName("disableOnlineEvaluation")] + public bool? DisableOnlineEvaluation { get; set; } + + /// Gets or sets the enableOnlineEvaluationOutputFile value. + [JsonPropertyName("enableOnlineEvaluationOutputFile")] + public bool? EnableOnlineEvaluationOutputFile { get; set; } } -/// New remote-steerability state to persist as a `session.remote_steerable_changed` event. +/// Redacted repository and GitHub host settings for a session. [Experimental(Diagnostics.Experimental)] -internal sealed class RemoteNotifySteerableChangedRequest +public sealed class SessionSettingsRepoSnapshot { - /// Whether the session now supports remote steering via GitHub. The runtime persists this as a `session.remote_steerable_changed` event so resume/replay sees the up-to-date capability. - [JsonPropertyName("remoteSteerable")] - public bool RemoteSteerable { get; set; } + /// Gets or sets the branch value. + [JsonPropertyName("branch")] + public string? Branch { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Gets or sets the commit value. + [JsonPropertyName("commit")] + public string? Commit { get; set; } + + /// Gets or sets the host value. + [JsonPropertyName("host")] + public string? Host { get; set; } + + /// Gets or sets the hostProtocol value. + [JsonPropertyName("hostProtocol")] + public string? HostProtocol { get; set; } + + /// Gets or sets the id value. + [JsonPropertyName("id")] + public double? Id { get; set; } + + /// Gets or sets the name value. + [JsonPropertyName("name")] + public string? Name { get; set; } + + /// Gets or sets the ownerId value. + [JsonPropertyName("ownerId")] + public double? OwnerId { get; set; } + + /// Gets or sets the ownerName value. + [JsonPropertyName("ownerName")] + public string? OwnerName { get; set; } + + /// Gets or sets the prCommitCount value. + [JsonPropertyName("prCommitCount")] + public double? PrCommitCount { get; set; } + + /// Gets or sets the readWrite value. + [JsonPropertyName("readWrite")] + public bool? ReadWrite { get; set; } + + /// Gets or sets the secretScanningUrl value. + [JsonPropertyName("secretScanningUrl")] + public string? SecretScanningUrl { get; set; } + + /// Gets or sets the serverUrl value. + [JsonPropertyName("serverUrl")] + public string? ServerUrl { get; set; } } -/// Current sharing status and shareable GitHub URL for a session. +/// Redacted validation and memory-tool settings for a session. [Experimental(Diagnostics.Experimental)] -public sealed class VisibilityGetResult +public sealed class SessionSettingsValidationSnapshot { - /// Shareable GitHub URL for the session. Present when the session is synced and the URL can be resolved. - [Url] - [StringSyntax(StringSyntaxAttribute.Uri)] - [JsonPropertyName("shareUrl")] - public string? ShareUrl { get; set; } + /// Gets or sets the advisoryEnabled value. + [JsonPropertyName("advisoryEnabled")] + public bool? AdvisoryEnabled { get; set; } - /// Current sharing status. Absent when the session is not synced or the status could not be retrieved (e.g. the user is not authenticated). - [JsonPropertyName("status")] - public SessionVisibilityStatus? Status { get; set; } + /// Gets or sets the codeqlEnabled value. + [JsonPropertyName("codeqlEnabled")] + public bool? CodeqlEnabled { get; set; } - /// Whether the session has been synced to Mission Control (i.e. has a GitHub task). When false, the session cannot be shared and `status`/`shareUrl` are absent. - [JsonPropertyName("synced")] - public bool Synced { get; set; } + /// Gets or sets the codeReviewEnabled value. + [JsonPropertyName("codeReviewEnabled")] + public bool? CodeReviewEnabled { get; set; } + + /// Gets or sets the codeReviewModel value. + [JsonPropertyName("codeReviewModel")] + public string? CodeReviewModel { get; set; } + + /// Gets or sets the dependabotTimeout value. + [JsonPropertyName("dependabotTimeout")] + public double? DependabotTimeout { get; set; } + + /// Gets or sets the memoryStoreEnabled value. + [JsonPropertyName("memoryStoreEnabled")] + public bool? MemoryStoreEnabled { get; set; } + + /// Gets or sets the memoryVoteEnabled value. + [JsonPropertyName("memoryVoteEnabled")] + public bool? MemoryVoteEnabled { get; set; } + + /// Gets or sets the secretScanningEnabled value. + [JsonPropertyName("secretScanningEnabled")] + public bool? SecretScanningEnabled { get; set; } + + /// Gets or sets the timeout value. + [JsonPropertyName("timeout")] + public double? Timeout { get; set; } +} + +/// Redacted, serializable view of session runtime settings for SDK boundary consumers. Secrets and raw feature flags are intentionally excluded. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionSettingsSnapshot +{ + /// Gets or sets the clientName value. + [JsonPropertyName("clientName")] + public string? ClientName { get; set; } + + /// Gets or sets the job value. + [JsonPropertyName("job")] + public SessionSettingsJobSnapshot Job { get => field ??= new(); set; } + + /// Gets or sets the model value. + [JsonPropertyName("model")] + public SessionSettingsModelSnapshot Model { get => field ??= new(); set; } + + /// Gets or sets the onlineEvaluation value. + [JsonPropertyName("onlineEvaluation")] + public SessionSettingsOnlineEvaluationSnapshot OnlineEvaluation { get => field ??= new(); set; } + + /// Gets or sets the repo value. + [JsonPropertyName("repo")] + public SessionSettingsRepoSnapshot Repo { get => field ??= new(); set; } + + /// Gets or sets the startTimeMs value. + [JsonPropertyName("startTimeMs")] + public double? StartTimeMs { get; set; } + + /// Gets or sets the timeoutMs value. + [JsonPropertyName("timeoutMs")] + public double? TimeoutMs { get; set; } + + /// Gets or sets the validation value. + [JsonPropertyName("validation")] + public SessionSettingsValidationSnapshot Validation { get => field ??= new(); set; } + + /// Gets or sets the version value. + [JsonPropertyName("version")] + public string? Version { get; set; } } /// Identifies the target session. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionVisibilityGetRequest +internal sealed class SessionSettingsSnapshotRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Effective sharing status and shareable GitHub URL after updating session visibility. +/// Result of evaluating a Rust-owned settings predicate. [Experimental(Diagnostics.Experimental)] -public sealed class VisibilitySetResult +internal sealed class SessionSettingsEvaluatePredicateResult { - /// Shareable GitHub URL for the session. Present when the session is synced and the URL can be resolved. - [Url] - [StringSyntax(StringSyntaxAttribute.Uri)] - [JsonPropertyName("shareUrl")] - public string? ShareUrl { get; set; } - - /// Effective sharing status after the update. May differ from the requested status for task types that are already visible to repository readers by default. Absent when the update could not be applied (e.g. the session is not synced or the user is not authenticated). - [JsonPropertyName("status")] - public SessionVisibilityStatus? Status { get; set; } - - /// Whether the session has been synced to Mission Control (i.e. has a GitHub task). When false, the visibility change could not be applied and `status`/`shareUrl` are absent. - [JsonPropertyName("synced")] - public bool Synced { get; set; } + /// Gets or sets the enabled value. + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } } -/// Desired sharing status for the session. +/// Named Rust-owned settings predicate to evaluate for this session. [Experimental(Diagnostics.Experimental)] -internal sealed class VisibilitySetRequest +internal sealed class SessionSettingsEvaluatePredicateRequest { + /// Predicate name. The runtime owns the raw feature-flag names and composition logic. + [JsonPropertyName("name")] + public SessionSettingsPredicateName Name { get; set; } + /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; - /// Sharing status to apply. "repo" makes the session visible to repository readers; "unshared" restricts it to the creator and collaborators. - [JsonPropertyName("status")] - public SessionVisibilityStatus Status { get; set; } + /// Tool name for tool-scoped predicates such as trivial-change handling. + [JsonPropertyName("toolName")] + public string? ToolName { get; set; } } -/// Scheduled prompt entry with ID, timing (`intervalMs`, `cron`, or `at`), prompt text, recurrence, and next run time. +/// Content-exclusion decision for one requested path. [Experimental(Diagnostics.Experimental)] -public sealed class ScheduleEntry +public sealed class ContentExclusionPathCheck { - /// Absolute fire time (epoch milliseconds) for a one-shot calendar schedule. - [JsonPropertyName("at")] - public long? At { get; set; } - - /// 5-field cron expression for a recurring calendar schedule, evaluated in `tz`. - [JsonPropertyName("cron")] - public string? Cron { get; set; } + /// Whether the session's complete content-exclusion policy excludes the path. + [JsonPropertyName("excluded")] + public bool Excluded { get; set; } - /// Display-only label for the prompt as shown in the UI (e.g. `/skill-name` for a skill-invocation schedule). The actual enqueued prompt is `prompt`. - [JsonPropertyName("displayPrompt")] - public string? DisplayPrompt { get; set; } + /// The path supplied by the caller. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; +} - /// Sequential id assigned by the runtime within the session. Stable across resumes (rebuilt from the event log). - [JsonPropertyName("id")] - public long Id { get; set; } - - /// Interval between scheduled ticks, in milliseconds (relative-interval schedules). - [JsonConverter(typeof(MillisecondsTimeSpanConverter))] - [JsonPropertyName("intervalMs")] - public TimeSpan? Interval { get; set; } - - /// ISO 8601 timestamp when the next tick is scheduled to fire. - [JsonPropertyName("nextRunAt")] - public DateTimeOffset NextRunAt { get; set; } - - /// Prompt text that gets enqueued on every tick. - [JsonPropertyName("prompt")] - public string Prompt { get; set; } = string.Empty; - - /// Whether the schedule re-arms after each tick (`/every`) or fires once (`/after`). - [JsonPropertyName("recurring")] - public bool Recurring { get; set; } - - /// True for a self-paced (`dynamic`) schedule: no fixed cadence; the model arms each next run via the `manage_schedule` `wakeup` action. `nextRunAt` is model-controlled. - [JsonPropertyName("selfPaced")] - public bool? SelfPaced { get; set; } - - /// IANA timezone the `cron` expression is evaluated in. - [JsonPropertyName("tz")] - public string? Tz { get; set; } -} - -/// Snapshot of the currently active recurring prompts for this session. +/// Batch content-exclusion result. Callers must fail closed when policy evaluation is unavailable. [Experimental(Diagnostics.Experimental)] -public sealed class ScheduleList +public sealed class ContentExclusionCheckPathsResult { - /// Active scheduled prompts, ordered by id. - [JsonPropertyName("entries")] - public IList Entries { get => field ??= []; set; } + /// Whether the session's policy service was available for the complete batch. When false, checks is empty and callers must treat every requested path as excluded. + [JsonPropertyName("available")] + public bool Available { get; set; } + + /// Per-path decisions in request order. Empty when available is false. + [JsonPropertyName("checks")] + public IList Checks { get => field ??= []; set; } } -/// Identifies the target session. +/// Local file system absolute paths within the session working directory to check against its content-exclusion policy. [Experimental(Diagnostics.Experimental)] -internal sealed class SessionScheduleListRequest +internal sealed class ContentExclusionCheckPathsRequest { + /// Local file system absolute paths within the session working directory to check. Results are returned in the same order, including duplicates. + [JsonPropertyName("paths")] + public IList Paths { get => field ??= []; set; } + /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Remove a scheduled prompt by id. The result entry is omitted if the id was unknown. +/// Identifier of the spawned process, used to correlate streamed output and exit notifications. [Experimental(Diagnostics.Experimental)] -public sealed class ScheduleStopResult +public sealed class ShellExecResult { - /// The removed entry, or omitted if no entry matched. - [JsonPropertyName("entry")] - public ScheduleEntry? Entry { get; set; } + /// Unique identifier for tracking streamed output. + [JsonPropertyName("processId")] + public string ProcessId { get; set; } = string.Empty; } -/// Identifier of the scheduled prompt to remove. +/// Shell command to run, with optional working directory and timeout in milliseconds. [Experimental(Diagnostics.Experimental)] -internal sealed class ScheduleStopRequest +internal sealed class ShellExecRequest { - /// Id of the scheduled prompt to remove. - [JsonPropertyName("id")] - public long Id { get; set; } + /// Shell command to execute. + [JsonPropertyName("command")] + public string Command { get; set; } = string.Empty; + + /// Working directory (defaults to session working directory). + [JsonPropertyName("cwd")] + public string? Cwd { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; + + /// Timeout in milliseconds (default: 30000). + [JsonConverter(typeof(MillisecondsTimeSpanConverter))] + [JsonPropertyName("timeout")] + public TimeSpan? Timeout { get; set; } } -/// A bearer token supplied by the SDK client for a BYOK provider. The runtime sets it as `Authorization: Bearer <token>` on the outbound request and does no caching; the SDK consumer owns token caching and refresh. +/// Indicates whether the signal was delivered; false if the process was unknown or already exited. [Experimental(Diagnostics.Experimental)] -public sealed class ProviderTokenAcquireResult +public sealed class ShellKillResult { - /// The bearer token value (without the `Bearer ` prefix). - [JsonPropertyName("token")] - public string Token { get; set; } = string.Empty; + /// Whether the signal was sent successfully. + [JsonPropertyName("killed")] + public bool Killed { get; set; } } -/// Asks the SDK client to acquire a bearer token for a BYOK provider whose config set `hasBearerTokenProvider: true`. Issued by the runtime before each outbound model request; the runtime does no caching, so this is sent once per request. +/// Identifier of a process previously returned by "shell.exec" and the signal to send. [Experimental(Diagnostics.Experimental)] -public sealed class ProviderTokenAcquireRequest +internal sealed class ShellKillRequest { - /// Name of the BYOK provider needing a token. For the legacy whole-session `provider` this is the implicit provider name; for named providers it is `NamedProviderConfig.name`. - [JsonPropertyName("providerName")] - public string ProviderName { get; set; } = string.Empty; + /// Process identifier returned by shell.exec. + [JsonPropertyName("processId")] + public string ProcessId { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; + + /// Signal to send (default: SIGTERM). + [JsonPropertyName("signal")] + public ShellKillSignal? Signal { get; set; } } -/// Result returned by an extension factory closure. +/// Result of a user-requested shell command. [Experimental(Diagnostics.Experimental)] -public sealed class FactoryExecuteResult +public sealed class UserRequestedShellCommandResult { - /// Factory result value. - [JsonPropertyName("result")] - public JsonElement Result { get; set; } + /// Error output when the execution failed. + [JsonPropertyName("error")] + public string? Error { get; set; } + + /// Process exit code, when available. + [JsonPropertyName("exitCode")] + public long? ExitCode { get; set; } + + /// Captured command output. + [JsonPropertyName("output")] + public string Output { get; set; } = string.Empty; + + /// Whether the command completed successfully. + [JsonPropertyName("success")] + public bool Success { get; set; } + + /// Tool call id emitted for the shell execution. + [JsonPropertyName("toolCallId")] + public string ToolCallId { get; set; } = string.Empty; } -/// Parameters sent to the owning extension to execute a factory closure. +/// User-requested shell command and cancellation handle. [Experimental(Diagnostics.Experimental)] -public sealed class FactoryExecuteRequest +internal sealed class ShellExecuteUserRequestedRequest { - /// Factory input value. - [JsonPropertyName("args")] - public JsonElement Args { get; set; } - - /// Registered factory name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Shell command to execute. + [JsonPropertyName("command")] + public string Command { get; set; } = string.Empty; - /// Factory run identifier. - [JsonPropertyName("runId")] - public string RunId { get; set; } = string.Empty; + /// Caller-provided cancellation handle for this execution. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Parameters for cooperatively aborting a factory body. +/// Cancellation result for a user-requested shell command. [Experimental(Diagnostics.Experimental)] -public sealed class FactoryAbortRequest +public sealed class CancelUserRequestedShellCommandResult { - /// Factory run identifier. - [JsonPropertyName("runId")] - public string RunId { get; set; } = string.Empty; + /// Whether an in-flight execution was found and signalled to cancel. + [JsonPropertyName("cancelled")] + public bool Cancelled { get; set; } +} + +/// User-requested shell execution cancellation handle. +[Experimental(Diagnostics.Experimental)] +internal sealed class ShellCancelUserRequestedRequest +{ + /// Request ID previously passed to executeUserRequested. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Describes a filesystem error. +/// Post-compaction context window usage breakdown. [Experimental(Diagnostics.Experimental)] -public sealed class SessionFsError +public sealed class HistoryCompactContextWindow { - /// Error classification. - [JsonPropertyName("code")] - public SessionFsErrorCode Code { get; set; } + /// Token count from non-system messages (user, assistant, tool). + [JsonPropertyName("conversationTokens")] + public long? ConversationTokens { get; set; } - /// Free-form detail about the error, for logging/diagnostics. - [JsonPropertyName("message")] - public string? Message { get; set; } + /// Current total tokens in the context window (system + conversation + tool definitions). + [JsonPropertyName("currentTokens")] + public long CurrentTokens { get; set; } + + /// Current number of messages in the conversation. + [JsonPropertyName("messagesLength")] + public long MessagesLength { get; set; } + + /// Token count from system message(s). + [JsonPropertyName("systemTokens")] + public long? SystemTokens { get; set; } + + /// Maximum token count for the model's context window. + [JsonPropertyName("tokenLimit")] + public long TokenLimit { get; set; } + + /// Token count from tool definitions. + [JsonPropertyName("toolDefinitionsTokens")] + public long? ToolDefinitionsTokens { get; set; } } -/// File content as a UTF-8 string, or a filesystem error if the read failed. +/// Compaction outcome with the number of tokens and messages removed, summary text, and the resulting context window breakdown. [Experimental(Diagnostics.Experimental)] -public sealed class SessionFsReadFileResult +public sealed class HistoryCompactResult { - /// File content as UTF-8 string. - [JsonPropertyName("content")] - public string Content { get; set; } = string.Empty; + /// Post-compaction context window usage breakdown. + [JsonPropertyName("contextWindow")] + public HistoryCompactContextWindow? ContextWindow { get; set; } - /// Describes a filesystem error. - [JsonPropertyName("error")] - public SessionFsError? Error { get; set; } + /// Number of messages removed during compaction. + [JsonPropertyName("messagesRemoved")] + public long MessagesRemoved { get; set; } + + /// Whether compaction completed successfully. + [JsonPropertyName("success")] + public bool Success { get; set; } + + /// Summary text produced by compaction. Omitted when compaction did not produce a summary (e.g. failure path). + [JsonPropertyName("summaryContent")] + public string? SummaryContent { get; set; } + + /// Number of tokens freed by compaction. + [JsonPropertyName("tokensRemoved")] + public long TokensRemoved { get; set; } } -/// Path of the file to read from the client-provided session filesystem. +/// RPC data type for SessionHistoryCompact operations. [Experimental(Diagnostics.Experimental)] -public sealed class SessionFsReadFileRequest +public sealed class SessionHistoryCompactRequest { - /// Path using SessionFs conventions. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// Optional user-provided instructions to focus the compaction summary. + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] + [MaxLength(4000)] + [JsonPropertyName("customInstructions")] + public string? CustomInstructions { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Context window token limit this compaction is targeting, recorded as the `tokenLimit` on the persisted `session.compaction_start` / `session.compaction_complete` events. Set it when the compaction targets a window other than the compacting model's own, e.g. switching to a model with a smaller context window: the compaction still runs on the current model, so the limit that motivated it would otherwise be lost. When absent, the events record the compacting model's own resolved limit. Attribution metadata only - it does not change how much the compaction removes. + [JsonPropertyName("tokenLimit")] + public long? TokenLimit { get; set; } + + /// What initiated this compaction request, recorded as the `trigger` on the persisted `session.compaction_start` / `session.compaction_complete` events. When absent, the compaction is persisted without trigger attribution (initiator unknown). + [JsonPropertyName("trigger")] + public SessionHistoryCompactRequestTrigger? Trigger { get; set; } } -/// File path, content to write, and optional mode for the client-provided session filesystem. +/// RPC data type for SessionHistoryCompactRequestWithSession operations. [Experimental(Diagnostics.Experimental)] -public sealed class SessionFsWriteFileRequest +internal sealed class SessionHistoryCompactRequestWithSession { - /// Content to write. - [JsonPropertyName("content")] - public string Content { get; set; } = string.Empty; - - /// Optional POSIX-style mode for newly created files. - [JsonPropertyName("mode")] - public long? Mode { get; set; } - - /// Path using SessionFs conventions. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// Optional user-provided instructions to focus the compaction summary. + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] + [MaxLength(4000)] + [JsonPropertyName("customInstructions")] + public string? CustomInstructions { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; + + /// Context window token limit this compaction is targeting, recorded as the `tokenLimit` on the persisted `session.compaction_start` / `session.compaction_complete` events. Set it when the compaction targets a window other than the compacting model's own, e.g. switching to a model with a smaller context window: the compaction still runs on the current model, so the limit that motivated it would otherwise be lost. When absent, the events record the compacting model's own resolved limit. Attribution metadata only - it does not change how much the compaction removes. + [JsonPropertyName("tokenLimit")] + public long? TokenLimit { get; set; } + + /// What initiated this compaction request, recorded as the `trigger` on the persisted `session.compaction_start` / `session.compaction_complete` events. When absent, the compaction is persisted without trigger attribution (initiator unknown). + [JsonPropertyName("trigger")] + public SessionHistoryCompactRequestTrigger? Trigger { get; set; } } -/// File path, content to append, and optional mode for the client-provided session filesystem. +/// Number of events that were removed by the truncation. [Experimental(Diagnostics.Experimental)] -public sealed class SessionFsAppendFileRequest +public sealed class HistoryTruncateResult { - /// Content to append. - [JsonPropertyName("content")] - public string Content { get; set; } = string.Empty; - - /// Optional POSIX-style mode for newly created files. - [JsonPropertyName("mode")] - public long? Mode { get; set; } - - /// Path using SessionFs conventions. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// Failure detail when checkpointCleanupFailed is true. + [JsonPropertyName("checkpointCleanupError")] + public string? CheckpointCleanupError { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// True when conversation truncation succeeded but post-truncation workspace checkpoint cleanup failed. History is already truncated; callers may still prune snapshots but should report a checkpoint-cleanup rather than a truncation failure. + [JsonPropertyName("checkpointCleanupFailed")] + public bool? CheckpointCleanupFailed { get; set; } -/// Indicates whether the requested path exists in the client-provided session filesystem. -[Experimental(Diagnostics.Experimental)] -public sealed class SessionFsExistsResult -{ - /// Whether the path exists. - [JsonPropertyName("exists")] - public bool Exists { get; set; } + /// Number of events that were removed. + [JsonPropertyName("eventsRemoved")] + public long EventsRemoved { get; set; } } -/// Path to test for existence in the client-provided session filesystem. +/// Identifier of the event to truncate to; this event and all later events are removed. [Experimental(Diagnostics.Experimental)] -public sealed class SessionFsExistsRequest +internal sealed class HistoryTruncateRequest { - /// Path using SessionFs conventions. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// Event ID to truncate to. This event and all events after it are removed from the session. + [JsonPropertyName("eventId")] + public string EventId { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Filesystem metadata for the requested path, or a filesystem error if the stat failed. +/// A root user turn that the session can rewind to. [Experimental(Diagnostics.Experimental)] -public sealed class SessionFsStatResult +public sealed class HistoryRewindPoint { - /// ISO 8601 timestamp of creation. - [JsonPropertyName("birthtime")] - public DateTimeOffset Birthtime { get; set; } + /// Whether at least one file in this turn or a later turn can be restored. + [JsonPropertyName("canRestoreFiles")] + public bool CanRestoreFiles { get; set; } - /// Describes a filesystem error. - [JsonPropertyName("error")] - public SessionFsError? Error { get; set; } + /// ID of the user.message event that begins the discarded suffix. + [JsonPropertyName("eventId")] + public string EventId { get; set; } = string.Empty; - /// Whether the path is a directory. - [JsonPropertyName("isDirectory")] - public bool IsDirectory { get; set; } + /// Number of unique files in this turn and all later turns that have captured changes. + [JsonPropertyName("fileCount")] + public long FileCount { get; set; } - /// Whether the path is a file. - [JsonPropertyName("isFile")] - public bool IsFile { get; set; } + /// Whether this turn was an automatically injected autopilot continuation. + [JsonPropertyName("isAutopilotContinuation")] + public bool IsAutopilotContinuation { get; set; } - /// ISO 8601 timestamp of last modification. - [JsonPropertyName("mtime")] - public DateTimeOffset Mtime { get; set; } + /// Lines added by this turn's captured file changes. + [JsonPropertyName("linesAdded")] + public long LinesAdded { get; set; } - /// File size in bytes. - [JsonPropertyName("size")] - public long Size { get; set; } + /// Lines removed by this turn's captured file changes. + [JsonPropertyName("linesRemoved")] + public long LinesRemoved { get; set; } + + /// ISO timestamp of the user turn. + [JsonPropertyName("timestamp")] + public string Timestamp { get; set; } = string.Empty; + + /// Whether this turn itself captured any file changes. + [JsonPropertyName("turnChangedFiles")] + public bool TurnChangedFiles { get; set; } + + /// User-visible message text for the turn. + [JsonPropertyName("userMessage")] + public string UserMessage { get; set; } = string.Empty; } -/// Path whose metadata should be returned from the client-provided session filesystem. +/// Rewind points and file-change-tracking availability for the session. [Experimental(Diagnostics.Experimental)] -public sealed class SessionFsStatRequest +public sealed class HistoryListRewindPointsResult { - /// Path using SessionFs conventions. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// Whether this session captured file changes from its first turn. + [JsonPropertyName("fileChangeTrackingEnabled")] + public bool FileChangeTrackingEnabled { get; set; } + /// Root user turns in chronological order. Empty when `unavailableReason` is set. + [JsonPropertyName("points")] + public IList Points { get => field ??= []; set; } + + /// Why the listed points could not be produced, when applicable; the points list is empty whenever it is set. `unsupported-remote-session` is permanent for the session and comes with `fileChangeTrackingEnabled: false`. `session-busy` is transient and only ever reported by a session that *is* tracking (`fileChangeTrackingEnabled: true`), because the file-change captures cannot be read while work that may still mutate them is in flight; the same request succeeds once the session settles, so a client that wants points should retry rather than treat it as a failure. It is never `file-change-tracking-disabled`: an untracked local session still lists conversation-only points and reports that through `fileChangeTrackingEnabled: false`. + [JsonPropertyName("unavailableReason")] + public HistoryRewindUnavailableReason? UnavailableReason { get; set; } +} + +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionHistoryListRewindPointsRequest +{ /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Directory path to create in the client-provided session filesystem, with options for recursive creation and POSIX mode. +/// A file that a conversation-and-files rewind would restore. [Experimental(Diagnostics.Experimental)] -public sealed class SessionFsMkdirRequest +public sealed class HistoryRewindFilePreview { - /// Optional POSIX-style mode for newly created directories. - [JsonPropertyName("mode")] - public long? Mode { get; set; } + /// Aggregate change made across the discarded turns. + [JsonPropertyName("changeType")] + public HistoryRewindChangeType ChangeType { get; set; } - /// Path using SessionFs conventions. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// Lines added across the discarded turns. + [JsonPropertyName("linesAdded")] + public long LinesAdded { get; set; } - /// Create parent directories as needed. - [JsonPropertyName("recursive")] - public bool? Recursive { get; set; } + /// Lines removed across the discarded turns. + [JsonPropertyName("linesRemoved")] + public long LinesRemoved { get; set; } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Absolute path of the captured file. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; } -/// Names of entries in the requested directory, or a filesystem error if the read failed. +/// Files and aggregate changes for a prospective rewind. [Experimental(Diagnostics.Experimental)] -public sealed class SessionFsReaddirResult +public sealed class HistoryPreviewRewindResult { - /// Entry names in the directory. - [JsonPropertyName("entries")] - public IList Entries { get => field ??= []; set; } + /// Whether file restore is available for this session. This is authoritative: switch on it and read `reason` only when it is false. + [JsonPropertyName("available")] + public bool Available { get; set; } - /// Describes a filesystem error. - [JsonPropertyName("error")] - public SessionFsError? Error { get; set; } + /// Number of unique files in the preview. + [JsonPropertyName("fileCount")] + public long FileCount { get; set; } + + /// Files ordered by path. + [JsonPropertyName("files")] + public IList Files { get => field ??= []; set; } + + /// Why file restore is unavailable, when applicable. Populated only when `available` is false and never set when `available` is true. + [JsonPropertyName("reason")] + public HistoryRewindUnavailableReason? Reason { get; set; } } -/// Directory path whose entries should be listed from the client-provided session filesystem. +/// Event boundary to preview for conversation-and-files rewind. [Experimental(Diagnostics.Experimental)] -public sealed class SessionFsReaddirRequest +internal sealed class HistoryPreviewRewindRequest { - /// Path using SessionFs conventions. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// ID of the user.message event that begins the discarded suffix. + [JsonPropertyName("eventId")] + public string EventId { get; set; } = string.Empty; /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Directory entry returned by session filesystem `readdirWithTypes`, with name and entry type. +/// A captured file that rewind intentionally left unchanged. [Experimental(Diagnostics.Experimental)] -public sealed class SessionFsReaddirWithTypesEntry +public sealed class HistorySkippedFileRestore { - /// Entry name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Absolute path of the skipped file. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; - /// Entry type. - [JsonPropertyName("type")] - public SessionFsReaddirWithTypesEntryType Type { get; set; } + /// Reason the file was not restored. + [JsonPropertyName("reason")] + public HistoryFileRestoreSkipReason Reason { get; set; } } -/// Entries in the requested directory paired with file/directory type information, or a filesystem error if the read failed. +/// Structured outcome of a rewind request. [Experimental(Diagnostics.Experimental)] -public sealed class SessionFsReaddirWithTypesResult +public sealed class HistoryRewindResult { - /// Directory entries with type information. - [JsonPropertyName("entries")] - public IList Entries { get => field ??= []; set; } - - /// Describes a filesystem error. + /// Failure detail. Set only for the failure and partial-failure outcomes (`files-rolled-back`, `rollback-incomplete`, `truncation-failed`, `checkpoint-cleanup-failed`, `snapshot-prune-failed`); omitted for `success` and for the unavailable outcomes (`session-busy`, `file-change-tracking-disabled`, `unsupported-remote-session`). [JsonPropertyName("error")] - public SessionFsError? Error { get; set; } + public string? Error { get; set; } + + /// Number of persisted events removed by conversation truncation. Present only when truncation succeeded (outcomes `success`, `checkpoint-cleanup-failed`, and `snapshot-prune-failed`); omitted for every unavailable outcome (`session-busy`, `file-change-tracking-disabled`, `unsupported-remote-session`) and for `truncation-failed`, `files-rolled-back`, and `rollback-incomplete`. + [JsonPropertyName("eventsRemoved")] + public long? EventsRemoved { get; set; } + + /// Overall rewind outcome. This discriminates the result: it governs which of the remaining fields are populated, so consumers must switch on it before reading `eventsRemoved`, `restoredFiles`, `skippedFiles`, or `error`. See each field for the outcomes that populate it. + [JsonPropertyName("outcome")] + public HistoryRewindOutcome Outcome { get; set; } + + /// Absolute paths restored to their captured preimages. Always empty for conversation-only rewinds and for the unavailable outcomes (`session-busy`, `file-change-tracking-disabled`, `unsupported-remote-session`); only conversation-and-files outcomes that reached the file-restore stage populate it. + [JsonPropertyName("restoredFiles")] + public IList RestoredFiles { get => field ??= []; set; } + + /// Captured files intentionally left unchanged. Always empty for conversation-only rewinds and for the unavailable outcomes (`session-busy`, `file-change-tracking-disabled`, `unsupported-remote-session`); only conversation-and-files outcomes that reached the file-restore stage populate it. + [JsonPropertyName("skippedFiles")] + public IList SkippedFiles { get => field ??= []; set; } } -/// Directory path whose entries (with type information) should be listed from the client-provided session filesystem. +/// Boundary and mode for rewinding session history. [Experimental(Diagnostics.Experimental)] -public sealed class SessionFsReaddirWithTypesRequest +internal sealed class HistoryRewindRequest { - /// Path using SessionFs conventions. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; + /// ID of the user.message event that begins the discarded suffix. + [JsonPropertyName("eventId")] + public string EventId { get; set; } = string.Empty; + + /// Whether to rewind only conversation history or also restore captured files. + [JsonPropertyName("mode")] + public HistoryRewindMode Mode { get; set; } /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Path to remove from the client-provided session filesystem, with options for recursive removal and force. +/// Indicates whether an in-progress background compaction was cancelled. [Experimental(Diagnostics.Experimental)] -public sealed class SessionFsRmRequest +public sealed class HistoryCancelBackgroundCompactionResult { - /// Ignore errors if the path does not exist. - [JsonPropertyName("force")] - public bool? Force { get; set; } - - /// Path using SessionFs conventions. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; - - /// Remove directories and their contents recursively. - [JsonPropertyName("recursive")] - public bool? Recursive { get; set; } + /// Whether an in-progress background compaction was cancelled. False when no compaction was running, when the session is remote, or when the underlying processor was unavailable. + [JsonPropertyName("cancelled")] + public bool Cancelled { get; set; } +} +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionHistoryCancelBackgroundCompactionRequest +{ /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Source and destination paths for renaming or moving an entry in the client-provided session filesystem. +/// Indicates whether an in-progress manual compaction was aborted. [Experimental(Diagnostics.Experimental)] -public sealed class SessionFsRenameRequest +public sealed class HistoryAbortManualCompactionResult { - /// Destination path using SessionFs conventions. - [JsonPropertyName("dest")] - public string Dest { get; set; } = string.Empty; + /// Whether an in-progress manual compaction was aborted. False when no manual compaction was running, when its abort controller was already aborted, or when the session is remote. + [JsonPropertyName("aborted")] + public bool Aborted { get; set; } +} +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionHistoryAbortManualCompactionRequest +{ /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; - - /// Source path using SessionFs conventions. - [JsonPropertyName("src")] - public string Src { get; set; } = string.Empty; } -/// Query results including rows, columns, and rows affected, or a filesystem error if execution failed. +/// Markdown summary of the conversation context (empty when not available). [Experimental(Diagnostics.Experimental)] -public sealed class SessionFsSqliteQueryResult +public sealed class HistorySummarizeForHandoffResult { - /// Column names from the result set. - [JsonPropertyName("columns")] - public IList Columns { get => field ??= []; set; } - - /// Describes a filesystem error. - [JsonPropertyName("error")] - public SessionFsError? Error { get; set; } - - /// SQLite last_insert_rowid() value for INSERT. - [JsonPropertyName("lastInsertRowid")] - public long? LastInsertRowid { get; set; } - - /// For SELECT: array of row objects. For others: empty array. - [JsonPropertyName("rows")] - public IList> Rows { get => field ??= []; set; } - - /// Number of rows affected (for INSERT/UPDATE/DELETE). - [JsonPropertyName("rowsAffected")] - public long RowsAffected { get; set; } + /// Markdown summary of the conversation context produced by an LLM. Empty string when there are no messages or when the session does not support local summarization. + [JsonPropertyName("summary")] + public string Summary { get; set; } = string.Empty; } -/// SQL query, query type, and optional bind parameters for executing a SQLite query against the per-session database. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -public sealed class SessionFsSqliteQueryRequest +internal sealed class SessionHistorySummarizeForHandoffRequest { - /// Optional named bind parameters. - [JsonPropertyName("params")] - public IDictionary? Params { get; set; } - - /// SQL query to execute. - [JsonPropertyName("query")] - public string Query { get; set; } = string.Empty; - - /// How to execute the query: 'exec' for DDL/multi-statement (no results), 'query' for SELECT (returns rows), 'run' for INSERT/UPDATE/DELETE (returns rowsAffected). - [JsonPropertyName("queryType")] - public SessionFsSqliteQueryType QueryType { get; set; } - /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Indicates whether the per-session SQLite database already exists. +/// What a successful clear removed. A clear that could not be applied rejects instead of reporting a count. [Experimental(Diagnostics.Experimental)] -public sealed class SessionFsSqliteExistsResult +public sealed class HistoryClearContextResult { - /// Whether the session database already exists. - [JsonPropertyName("exists")] - public bool Exists { get; set; } + /// Number of non-system, non-developer messages that were removed from the conversation. Zero only when the window already held no conversation. + [JsonPropertyName("messagesCleared")] + public long MessagesCleared { get; set; } } -/// Identifies the target session. -public sealed class SessionFsSqliteExistsRequest +/// Parameters for clearing the conversation and seeding the window that replaces it. +[Experimental(Diagnostics.Experimental)] +internal sealed class HistoryClearContextRequest { + /// First user message of the fresh context window. Required: a cleared window holding only system and developer messages is not a conversation a model can answer, so every clear seeds the window it creates. Delivered by the enclosing turn driver once the agentic loop exits, which is why the call must be made from inside a tool handler. + [JsonPropertyName("prompt")] + public string Prompt { get; set; } = string.Empty; + /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// Canvas open result returned by the provider. +/// User-facing pending queue entry, with kind and display text for a queued message, slash command, or model change. [Experimental(Diagnostics.Experimental)] -public sealed class CanvasProviderOpenResult +public sealed class QueuePendingItems { - /// Provider-supplied status text. - [JsonPropertyName("status")] - public string? Status { get; set; } + /// Agent mode stored on this queued entry, as stamped when it was enqueued. Items without an explicit mode report interactive. This is not necessarily the mode that will constrain the turn: a plan or autopilot session applies its own write gate, continuation loop and permission posture to every drained item regardless of the mode stored here. + [JsonPropertyName("agentMode")] + public SendAgentMode AgentMode { get; set; } - /// Provider-supplied title. - [JsonPropertyName("title")] - public string? Title { get; set; } + /// Human-readable text to display for this queue entry in the UI. + [JsonPropertyName("displayText")] + public string DisplayText { get; set; } = string.Empty; - /// URL for web-rendered canvases. - [JsonPropertyName("url")] - public string? Url { get; set; } + /// Stable opaque id for the canonical queued item. Batch rows share one id. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; + + /// Whether this item is a queued user message or a queued slash command / model change. + [JsonPropertyName("kind")] + public QueuePendingItemsKind Kind { get; set; } } -/// Host capabilities. +/// Snapshot of the session's pending queued items and immediate-steering messages. [Experimental(Diagnostics.Experimental)] -public sealed class CanvasHostContextCapabilities +public sealed class QueuePendingItemsResult { - /// Whether canvas rendering is supported. - [JsonPropertyName("canvases")] - public bool? Canvases { get; set; } + /// Pending queued items in submission order. Includes user messages, queued slash commands, and queued model changes; omits internal system items. + [JsonPropertyName("items")] + public IList Items { get => field ??= []; set; } + + /// Display text for messages currently in the immediate steering queue (interjections sent during a running turn). + [JsonPropertyName("steeringMessages")] + public IList SteeringMessages { get => field ??= []; set; } } -/// Host context supplied by the runtime. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -public sealed class CanvasHostContext +internal sealed class SessionQueuePendingItemsRequest { - /// Host capabilities. - [JsonPropertyName("capabilities")] - public CanvasHostContextCapabilities? Capabilities { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } -/// Session context supplied by the runtime. +/// Internal snapshot of native queue state for local session orchestration. [Experimental(Diagnostics.Experimental)] -public sealed class CanvasSessionContext +internal sealed class QueueSnapshotResult { - /// Active session working directory, when known. - [JsonPropertyName("workingDirectory")] - public string? WorkingDirectory { get; set; } + /// Insertion orders for queued items, aligned with `items`. + [JsonPropertyName("itemOrders")] + public IList? ItemOrders { get; set; } + + /// User-facing pending items in FIFO order. + [JsonPropertyName("items")] + public IList Items { get => field ??= []; set; } + + /// Insertion orders for immediate steering messages, aligned with `steeringMessages`. + [JsonPropertyName("steeringMessageOrders")] + public IList? SteeringMessageOrders { get; set; } + + /// Immediate steering messages waiting for an active turn. + [JsonPropertyName("steeringMessages")] + public IList SteeringMessages { get => field ??= []; set; } } -/// Canvas open parameters sent to the provider. +/// Identifies the target session. [Experimental(Diagnostics.Experimental)] -public sealed class CanvasProviderOpenRequest +internal sealed class SessionQueueSnapshotRequest { - /// Provider-local canvas identifier. - [JsonPropertyName("canvasId")] - public string CanvasId { get; set; } = string.Empty; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} - /// Owning provider identifier. - [JsonPropertyName("extensionId")] - public string ExtensionId { get; set; } = string.Empty; +/// Result of moving a queued item. +[Experimental(Diagnostics.Experimental)] +public sealed class QueueMoveItemResult +{ + /// True when the item changed position; false when it was already at the requested position. + [JsonPropertyName("changed")] + public bool Changed { get; set; } +} - /// Host context supplied by the runtime. - [JsonPropertyName("host")] - public CanvasHostContext? Host { get; set; } +/// Parameters for moving a queued item by stable id. +[Experimental(Diagnostics.Experimental)] +internal sealed class QueueMoveItemRequest +{ + /// Stable opaque queued-item id. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; - /// Canvas open input. - [JsonPropertyName("input")] - public JsonElement? Input { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; - /// Stable caller-supplied canvas instance identifier. - [JsonPropertyName("instanceId")] - public string InstanceId { get; set; } = string.Empty; + /// Zero-based target position in the public visible queue. Values outside the queue clamp to an end. + [JsonPropertyName("toPosition")] + public long ToPosition { get; set; } +} - /// Session context supplied by the runtime. - [JsonPropertyName("session")] - public CanvasSessionContext? Session { get; set; } +/// Result of inserting a queued message. +[Experimental(Diagnostics.Experimental)] +public sealed class QueueInsertAtResult +{ + /// Fresh stable opaque id assigned to the inserted item. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; +} + +/// Serializable message fields accepted by queue.insertAt. +[Experimental(Diagnostics.Experimental)] +public sealed class QueueInsertMessage +{ + /// Optional explicit agent mode. When omitted, the session's current mode is assigned. + [JsonPropertyName("agentMode")] + public SendAgentMode? AgentMode { get; set; } + + /// Optional attachments for the message. + [JsonPropertyName("attachments")] + public IList? Attachments { get; set; } + + /// Whether the message is billable. + [JsonPropertyName("billable")] + public bool? Billable { get; set; } + + /// Accepted for internal SendOptions compatibility but ignored; delivery is derived from current session activity. + [JsonPropertyName("delivery")] + public string? Delivery { get; set; } + + /// Optional user-facing display text. + [JsonPropertyName("displayPrompt")] + public string? DisplayPrompt { get; set; } + + /// Accepted for SendOptions compatibility but ignored; inserted items always use queued delivery semantics. + [JsonPropertyName("mode")] + public SendMode? Mode { get; set; } + + /// Accepted for SendOptions compatibility but ignored; the requested public position controls placement. + [JsonPropertyName("prepend")] + public bool? Prepend { get; set; } + + /// The user message text. + [JsonPropertyName("prompt")] + public string Prompt { get; set; } = string.Empty; + + /// Per-turn request headers. + [JsonPropertyName("requestHeaders")] + public IDictionary? RequestHeaders { get; set; } + + /// Required tool name for the turn, when any. + [JsonPropertyName("requiredTool")] + public string? RequiredTool { get; set; } + + /// Optional provenance source. `system` is rejected: it would hide the inserted row from `pendingItems` and make it unaddressable while still executing, so inserted items must stay visible. + [JsonPropertyName("source")] + public string? Source { get; set; } + + /// Accepted for SendOptions compatibility but ignored; insertion scheduling is controlled by the queue drain state. + [JsonPropertyName("wait")] + public bool? Wait { get; set; } +} + +/// Parameters for inserting a queued message at a public visible position. +[Experimental(Diagnostics.Experimental)] +internal sealed class QueueInsertAtRequest +{ + /// Gets or sets the message value. + [JsonPropertyName("message")] + public QueueInsertMessage Message { get => field ??= new(); set; } + + /// Zero-based position in the public visible queue. Values outside the queue clamp to an end. + [JsonPropertyName("position")] + public long Position { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Result of removing a queued item. +[Experimental(Diagnostics.Experimental)] +public sealed class QueueRemoveAtResult +{ + /// True when the addressed item was removed. + [JsonPropertyName("removed")] + public bool Removed { get; set; } +} + +/// Parameters for removing a queued item by stable id. +[Experimental(Diagnostics.Experimental)] +internal sealed class QueueRemoveAtRequest +{ + /// Gets or sets the id value. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Result of editing a queued message. +[Experimental(Diagnostics.Experimental)] +public sealed class QueueUpdateTextResult +{ + /// True when the stored text changed. + [JsonPropertyName("updated")] + public bool Updated { get; set; } +} + +/// Parameters for editing a single queued message. +[Experimental(Diagnostics.Experimental)] +internal sealed class QueueUpdateTextRequest +{ + /// Gets or sets the displayPrompt value. + [JsonPropertyName("displayPrompt")] + public string? DisplayPrompt { get; set; } + + /// Gets or sets the id value. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; + + /// Gets or sets the prompt value. + [JsonPropertyName("prompt")] + public string Prompt { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Result of duplicating a queued item. +[Experimental(Diagnostics.Experimental)] +public sealed class QueueDuplicateAtResult +{ + /// Fresh stable opaque id assigned to the duplicate. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; +} + +/// Parameters for duplicating a queued item. +[Experimental(Diagnostics.Experimental)] +internal sealed class QueueDuplicateAtRequest +{ + /// Gets or sets the id value. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Parameters for acquiring or releasing the queued-lane drain pause. Acquisition is exclusive and non-idempotent: `paused: true` against an already-paused session fails with `queue_already_paused`. The pause is never released automatically — it is not tied to the caller's lifetime, so a client that exits without sending `paused: false` leaves the lane frozen. Release is unowned: `paused: false` clears the pause for any caller, including one that never acquired it. +[Experimental(Diagnostics.Experimental)] +internal sealed class QueueSetDrainPausedRequest +{ + /// Gets or sets the paused value. + [JsonPropertyName("paused")] + public bool Paused { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Result of trying to steer a queued message into a live turn. +[Experimental(Diagnostics.Experimental)] +public sealed class QueueSendNowResult +{ + /// True when the item was accepted into the steering lane; false when no main turn was live. + [JsonPropertyName("steered")] + public bool Steered { get; set; } +} + +/// Parameters for steering a queued message into a live turn. +[Experimental(Diagnostics.Experimental)] +internal sealed class QueueSendNowRequest +{ + /// Gets or sets the id value. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Whether the native queue has pending work. +[Experimental(Diagnostics.Experimental)] +internal sealed class QueueHasPendingResult +{ + /// True when queued or immediate native work is pending. + [JsonPropertyName("hasPending")] + public bool HasPending { get; set; } +} + +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionQueueHasPendingRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Whether a deferred-idle drain should run. +[Experimental(Diagnostics.Experimental)] +internal sealed class QueueBeginDeferredIdleDrainResult +{ + /// True when the host should run finishDeferredIdleDrain asynchronously. + [JsonPropertyName("shouldDrain")] + public bool ShouldDrain { get; set; } +} + +/// Inputs for starting a deferred-idle drain. +[Experimental(Diagnostics.Experimental)] +internal sealed class QueueBeginDeferredIdleDrainRequest +{ + /// Whether the host still has active background work. + [JsonPropertyName("activeBackgroundWork")] + public bool ActiveBackgroundWork { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Action selected by the native deferred-idle drain. +[Experimental(Diagnostics.Experimental)] +internal sealed class QueueFinishDeferredIdleDrainResult +{ + /// Whether the deferred idle was caused by an aborted foreground turn. + [JsonPropertyName("aborted")] + public bool Aborted { get; set; } + + /// One of none, processQueue, or emitSessionIdle. + [JsonPropertyName("action")] + public string Action { get; set; } = string.Empty; +} + +/// Inputs for completing a deferred-idle drain. +[Experimental(Diagnostics.Experimental)] +internal sealed class QueueFinishDeferredIdleDrainRequest +{ + /// Whether the host still has active background work. + [JsonPropertyName("activeBackgroundWork")] + public bool ActiveBackgroundWork { get; set; } + + /// Whether native queued work remains. + [JsonPropertyName("hasPending")] + public bool HasPending { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Inputs for marking session.idle deferred in native state. +[Experimental(Diagnostics.Experimental)] +internal sealed class QueueDeferSessionIdleRequest +{ + /// Whether the deferred idle was caused by an aborted foreground turn. + [JsonPropertyName("aborted")] + public bool Aborted { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Indicates whether a user-facing pending item was removed. +[Experimental(Diagnostics.Experimental)] +public sealed class QueueRemoveMostRecentResult +{ + /// True if a user-facing pending item was removed (LIFO across both queues); false when no removable items remained. + [JsonPropertyName("removed")] + public bool Removed { get; set; } +} + +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionQueueRemoveMostRecentRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionQueueClearRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Internal filter for consuming queued system notifications. +[Experimental(Diagnostics.Experimental)] +internal sealed class QueueConsumeSystemNotificationsRequest +{ + /// Opaque runtime-owned filter object. + [JsonPropertyName("filter")] + public JsonElement Filter { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Result of enqueueing the resume-pending wake item. +[Experimental(Diagnostics.Experimental)] +internal sealed class QueueEnqueueResumePendingResult +{ + /// True when a wake item was newly queued. + [JsonPropertyName("queued")] + public bool Queued { get; set; } +} + +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionQueueEnqueueResumePendingRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionQueueProcessRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Batch of session events returned by a read, with cursor and continuation metadata. +[Experimental(Diagnostics.Experimental)] +public sealed class EventsReadResult +{ + /// Opaque cursor for the next read. Pass back unchanged in the next read.cursor to continue from where this read left off. Always present, even when no events were returned. For a backward read this cursor pages toward OLDER events; keep passing `direction: backward` with it (the cursor is also self-describing, so backward paging continues correctly). + [JsonPropertyName("cursor")] + public string Cursor { get; set; } = string.Empty; + + /// Cursor status: 'ok' means the cursor was applied successfully; 'expired' means the cursor referred to an event that no longer exists in history (e.g. truncated or compacted away) and the read fell back to a boundary of the remaining history. For a forward read the fallback starts from the beginning of the remaining history; for a backward read it falls back to the tail (the newest window). Because the fallback page is a fresh boundary snapshot rather than a continuation of the requested cursor, it may overlap events the consumer has already rendered — a backward fallback to the tail in particular can repeat the newest window. On 'expired', consumers should reset or rebase their local pagination state (or deduplicate by event id) before continuing from the returned cursor rather than blindly appending/prepending the fallback page. + [JsonPropertyName("cursorStatus")] + public EventsCursorStatus CursorStatus { get; set; } + + /// Session events for this batch, merged into a single stream in creation order: durable (persisted) events and ephemeral events interleave exactly as they were emitted. Set `includeEphemeral: false` to receive only durable events. Ephemeral events are never replayable once pruned from the in-memory ring, so a consumer that needs them should keep reading with a non-zero `waitMs`. For a backward (tail-first) read, the returned window contains persisted events only, still in chronological (oldest-to-newest) append order. + [JsonPropertyName("events")] + public IList Events { get => field ??= []; set; } + + /// True when more events are available in the read's direction. For a forward read, true means the batch returned `max` events and more are available immediately. For a backward read, true means older persisted events remain before the returned window. + [JsonPropertyName("hasMore")] + public bool HasMore { get; set; } +} + +/// Cursor, batch size, and optional long-poll/filter parameters for reading session events. +[Experimental(Diagnostics.Experimental)] +internal sealed class EventLogReadRequest +{ + /// Optional non-empty list of subagent identifiers. When provided, only events owned by one of these agents are returned; ownership recognizes the event envelope's agentId plus legacy data.agentId and data.parentToolCallId markers. This filter takes precedence over agentScope. + [JsonPropertyName("agentIds")] + public IList? AgentIds { get; set; } + + /// Agent-scope filter: 'primary' returns only main-agent events plus events whose type starts with 'subagent.' (matching the typed-subscription default behavior); 'all' returns events from all agents (matching wildcard-subscription behavior). Default is 'all' to preserve wildcard semantics for catch-up callers. + [JsonPropertyName("agentScope")] + public EventsAgentScope? AgentScope { get; set; } + + /// Opaque cursor returned by a previous read. Omit on the first call to start from the beginning of the session's persisted history. + [JsonPropertyName("cursor")] + public string? Cursor { get; set; } + + /// Direction to page through the session's persisted event history. 'forward' (default) pages from the cursor toward newer events (or from the start of history when no cursor is given). 'backward' enables tail-first reads: with no cursor it returns the NEWEST `max` events, and the returned cursor pages toward OLDER events on subsequent backward reads. Events within a returned batch are always in chronological (oldest-to-newest) order, even for a backward read. Backward reads cover PERSISTED history only; ephemeral events are never returned by a backward read. `direction` selects the INITIAL read only: the returned cursor is self-describing, so a continuation read pages in the cursor's own direction regardless of the `direction` passed alongside it — a forward cursor always pages forward and a backward cursor always pages backward. Pass the direction that matches the cursor to avoid confusion. + [JsonPropertyName("direction")] + public EventsReadDirection? Direction { get; set; } + + /// When false, skip ephemeral events entirely and return only durable (persisted) events. History-backfill callers that discard ephemerals anyway should set this so the read is bounded by the durable log length instead of racing the ephemeral ring on a busy session. Defaults to true (ephemerals are interleaved with durable events in creation order). Ignored by backward reads, which always cover persisted history only. + [JsonPropertyName("includeEphemeral")] + public bool? IncludeEphemeral { get; set; } + + /// Maximum number of events to return in this batch (1–1000, default 200). + [JsonPropertyName("max")] + public long? Max { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + + /// Either '*' to receive all event types, or a non-empty list of event types to receive. + [JsonPropertyName("types")] + public JsonElement? Types { get; set; } + + /// Milliseconds to wait for new events when the cursor is at the tail of history. 0 (default) returns immediately even if no events are available. Capped at 30000ms. Ephemeral events that arrive during the wait are delivered in this batch but are NOT replayable on a subsequent read (use a non-zero waitMs in your next call to capture future ephemerals as they happen). This applies to forward reads only: a backward read always returns immediately and ignores `waitMs`, because backward paging covers persisted history only while new events append at the tail (the opposite end from a backward page), so no blocking or ephemeral delivery can occur. + [JsonConverter(typeof(MillisecondsTimeSpanConverter))] + [JsonPropertyName("waitMs")] + public TimeSpan? Wait { get; set; } +} + +/// Snapshot of the current tail cursor without returning any events. Use this when a consumer wants to subscribe to live events going forward without first paginating through the entire persisted history (which would happen if `read` were called without a cursor on a long-lived session). +[Experimental(Diagnostics.Experimental)] +public sealed class EventLogTailResult +{ + /// Opaque cursor pointing at the current tail of the session's persisted-events history. Pass back to `read` to receive only events that arrive AFTER this snapshot. When the session has no events, this returns the same sentinel as an unset cursor (i.e. equivalent to omitting the cursor on a first read). + [JsonPropertyName("cursor")] + public string Cursor { get; set; } = string.Empty; +} + +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionEventLogTailRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Opaque handle representing an event-type interest registration. +[Experimental(Diagnostics.Experimental)] +public sealed class RegisterEventInterestResult +{ + /// Opaque handle for this registration. Pass to releaseInterest to release. Each call to registerInterest produces a fresh handle, even when the same eventType is registered multiple times. + [JsonPropertyName("handle")] + public string Handle { get; set; } = string.Empty; +} + +/// Event type to register consumer interest for, used by runtime gating logic. +[Experimental(Diagnostics.Experimental)] +internal sealed class RegisterEventInterestParams +{ + /// The event type the consumer wants the runtime to treat as 'observed' for behavior-switching gating. Some runtime code paths inspect whether any consumer is interested in a specific event type and choose a different implementation accordingly (e.g. `mcp.oauth_required`: when interest is registered the runtime delegates interactive OAuth token acquisition to the consumer via `mcp.oauth_required` events; when no interest is registered the runtime still attempts non-interactive reconnect from cached or refreshable tokens, and only marks the server `needs-auth` if usable credentials are unavailable — it does not open a browser or start interactive OAuth without a consumer). SDK clients that long-poll events do NOT automatically appear as listeners to these gating checks — they must explicitly call `registerInterest` for each event type they want the runtime to count as having a consumer. Multiple registrations for the same event type from the same or different consumers are tracked independently and must each be released. See: `mcp.oauth_required`, `sampling.requested`, `auto_mode_switch.requested`, `session_limits_exhausted.requested`, `user_input.requested`, `elicitation.requested`, `command.queued`, `exit_plan_mode.requested`. + [JsonPropertyName("eventType")] + public string EventType { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Indicates whether the operation succeeded. +[Experimental(Diagnostics.Experimental)] +public sealed class EventLogReleaseInterestResult +{ + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } +} + +/// Opaque handle previously returned by `registerInterest` to release. +[Experimental(Diagnostics.Experimental)] +internal sealed class ReleaseEventInterestParams +{ + /// Handle returned by a previous `registerInterest` call. Idempotent: releasing an unknown or already-released handle is a no-op (returns success). When the last outstanding handle for an event type is released, the runtime reverts to its 'no consumer' code path for that event type. + [JsonPropertyName("handle")] + public string Handle { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Aggregated code change metrics. +[Experimental(Diagnostics.Experimental)] +public sealed class UsageMetricsCodeChanges +{ + /// Distinct file paths modified during the session. + [JsonPropertyName("filesModified")] + public IList FilesModified { get => field ??= []; set; } + + /// Number of distinct files modified. + [JsonPropertyName("filesModifiedCount")] + public long FilesModifiedCount { get; set; } + + /// Total lines of code added. + [JsonPropertyName("linesAdded")] + public long LinesAdded { get; set; } + + /// Total lines of code removed. + [JsonPropertyName("linesRemoved")] + public long LinesRemoved { get; set; } +} + +/// Request count and cost metrics for this model. +[Experimental(Diagnostics.Experimental)] +public sealed class UsageMetricsModelMetricRequests +{ + /// User-initiated premium request cost (with multiplier applied). + [JsonPropertyName("cost")] + public double Cost { get; set; } + + /// Number of API requests made with this model. + [JsonPropertyName("count")] + public long Count { get; set; } +} + +/// Per-model token-detail entry containing the accumulated token count for one token type. +[Experimental(Diagnostics.Experimental)] +public sealed class UsageMetricsModelMetricTokenDetail +{ + /// Accumulated token count for this token type. + [JsonPropertyName("tokenCount")] + public long TokenCount { get; set; } +} + +/// Token usage metrics for this model. +[Experimental(Diagnostics.Experimental)] +public sealed class UsageMetricsModelMetricUsage +{ + /// Total tokens read from prompt cache. + [JsonPropertyName("cacheReadTokens")] + public long CacheReadTokens { get; set; } + + /// Total tokens written to prompt cache. + [JsonPropertyName("cacheWriteTokens")] + public long CacheWriteTokens { get; set; } + + /// Total input tokens consumed. + [JsonPropertyName("inputTokens")] + public long InputTokens { get; set; } + + /// Total output tokens produced. + [JsonPropertyName("outputTokens")] + public long OutputTokens { get; set; } + + /// Total output tokens used for reasoning. + [JsonPropertyName("reasoningTokens")] + public long? ReasoningTokens { get; set; } +} + +/// Per-model usage metrics, including request counts/costs, token usage, nano-AI units, and per-token-type details. +[Experimental(Diagnostics.Experimental)] +public sealed class UsageMetricsModelMetric +{ + /// Latest known prompt-cache expiration for this model. A timestamp in the past indicates that the observed cache has expired. + [JsonPropertyName("cacheExpiresAt")] + public DateTimeOffset? CacheExpiresAt { get; set; } + + /// Request count and cost metrics for this model. + [JsonPropertyName("requests")] + public UsageMetricsModelMetricRequests Requests { get => field ??= new(); set; } + + /// Token count details per type. + [JsonPropertyName("tokenDetails")] + public IDictionary? TokenDetails { get; set; } + + /// Accumulated nano-AI units cost for this model. + [JsonPropertyName("totalNanoAiu")] + public double? TotalNanoAiu { get; set; } + + /// Token usage metrics for this model. + [JsonPropertyName("usage")] + public UsageMetricsModelMetricUsage Usage { get => field ??= new(); set; } +} + +/// Session-wide token-detail entry containing the accumulated token count for one token type. +[Experimental(Diagnostics.Experimental)] +public sealed class UsageMetricsTokenDetail +{ + /// Accumulated token count for this token type. + [JsonPropertyName("tokenCount")] + public long TokenCount { get; set; } +} + +/// Accumulated session usage metrics, including premium request cost, token counts, model breakdown, and code-change totals. +[Experimental(Diagnostics.Experimental)] +public sealed class UsageGetMetricsResult +{ + /// Aggregated code change metrics. + [JsonPropertyName("codeChanges")] + public UsageMetricsCodeChanges CodeChanges { get => field ??= new(); set; } + + /// Currently active model identifier. + [JsonPropertyName("currentModel")] + public string? CurrentModel { get; set; } + + /// Input tokens from the most recent main-agent API call. + [JsonPropertyName("lastCallInputTokens")] + public long LastCallInputTokens { get; set; } + + /// Output tokens from the most recent main-agent API call. + [JsonPropertyName("lastCallOutputTokens")] + public long LastCallOutputTokens { get; set; } + + /// Per-model token and request metrics, keyed by model identifier. + [JsonPropertyName("modelMetrics")] + public IDictionary ModelMetrics { get => field ??= new Dictionary(); set; } + + /// ISO 8601 timestamp when the session started. + [JsonPropertyName("sessionStartTime")] + public DateTimeOffset SessionStartTime { get; set; } + + /// Session-wide per-token-type accumulated token counts. + [JsonPropertyName("tokenDetails")] + public IDictionary? TokenDetails { get; set; } + + /// Total time spent in model API calls (milliseconds). + [JsonConverter(typeof(MillisecondsTimeSpanConverter))] + [JsonPropertyName("totalApiDurationMs")] + public TimeSpan TotalApiDuration { get; set; } + + /// Session-wide accumulated nano-AI units cost. + [JsonPropertyName("totalNanoAiu")] + public double? TotalNanoAiu { get; set; } + + /// Total user-initiated premium request cost across all models (may be fractional due to multipliers). + [JsonPropertyName("totalPremiumRequestCost")] + public double TotalPremiumRequestCost { get; set; } + + /// Raw count of user-initiated API requests. + [JsonPropertyName("totalUserRequests")] + public long TotalUserRequests { get; set; } +} + +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionUsageGetMetricsRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Prediction result. Available results include prediction details; unavailable results include an explicit reason. +/// Polymorphic base type discriminated by kind. +[Experimental(Diagnostics.Experimental)] +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(SessionLimitPredictionResultAvailable), "available")] +[JsonDerivedType(typeof(SessionLimitPredictionResultUnavailable), "unavailable")] +public partial class SessionLimitPredictionResult +{ + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; +} + + +/// Baseline data provenance for a prediction. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionLimitPredictionBaselineData +{ + /// End of the baseline data slice. + [JsonPropertyName("windowEnd")] + public string WindowEnd { get; set; } = string.Empty; + + /// Start of the baseline data slice. + [JsonPropertyName("windowStart")] + public string WindowStart { get; set; } = string.Empty; +} + +/// Semantic usage tier and its AI-credit cap. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionLimitPredictionTierOption +{ + /// AI-credit cap for this tier. + [JsonPropertyName("cap")] + public double Cap { get; set; } + + /// Gets or sets the tier value. + [JsonPropertyName("tier")] + public SessionLimitPredictionTier Tier { get; set; } +} + +/// Explainable AI-credit session-limit prediction. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionLimitPredictionDetails +{ + /// Baseline data provenance. + [JsonPropertyName("baselineData")] + public SessionLimitPredictionBaselineData BaselineData { get => field ??= new(); set; } + + /// Client population used for the prediction. + [JsonPropertyName("clientType")] + public SessionLimitPredictionClientType ClientType { get; set; } + + /// Resolved model family when known. + [JsonPropertyName("family")] + public string? Family { get; set; } + + /// Model identifier used for lookup. + [JsonPropertyName("modelId")] + public string ModelId { get; set; } = string.Empty; + + /// Recommended maximum AI credits for this session. + [JsonPropertyName("recommendedCap")] + public double RecommendedCap { get; set; } + + /// Tier chosen as the recommended cap. + [JsonPropertyName("recommendedTier")] + public SessionLimitPredictionTier RecommendedTier { get; set; } + + /// Baseline fallback level used to create the prediction. + [JsonPropertyName("source")] + public SessionLimitPredictionSource Source { get; set; } + + /// Key matched at the source level, such as a model id, family id, or `global`. + [JsonPropertyName("sourceKey")] + public string SourceKey { get; set; } = string.Empty; + + /// Ordered usage tiers and their AI-credit caps. + [JsonPropertyName("tiers")] + public IList Tiers { get => field ??= []; set; } +} + +/// The available variant of . +[Experimental(Diagnostics.Experimental)] +public partial class SessionLimitPredictionResultAvailable : SessionLimitPredictionResult +{ + /// + [JsonIgnore] + public override string Kind => "available"; + + /// Predicted session limit details. + [JsonPropertyName("prediction")] + public required SessionLimitPredictionDetails Prediction { get; set; } +} + +/// The unavailable variant of . +[Experimental(Diagnostics.Experimental)] +public partial class SessionLimitPredictionResultUnavailable : SessionLimitPredictionResult +{ + /// + [JsonIgnore] + public override string Kind => "unavailable"; + + /// Reason no prediction is available. + [JsonPropertyName("reason")] + public required SessionLimitPredictionUnavailableReason Reason { get; set; } +} + +/// RPC data type for SessionLimitPredictionPredict operations. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionLimitPredictionPredictRequest +{ + /// Client type to size for. Defaults to `cli-interactive`. + [JsonPropertyName("clientType")] + public SessionLimitPredictionClientType? ClientType { get; set; } + + /// Optional model identifier override. If omitted, the session's current model is used. + [JsonPropertyName("modelId")] + public string? ModelId { get; set; } +} + +/// RPC data type for SessionLimitPredictionPredictRequestWithSession operations. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionLimitPredictionPredictRequestWithSession +{ + /// Client type to size for. Defaults to `cli-interactive`. + [JsonPropertyName("clientType")] + public SessionLimitPredictionClientType? ClientType { get; set; } + + /// Optional model identifier override. If omitted, the session's current model is used. + [JsonPropertyName("modelId")] + public string? ModelId { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// GitHub URL for the session and a flag indicating whether remote steering is enabled. +[Experimental(Diagnostics.Experimental)] +public sealed class RemoteEnableResult +{ + /// Whether remote steering is enabled. + [JsonPropertyName("remoteSteerable")] + public bool RemoteSteerable { get; set; } + + /// GitHub frontend URL for this session. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] + [JsonPropertyName("url")] + public string? Url { get; set; } +} + +/// Optional remote session mode ("off", "export", or "on"); defaults to enabling both export and remote steering. +[Experimental(Diagnostics.Experimental)] +internal sealed class RemoteEnableRequest +{ + /// Per-session remote mode. "off" disables remote, "export" exports session events to GitHub without enabling remote steering, "on" enables both export and remote steering. + [JsonPropertyName("mode")] + public RemoteSessionMode? Mode { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionRemoteDisableRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Persist a steerability change as a `session.remote_steerable_changed` event. Used by the host (CLI / SDK consumer) when it has just finished enabling or disabling steering on a remote exporter that the runtime does not directly own. +[Experimental(Diagnostics.Experimental)] +public sealed class RemoteNotifySteerableChangedResult +{ +} + +/// New remote-steerability state to persist as a `session.remote_steerable_changed` event. +[Experimental(Diagnostics.Experimental)] +internal sealed class RemoteNotifySteerableChangedRequest +{ + /// Whether the session now supports remote steering via GitHub. The runtime persists this as a `session.remote_steerable_changed` event so resume/replay sees the up-to-date capability. + [JsonPropertyName("remoteSteerable")] + public bool RemoteSteerable { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Current sharing status and shareable GitHub URL for a session. +[Experimental(Diagnostics.Experimental)] +public sealed class VisibilityGetResult +{ + /// Shareable GitHub URL for the session. Present when the session is synced and the URL can be resolved. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] + [JsonPropertyName("shareUrl")] + public string? ShareUrl { get; set; } + + /// Current sharing status. Absent when the session is not synced or the status could not be retrieved (e.g. the user is not authenticated). + [JsonPropertyName("status")] + public SessionVisibilityStatus? Status { get; set; } + + /// Whether the session has been synced to Mission Control (i.e. has a GitHub task). When false, the session cannot be shared and `status`/`shareUrl` are absent. + [JsonPropertyName("synced")] + public bool Synced { get; set; } +} + +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionVisibilityGetRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Effective sharing status and shareable GitHub URL after updating session visibility. +[Experimental(Diagnostics.Experimental)] +public sealed class VisibilitySetResult +{ + /// Shareable GitHub URL for the session. Present when the session is synced and the URL can be resolved. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] + [JsonPropertyName("shareUrl")] + public string? ShareUrl { get; set; } + + /// Effective sharing status after the update. May differ from the requested status for task types that are already visible to repository readers by default. Absent when the update could not be applied (e.g. the session is not synced or the user is not authenticated). + [JsonPropertyName("status")] + public SessionVisibilityStatus? Status { get; set; } + + /// Whether the session has been synced to Mission Control (i.e. has a GitHub task). When false, the visibility change could not be applied and `status`/`shareUrl` are absent. + [JsonPropertyName("synced")] + public bool Synced { get; set; } +} + +/// Desired sharing status for the session. +[Experimental(Diagnostics.Experimental)] +internal sealed class VisibilitySetRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + + /// Sharing status to apply. "repo" makes the session visible to repository readers; "unshared" restricts it to the creator and collaborators. + [JsonPropertyName("status")] + public SessionVisibilityStatus Status { get; set; } +} + +/// Scheduled prompt entry with ID, timing (`intervalMs`, `cron`, or `at`), prompt text, recurrence, and next run time. +[Experimental(Diagnostics.Experimental)] +public sealed class ScheduleEntry +{ + /// Absolute fire time (epoch milliseconds) for a one-shot calendar schedule. + [JsonPropertyName("at")] + public long? At { get; set; } + + /// 5-field cron expression for a recurring calendar schedule, evaluated in `tz`. + [JsonPropertyName("cron")] + public string? Cron { get; set; } + + /// Display-only label for the prompt as shown in the UI (e.g. `/skill-name` for a skill-invocation schedule). The actual enqueued prompt is `prompt`. + [JsonPropertyName("displayPrompt")] + public string? DisplayPrompt { get; set; } + + /// Sequential id assigned by the runtime within the session. Stable across resumes (rebuilt from the event log). + [JsonPropertyName("id")] + public long Id { get; set; } + + /// Interval between scheduled ticks, in milliseconds (relative-interval schedules). + [JsonConverter(typeof(MillisecondsTimeSpanConverter))] + [JsonPropertyName("intervalMs")] + public TimeSpan? Interval { get; set; } + + /// ISO 8601 timestamp when the next tick is scheduled to fire. + [JsonPropertyName("nextRunAt")] + public DateTimeOffset NextRunAt { get; set; } + + /// Prompt text that gets enqueued on every tick. + [JsonPropertyName("prompt")] + public string Prompt { get; set; } = string.Empty; + + /// Whether the schedule re-arms after each tick (`/every`) or fires once (`/after`). + [JsonPropertyName("recurring")] + public bool Recurring { get; set; } + + /// True for a self-paced (`dynamic`) schedule: no fixed cadence; the model arms each next run via the `manage_schedule` `wakeup` action. `nextRunAt` is model-controlled. + [JsonPropertyName("selfPaced")] + public bool? SelfPaced { get; set; } + + /// IANA timezone the `cron` expression is evaluated in. + [JsonPropertyName("tz")] + public string? Tz { get; set; } +} + +/// Snapshot of the currently active recurring prompts for this session. +[Experimental(Diagnostics.Experimental)] +public sealed class ScheduleList +{ + /// Active scheduled prompts, ordered by id. + [JsonPropertyName("entries")] + public IList Entries { get => field ??= []; set; } +} + +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionScheduleListRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionScheduleHydrateRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Whether the session currently has an active self-paced schedule. +[Experimental(Diagnostics.Experimental)] +internal sealed class ScheduleHasSelfPacedResult +{ + /// True when at least one active schedule is self-paced. + [JsonPropertyName("hasSelfPaced")] + public bool HasSelfPaced { get; set; } +} + +/// Identifies the target session. +[Experimental(Diagnostics.Experimental)] +internal sealed class SessionScheduleHasSelfPacedRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Result of registering or re-arming a scheduled prompt. +[Experimental(Diagnostics.Experimental)] +internal sealed class ScheduleAddResult +{ + /// The registered or updated schedule entry. + [JsonPropertyName("entry")] + public ScheduleEntry? Entry { get; set; } + + /// User-facing validation error, when registration failed. + [JsonPropertyName("error")] + public string? Error { get; set; } +} + +/// Register a relative-interval scheduled prompt. +[Experimental(Diagnostics.Experimental)] +internal sealed class ScheduleAddRequest +{ + /// Optional display-only prompt label. + [JsonPropertyName("displayPrompt")] + public string? DisplayPrompt { get; set; } + + /// Human-readable interval such as `30s`, `5m`, or `2h`. + [JsonPropertyName("interval")] + public string Interval { get; set; } = string.Empty; + + /// Prompt text to enqueue when the schedule fires. + [JsonPropertyName("prompt")] + public string Prompt { get; set; } = string.Empty; + + /// Whether the schedule should re-arm after each tick. Defaults to true. + [JsonPropertyName("recurring")] + public bool? Recurring { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Register a cron scheduled prompt. +[Experimental(Diagnostics.Experimental)] +internal sealed class ScheduleAddCronRequest +{ + /// 5-field cron expression. + [JsonPropertyName("cron")] + public string Cron { get; set; } = string.Empty; + + /// Optional display-only prompt label. + [JsonPropertyName("displayPrompt")] + public string? DisplayPrompt { get; set; } + + /// Prompt text to enqueue when the schedule fires. + [JsonPropertyName("prompt")] + public string Prompt { get; set; } = string.Empty; + + /// Whether the schedule should re-arm after each tick. Defaults to true. + [JsonPropertyName("recurring")] + public bool? Recurring { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + + /// IANA timezone for evaluating the cron expression. + [JsonPropertyName("tz")] + public string? Tz { get; set; } +} + +/// Register an absolute-time scheduled prompt. +[Experimental(Diagnostics.Experimental)] +internal sealed class ScheduleAddAtRequest +{ + /// Epoch milliseconds when the prompt should fire. + [JsonPropertyName("at")] + public long At { get; set; } + + /// Optional display-only prompt label. + [JsonPropertyName("displayPrompt")] + public string? DisplayPrompt { get; set; } + + /// Prompt text to enqueue when the schedule fires. + [JsonPropertyName("prompt")] + public string Prompt { get; set; } = string.Empty; + + /// Whether the schedule should re-arm after each tick. Defaults to false. + [JsonPropertyName("recurring")] + public bool? Recurring { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Register a self-paced scheduled prompt. +[Experimental(Diagnostics.Experimental)] +internal sealed class ScheduleAddSelfPacedRequest +{ + /// Optional display-only prompt label. + [JsonPropertyName("displayPrompt")] + public string? DisplayPrompt { get; set; } + + /// Prompt text to enqueue when the schedule fires. + [JsonPropertyName("prompt")] + public string Prompt { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Re-arm a self-paced scheduled prompt. +[Experimental(Diagnostics.Experimental)] +internal sealed class ScheduleRearmSelfPacedRequest +{ + /// Epoch milliseconds when the prompt should next fire. + [JsonPropertyName("at")] + public long At { get; set; } + + /// Id of the self-paced scheduled prompt. + [JsonPropertyName("id")] + public long Id { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Remove a scheduled prompt by id. The result entry is omitted if the id was unknown. +[Experimental(Diagnostics.Experimental)] +public sealed class ScheduleStopResult +{ + /// The removed entry, or omitted if no entry matched. + [JsonPropertyName("entry")] + public ScheduleEntry? Entry { get; set; } +} + +/// Identifier of the scheduled prompt to remove. +[Experimental(Diagnostics.Experimental)] +internal sealed class ScheduleStopRequest +{ + /// Id of the scheduled prompt to remove. + [JsonPropertyName("id")] + public long Id { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// A bearer token supplied by the SDK client for a BYOK provider. The runtime sets it as `Authorization: Bearer <token>` on the outbound request and does no caching; the SDK consumer owns token caching and refresh. +[Experimental(Diagnostics.Experimental)] +public sealed class ProviderTokenAcquireResult +{ + /// The bearer token value (without the `Bearer ` prefix). + [JsonPropertyName("token")] + public string Token { get; set; } = string.Empty; +} + +/// Asks the SDK client to acquire a bearer token for a BYOK provider whose config set `hasBearerTokenProvider: true`. Issued by the runtime before each outbound model request; the runtime does no caching, so this is sent once per request. +[Experimental(Diagnostics.Experimental)] +public sealed class ProviderTokenAcquireRequest +{ + /// Name of the BYOK provider needing a token. For the legacy whole-session `provider` this is the implicit provider name; for named providers it is `NamedProviderConfig.name`. + [JsonPropertyName("providerName")] + public string ProviderName { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Result returned by an extension factory closure. +[Experimental(Diagnostics.Experimental)] +public sealed class FactoryExecuteResult +{ + /// Factory result value. + [JsonPropertyName("result")] + public JsonElement? Result { get; set; } +} + +/// Parameters sent to the owning extension to execute a factory closure. +[Experimental(Diagnostics.Experimental)] +public sealed class FactoryExecuteRequest +{ + /// Factory input value. + [JsonPropertyName("args")] + public JsonElement Args { get; set; } + + /// Opaque token identifying this factory execution attempt. + [JsonPropertyName("executionToken")] + public string ExecutionToken { get; set; } = string.Empty; + + /// Registered factory name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Factory run identifier. + [JsonPropertyName("runId")] + public string RunId { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Parameters for cooperatively aborting a factory body. +[Experimental(Diagnostics.Experimental)] +public sealed class FactoryAbortRequest +{ + /// Factory run identifier. + [JsonPropertyName("runId")] + public string RunId { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Describes a filesystem error. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionFsError +{ + /// Error classification. + [JsonPropertyName("code")] + public SessionFsErrorCode Code { get; set; } + + /// Free-form detail about the error, for logging/diagnostics. + [JsonPropertyName("message")] + public string? Message { get; set; } +} + +/// File content as a UTF-8 string, or a filesystem error if the read failed. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionFsReadFileResult +{ + /// File content as UTF-8 string. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; + + /// Describes a filesystem error. + [JsonPropertyName("error")] + public SessionFsError? Error { get; set; } +} + +/// Path of the file to read from the client-provided session filesystem. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionFsReadFileRequest +{ + /// Path using SessionFs conventions. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// File path, content to write, and optional mode for the client-provided session filesystem. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionFsWriteFileRequest +{ + /// Content to write. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; + + /// Optional POSIX-style mode for newly created files. + [JsonPropertyName("mode")] + public long? Mode { get; set; } + + /// Path using SessionFs conventions. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// File path, content to append, and optional mode for the client-provided session filesystem. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionFsAppendFileRequest +{ + /// Content to append. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; + + /// Optional POSIX-style mode for newly created files. + [JsonPropertyName("mode")] + public long? Mode { get; set; } + + /// Path using SessionFs conventions. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Indicates whether the requested path exists in the client-provided session filesystem. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionFsExistsResult +{ + /// Whether the path exists. + [JsonPropertyName("exists")] + public bool Exists { get; set; } +} + +/// Path to test for existence in the client-provided session filesystem. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionFsExistsRequest +{ + /// Path using SessionFs conventions. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Filesystem metadata for the requested path, or a filesystem error if the stat failed. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionFsStatResult +{ + /// ISO 8601 timestamp of creation. + [JsonPropertyName("birthtime")] + public DateTimeOffset Birthtime { get; set; } + + /// Describes a filesystem error. + [JsonPropertyName("error")] + public SessionFsError? Error { get; set; } + + /// Whether the path is a directory. + [JsonPropertyName("isDirectory")] + public bool IsDirectory { get; set; } + + /// Whether the path is a file. + [JsonPropertyName("isFile")] + public bool IsFile { get; set; } + + /// ISO 8601 timestamp of last modification. + [JsonPropertyName("mtime")] + public DateTimeOffset Mtime { get; set; } + + /// File size in bytes. + [JsonPropertyName("size")] + public long Size { get; set; } +} + +/// Path whose metadata should be returned from the client-provided session filesystem. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionFsStatRequest +{ + /// Path using SessionFs conventions. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Directory path to create in the client-provided session filesystem, with options for recursive creation and POSIX mode. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionFsMkdirRequest +{ + /// Optional POSIX-style mode for newly created directories. + [JsonPropertyName("mode")] + public long? Mode { get; set; } + + /// Path using SessionFs conventions. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; + + /// Create parent directories as needed. + [JsonPropertyName("recursive")] + public bool? Recursive { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Names of entries in the requested directory, or a filesystem error if the read failed. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionFsReaddirResult +{ + /// Entry names in the directory. + [JsonPropertyName("entries")] + public IList Entries { get => field ??= []; set; } + + /// Describes a filesystem error. + [JsonPropertyName("error")] + public SessionFsError? Error { get; set; } +} + +/// Directory path whose entries should be listed from the client-provided session filesystem. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionFsReaddirRequest +{ + /// Path using SessionFs conventions. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Directory entry returned by session filesystem `readdirWithTypes`, with name and entry type. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionFsReaddirWithTypesEntry +{ + /// Entry name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Entry type. + [JsonPropertyName("type")] + public SessionFsReaddirWithTypesEntryType Type { get; set; } +} + +/// Entries in the requested directory paired with file/directory type information, or a filesystem error if the read failed. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionFsReaddirWithTypesResult +{ + /// Directory entries with type information. + [JsonPropertyName("entries")] + public IList Entries { get => field ??= []; set; } + + /// Describes a filesystem error. + [JsonPropertyName("error")] + public SessionFsError? Error { get; set; } +} + +/// Directory path whose entries (with type information) should be listed from the client-provided session filesystem. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionFsReaddirWithTypesRequest +{ + /// Path using SessionFs conventions. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Path to remove from the client-provided session filesystem, with options for recursive removal and force. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionFsRmRequest +{ + /// Ignore errors if the path does not exist. + [JsonPropertyName("force")] + public bool? Force { get; set; } + + /// Path using SessionFs conventions. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; + + /// Remove directories and their contents recursively. + [JsonPropertyName("recursive")] + public bool? Recursive { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Source and destination paths for renaming or moving an entry in the client-provided session filesystem. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionFsRenameRequest +{ + /// Destination path using SessionFs conventions. + [JsonPropertyName("dest")] + public string Dest { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + + /// Source path using SessionFs conventions. + [JsonPropertyName("src")] + public string Src { get; set; } = string.Empty; +} + +/// Query results including rows, columns, and rows affected, or a filesystem error if execution failed. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionFsSqliteQueryResult +{ + /// Column names from the result set. + [JsonPropertyName("columns")] + public IList Columns { get => field ??= []; set; } + + /// Describes a filesystem error. + [JsonPropertyName("error")] + public SessionFsError? Error { get; set; } + + /// SQLite last_insert_rowid() value for INSERT. + [JsonPropertyName("lastInsertRowid")] + public long? LastInsertRowid { get; set; } + + /// For SELECT: array of row objects. For others: empty array. + [JsonPropertyName("rows")] + public IList> Rows { get => field ??= []; set; } + + /// Number of rows affected (for INSERT/UPDATE/DELETE). + [JsonPropertyName("rowsAffected")] + public long RowsAffected { get; set; } +} + +/// SQL query, query type, and optional bind parameters for executing a SQLite query against the per-session database. The provider applies its SQLite busy timeout for every call. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionFsSqliteQueryRequest +{ + /// Optional named bind parameters. + [JsonPropertyName("params")] + public IDictionary? Params { get; set; } + + /// SQL query to execute. + [JsonPropertyName("query")] + public string Query { get; set; } = string.Empty; + + /// How to execute the query: 'exec' for DDL/multi-statement (no results), 'query' for SELECT (returns rows), 'run' for INSERT/UPDATE/DELETE (returns rowsAffected). + [JsonPropertyName("queryType")] + public SessionFsSqliteQueryType QueryType { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Classified SQLite transaction failure. busyOrLocked guarantees rollback; postCommitAmbiguous must never be retried. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionFsSqliteTransactionError +{ + /// Gets or sets the errorClass value. + [JsonPropertyName("errorClass")] + public SessionFsSqliteTransactionErrorClass ErrorClass { get; set; } + + /// Gets or sets the message value. + [JsonPropertyName("message")] + public string Message { get; set; } = string.Empty; +} + +/// Per-statement results, or a classified transaction error. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionFsSqliteTransactionResult +{ + /// Gets or sets the error value. + [JsonPropertyName("error")] + public SessionFsSqliteTransactionError? Error { get; set; } + + /// Gets or sets the results value. + [JsonPropertyName("results")] + public IList Results { get => field ??= []; set; } +} + +/// One statement in an atomic SQLite transaction. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionFsSqliteTransactionStatement +{ + /// Optional named bind parameters. + [JsonPropertyName("params")] + public IDictionary? Params { get; set; } + + /// SQL statement to execute. + [JsonPropertyName("query")] + public string Query { get; set; } = string.Empty; + + /// How to execute the statement. + [JsonPropertyName("queryType")] + public SessionFsSqliteQueryType QueryType { get; set; } +} + +/// Statements to execute atomically. Providers apply busy handling for every call. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionFsSqliteTransactionRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + + /// Gets or sets the statements value. + [JsonPropertyName("statements")] + public IList Statements { get => field ??= []; set; } +} + +/// Indicates whether the per-session SQLite database already exists. +[Experimental(Diagnostics.Experimental)] +public sealed class SessionFsSqliteExistsResult +{ + /// Whether the session database already exists. + [JsonPropertyName("exists")] + public bool Exists { get; set; } +} + +/// Identifies the target session. +public sealed class SessionFsSqliteExistsRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Canvas open result returned by the provider. +[Experimental(Diagnostics.Experimental)] +public sealed class CanvasProviderOpenResult +{ + /// Provider-supplied status text. + [JsonPropertyName("status")] + public string? Status { get; set; } + + /// Provider-supplied title. + [JsonPropertyName("title")] + public string? Title { get; set; } + + /// URL for web-rendered canvases. + [JsonPropertyName("url")] + public string? Url { get; set; } +} + +/// Host capabilities. +[Experimental(Diagnostics.Experimental)] +public sealed class CanvasHostContextCapabilities +{ + /// Whether canvas rendering is supported. + [JsonPropertyName("canvases")] + public bool? Canvases { get; set; } +} + +/// Host context supplied by the runtime. +[Experimental(Diagnostics.Experimental)] +public sealed class CanvasHostContext +{ + /// Host capabilities. + [JsonPropertyName("capabilities")] + public CanvasHostContextCapabilities? Capabilities { get; set; } +} + +/// Session context supplied by the runtime. +[Experimental(Diagnostics.Experimental)] +public sealed class CanvasSessionContext +{ + /// Active session working directory, when known. + [JsonPropertyName("workingDirectory")] + public string? WorkingDirectory { get; set; } +} + +/// Canvas open parameters sent to the provider. +[Experimental(Diagnostics.Experimental)] +public sealed class CanvasProviderOpenRequest +{ + /// Provider-local canvas identifier. + [JsonPropertyName("canvasId")] + public string CanvasId { get; set; } = string.Empty; + + /// Owning provider identifier. + [JsonPropertyName("extensionId")] + public string ExtensionId { get; set; } = string.Empty; + + /// Host context supplied by the runtime. + [JsonPropertyName("host")] + public CanvasHostContext? Host { get; set; } + + /// Canvas open input. + [JsonPropertyName("input")] + public JsonElement? Input { get; set; } + + /// Stable caller-supplied canvas instance identifier. + [JsonPropertyName("instanceId")] + public string InstanceId { get; set; } = string.Empty; + + /// Session context supplied by the runtime. + [JsonPropertyName("session")] + public CanvasSessionContext? Session { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Canvas close parameters sent to the provider. +[Experimental(Diagnostics.Experimental)] +public sealed class CanvasProviderCloseRequest +{ + /// Provider-local canvas identifier. + [JsonPropertyName("canvasId")] + public string CanvasId { get; set; } = string.Empty; + + /// Owning provider identifier. + [JsonPropertyName("extensionId")] + public string ExtensionId { get; set; } = string.Empty; + + /// Host context supplied by the runtime. + [JsonPropertyName("host")] + public CanvasHostContext? Host { get; set; } + + /// Canvas instance identifier. + [JsonPropertyName("instanceId")] + public string InstanceId { get; set; } = string.Empty; + + /// Session context supplied by the runtime. + [JsonPropertyName("session")] + public CanvasSessionContext? Session { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Canvas action invocation parameters sent to the provider. +[Experimental(Diagnostics.Experimental)] +public sealed class CanvasProviderInvokeActionRequest +{ + /// Action name to invoke. + [JsonPropertyName("actionName")] + public string ActionName { get; set; } = string.Empty; + + /// Provider-local canvas identifier. + [JsonPropertyName("canvasId")] + public string CanvasId { get; set; } = string.Empty; + + /// Owning provider identifier. + [JsonPropertyName("extensionId")] + public string ExtensionId { get; set; } = string.Empty; + + /// Host context supplied by the runtime. + [JsonPropertyName("host")] + public CanvasHostContext? Host { get; set; } + + /// Action input. + [JsonPropertyName("input")] + public JsonElement? Input { get; set; } + + /// Canvas instance identifier. + [JsonPropertyName("instanceId")] + public string InstanceId { get; set; } = string.Empty; + + /// Session context supplied by the runtime. + [JsonPropertyName("session")] + public CanvasSessionContext? Session { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Opaque integrator-owned process launch profile for one extension entrypoint. +[Experimental(Diagnostics.Experimental)] +public sealed class ExtensionLaunchProfile +{ + /// Opaque integrator-defined arguments passed to the executable. The runtime does not append the extension entrypoint. + [JsonPropertyName("args")] + public IList Args { get => field ??= []; set; } + + /// Opaque integrator-defined environment variables. Runtime-owned COPILOT_SDK_PATH, SESSION_ID, and COPILOT_EXTENSION_PARENT_PID values take precedence. + [JsonPropertyName("env")] + public IDictionary Env { get => field ??= new Dictionary(); set; } + + /// Executable used to launch the extension entrypoint. + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] + [MinLength(1)] + [JsonPropertyName("executable")] + public string Executable { get; set; } = string.Empty; +} + +/// The launch profile for a supported entrypoint. Omit launch when the provider does not support the entrypoint. +[Experimental(Diagnostics.Experimental)] +public sealed class ExtensionLaunchProviderResolveResult +{ + /// Opaque launch profile, omitted when this provider does not support the entrypoint. + [JsonPropertyName("launch")] + public ExtensionLaunchProfile? Launch { get; set; } +} + +/// A discovered extension entrypoint that the registered integrator may classify and resolve to an opaque launch profile. +[Experimental(Diagnostics.Experimental)] +public sealed class ExtensionLaunchProviderResolveRequest +{ + /// Source-qualified extension identifier. + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; + + /// Absolute path to the discovered extension entrypoint. + [JsonPropertyName("modulePath")] + public string ModulePath { get; set; } = string.Empty; + + /// Human-readable extension name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Discovery source for the extension entrypoint. + [JsonPropertyName("source")] + public ExtensionSource Source { get; set; } +} + +/// Acknowledgement. Returning successfully simply means the SDK accepted the start frame; it does not imply the request will succeed. +[Experimental(Diagnostics.Experimental)] +public sealed class LlmInferenceHttpRequestStartResult +{ +} + +/// The head of an outbound model-layer HTTP request. +[Experimental(Diagnostics.Experimental)] +public sealed class LlmInferenceHttpRequestStartRequest +{ + /// Stable identity of the agent trajectory that issued this request. Present when the request originates from an agent turn; absent for requests outside any agent context. This is the same identity used by lifecycle and bridged session events and remains constant across turns and retries. + [JsonPropertyName("agentId")] + public string? AgentId { get; set; } + + /// Identity of the agent invocation (one agentic loop) that issued this request. It remains fixed across physical retries within the invocation and is distinct from the stable trajectory `agentId`. A caller-supplied invocation id always takes precedence (this covers auxiliary calls that have no model call id). Otherwise, first-party CAPI requests fall back to the runtime's agent task id — the same value the runtime emits as the `X-Agent-Task-Id` header — while custom-provider requests fall back to the model call id. + [JsonPropertyName("agentInvocationId")] + public string? AgentInvocationId { get; set; } + + /// Gets or sets the headers value. + [JsonPropertyName("headers")] + public IDictionary> Headers { get => field ??= new Dictionary>(); set; } + + /// Coarse classification of the interaction that produced this request. Open string for forward-compatibility; known values include `conversation-agent`, `conversation-subagent`, `conversation-sampling`, `conversation-background`, `conversation-compaction`, and `conversation-user`. Absent when the runtime did not classify the request. Comes from the runtime's per-request agent context independently of transport; on the CAPI transport the runtime derives the upstream `X-Interaction-Type` header from this same context. + [JsonPropertyName("interactionType")] + public string? InteractionType { get; set; } + + /// HTTP method, e.g. GET, POST. + [JsonPropertyName("method")] + public string Method { get; set; } = string.Empty; + + /// Stable identity of the immediate parent trajectory. Present for child trajectories such as subagents and conversation-sampling requests; absent for root-agent and non-agent requests. + [JsonPropertyName("parentAgentId")] + public string? ParentAgentId { get; set; } + + /// Opaque runtime-minted id, unique per in-flight request. The SDK uses this to correlate httpRequestChunk frames and to address its httpResponseStart / httpResponseChunk replies back to the runtime. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; + + /// Id of the runtime session that triggered this request, when one is in scope. Absent for requests issued outside any session (e.g. startup model-catalog or capability resolution). This is a payload field — not a dispatch key — because the client-global API is registered process-wide rather than per session. + [JsonPropertyName("sessionId")] + public string? SessionId { get; set; } + + /// Transport the runtime would otherwise use for this request. `http` (the default when absent) covers plain HTTP and SSE responses; `websocket` indicates a full-duplex message channel where each body chunk maps to one WebSocket message and the `binary` flag distinguishes text from binary frames. The SDK consumer uses this to decide whether to service the request with an HTTP client or a WebSocket client. It is the one piece of request metadata the consumer cannot reliably infer from the URL or headers alone. + [JsonPropertyName("transport")] + public LlmInferenceHttpRequestStartTransport? Transport { get; set; } + + /// Absolute request URL. + [JsonPropertyName("url")] + public string Url { get; set; } = string.Empty; +} + +/// Acknowledgement. The SDK is free to ignore the ack and treat chunk delivery as fire-and-forget. +[Experimental(Diagnostics.Experimental)] +public sealed class LlmInferenceHttpRequestChunkResult +{ +} + +/// A request body chunk or cancellation signal. +[Experimental(Diagnostics.Experimental)] +public sealed class LlmInferenceHttpRequestChunkRequest +{ + /// Identity of the agent invocation (one agentic loop) this body chunk belongs to, matching the `agentInvocationId` semantics on httpRequestStart. Carried per chunk so a persistent transport can attribute successive turns correctly: when a WebSocket connection is reused across turns, the httpRequestStart identity reflects only the turn that opened the connection, so each later turn stamps its own invocation id here. Absent when the runtime has no invocation context for the request, or on the plain-HTTP transport where every request has its own httpRequestStart. + [JsonPropertyName("agentInvocationId")] + public string? AgentInvocationId { get; set; } + + /// When true, `data` is base64-encoded bytes. When absent or false, `data` is UTF-8 text. + [JsonPropertyName("binary")] + public bool? Binary { get; set; } + + /// When true, the runtime is cancelling the in-flight request (e.g. upstream consumer aborted). `data` is ignored. Implies end-of-request. + [JsonPropertyName("cancel")] + public bool? Cancel { get; set; } + + /// Optional human-readable reason for the cancellation, propagated for logging. + [JsonPropertyName("cancelReason")] + public string? CancelReason { get; set; } + + /// Body byte range. UTF-8 text when `binary` is absent or false; base64-encoded bytes when `binary` is true. May be empty. + [JsonPropertyName("data")] + public string Data { get; set; } = string.Empty; + + /// When true, this is the final body chunk for the request. The SDK may rely on having received an end-marked chunk before treating the request body as complete. + [JsonPropertyName("end")] + public bool? End { get; set; } + + /// Matches the requestId from the originating httpRequestStart frame. + [JsonPropertyName("requestId")] + public string RequestId { get; set; } = string.Empty; +} + +/// Client environment metadata describing the process that produced a telemetry event. +[Experimental(Diagnostics.Experimental)] +public sealed class GitHubTelemetryClientInfo +{ + /// Copilot CLI version string. + [JsonPropertyName("cli_version")] + public string CliVersion { get; set; } = string.Empty; + + /// Name of the client application. + [JsonPropertyName("client_name")] + public string? ClientName { get; set; } + + /// Type of client. + [JsonPropertyName("client_type")] + public string? ClientType { get; set; } + + /// Copilot subscription plan, when known. + [JsonPropertyName("copilot_plan")] + public string? CopilotPlan { get; set; } + + /// Stable machine identifier for the device. + [JsonPropertyName("dev_device_id")] + public string? DevDeviceId { get; set; } + + /// Whether the user is a GitHub/Microsoft staff member. + [JsonPropertyName("is_staff")] + public bool? IsStaff { get; set; } + + /// Node.js runtime version string. + [JsonPropertyName("node_version")] + public string NodeVersion { get; set; } = string.Empty; + + /// Operating system architecture (e.g. arm64, x64). + [JsonPropertyName("os_arch")] + public string OsArch { get; set; } = string.Empty; + + /// Operating system platform (e.g. darwin, linux, win32). + [JsonPropertyName("os_platform")] + public string OsPlatform { get; set; } = string.Empty; + + /// Operating system version string. + [JsonPropertyName("os_version")] + public string OsVersion { get; set; } = string.Empty; +} + +/// A single telemetry event in the runtime's native GitHub-shaped telemetry format, forwarded verbatim to opted-in hosts. The `restricted` flag on the enclosing GitHubTelemetryNotification distinguishes standard from restricted events; the payload shape is identical for both. +[Experimental(Diagnostics.Experimental)] +public sealed class GitHubTelemetryEvent +{ + /// Client environment metadata. + [JsonPropertyName("client")] + public GitHubTelemetryClientInfo? Client { get; set; } + + /// Copilot tracking ID for user-level attribution. + [JsonPropertyName("copilot_tracking_id")] + public string? CopilotTrackingId { get; set; } + + /// Timestamp when the event was created (ISO 8601 format). + [JsonPropertyName("created_at")] + public string? CreatedAt { get; set; } + + /// Experiment assignment context. + [JsonPropertyName("exp_assignment_context")] + public string? ExpAssignmentContext { get; set; } + + /// Feature flags enabled for this session, as a map from flag to value. + [JsonPropertyName("features")] + public IDictionary? Features { get; set; } + + /// Event type/kind (e.g. get_completion_with_tools_turn, tool_call_executed). + [JsonPropertyName("kind")] + public string Kind { get; set; } = string.Empty; + + /// Numeric metrics as a map from key to value. + [JsonPropertyName("metrics")] + public IDictionary Metrics { get => field ??= new Dictionary(); set; } + + /// Reference to the model call that produced this event. + [JsonPropertyName("model_call_id")] + public string? ModelCallId { get; set; } + + /// String-valued properties as a map from key to value. + [JsonPropertyName("properties")] + public IDictionary Properties { get => field ??= new Dictionary(); set; } + + /// Session identifier the event belongs to. + [JsonPropertyName("session_id")] + public string? SessionId { get; set; } +} + +/// Payload for a `gitHubTelemetry.event` notification: a single GitHub telemetry event the runtime forwards to a host connection that opted into telemetry forwarding during the `server.connect` handshake. +[Experimental(Diagnostics.Experimental)] +public sealed class GitHubTelemetryNotification +{ + /// The telemetry event, in the runtime's native GitHub-shaped telemetry format. + [JsonPropertyName("event")] + public GitHubTelemetryEvent Event { get => field ??= new(); set; } + + /// Whether this is a restricted telemetry event (cli.restricted_telemetry). Hosts must route restricted events to first-party Microsoft stores only. + [JsonPropertyName("restricted")] + public bool Restricted { get; set; } + + /// Session the telemetry event belongs to, when it is session-scoped. Omitted for sessionless events (for example, `server.sendTelemetry` calls with no session id), which are still forwarded to opted-in connections. + [JsonPropertyName("sessionId")] + public string? SessionId { get; set; } +} + +/// Resolved Anthropic adaptive-thinking capability for a model. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct AdaptiveThinkingSupport : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public AdaptiveThinkingSupport(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// The model does not accept thinking.type='adaptive'. + public static AdaptiveThinkingSupport Unsupported { get; } = new("unsupported"); + + /// The model accepts adaptive thinking but also accepts thinking.type='enabled'. + public static AdaptiveThinkingSupport Optional { get; } = new("optional"); + + /// The model only accepts adaptive thinking and rejects thinking.type='enabled' with HTTP 400 (e.g. opus-4.7/4.8). + public static AdaptiveThinkingSupport Required { get; } = new("required"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(AdaptiveThinkingSupport left, AdaptiveThinkingSupport right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(AdaptiveThinkingSupport left, AdaptiveThinkingSupport right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is AdaptiveThinkingSupport other && Equals(other); + + /// + public bool Equals(AdaptiveThinkingSupport other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override AdaptiveThinkingSupport Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, AdaptiveThinkingSupport value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AdaptiveThinkingSupport)); + } + } +} + + +/// Model capability category for grouping in the model picker. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct ModelPickerCategory : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public ModelPickerCategory(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Lightweight model category optimized for faster, lower-cost interactions. + public static ModelPickerCategory Lightweight { get; } = new("lightweight"); + + /// Versatile model category suitable for a broad range of tasks. + public static ModelPickerCategory Versatile { get; } = new("versatile"); + + /// Powerful model category optimized for complex tasks. + public static ModelPickerCategory Powerful { get; } = new("powerful"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ModelPickerCategory left, ModelPickerCategory right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ModelPickerCategory left, ModelPickerCategory right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is ModelPickerCategory other && Equals(other); + + /// + public bool Equals(ModelPickerCategory other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override ModelPickerCategory Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, ModelPickerCategory value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ModelPickerCategory)); + } + } +} + + +/// Relative cost tier for token-based billing users. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct ModelPickerPriceCategory : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public ModelPickerPriceCategory(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Lowest relative token cost tier. + public static ModelPickerPriceCategory Low { get; } = new("low"); + + /// Medium relative token cost tier. + public static ModelPickerPriceCategory Medium { get; } = new("medium"); + + /// High relative token cost tier. + public static ModelPickerPriceCategory High { get; } = new("high"); + + /// Highest relative token cost tier. + public static ModelPickerPriceCategory VeryHigh { get; } = new("very_high"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ModelPickerPriceCategory left, ModelPickerPriceCategory right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ModelPickerPriceCategory left, ModelPickerPriceCategory right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is ModelPickerPriceCategory other && Equals(other); + + /// + public bool Equals(ModelPickerPriceCategory other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override ModelPickerPriceCategory Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, ModelPickerPriceCategory value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ModelPickerPriceCategory)); + } + } +} + + +/// Current policy state for this model. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct ModelPolicyState : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public ModelPolicyState(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// The model is enabled by policy. + public static ModelPolicyState Enabled { get; } = new("enabled"); + + /// The model is disabled by policy. + public static ModelPolicyState Disabled { get; } = new("disabled"); + + /// No explicit policy is configured for the model. + public static ModelPolicyState Unconfigured { get; } = new("unconfigured"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ModelPolicyState left, ModelPolicyState right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ModelPolicyState left, ModelPolicyState right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is ModelPolicyState other && Equals(other); + + /// + public bool Equals(ModelPolicyState other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override ModelPolicyState Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, ModelPolicyState value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ModelPolicyState)); + } + } +} + + +/// Server transport type: stdio, http, sse (deprecated), or memory. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct DiscoveredMcpServerType : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public DiscoveredMcpServerType(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Server communicates over stdio with a local child process. + public static DiscoveredMcpServerType Stdio { get; } = new("stdio"); + + /// Server communicates over streamable HTTP. + public static DiscoveredMcpServerType Http { get; } = new("http"); + + /// Server communicates over Server-Sent Events (deprecated). + public static DiscoveredMcpServerType Sse { get; } = new("sse"); + + /// Server is backed by an in-memory runtime implementation. + public static DiscoveredMcpServerType Memory { get; } = new("memory"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(DiscoveredMcpServerType left, DiscoveredMcpServerType right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(DiscoveredMcpServerType left, DiscoveredMcpServerType right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is DiscoveredMcpServerType other && Equals(other); + + /// + public bool Equals(DiscoveredMcpServerType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override DiscoveredMcpServerType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, DiscoveredMcpServerType value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(DiscoveredMcpServerType)); + } + } +} + + +/// Persisted extension discovery source. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct DiscoveredExtensionSource : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public DiscoveredExtensionSource(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Extension discovered from the user's extensions directory. + public static DiscoveredExtensionSource User { get; } = new("user"); + + /// Extension contributed by an installed plugin. + public static DiscoveredExtensionSource Plugin { get; } = new("plugin"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(DiscoveredExtensionSource left, DiscoveredExtensionSource right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(DiscoveredExtensionSource left, DiscoveredExtensionSource right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is DiscoveredExtensionSource other && Equals(other); + + /// + public bool Equals(DiscoveredExtensionSource other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override DiscoveredExtensionSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, DiscoveredExtensionSource value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(DiscoveredExtensionSource)); + } + } +} + + +/// Effective extension loading and agent-management mode. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct DiscoveredExtensionMode : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public DiscoveredExtensionMode(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Extensions are not loaded. + public static DiscoveredExtensionMode Disabled { get; } = new("disabled"); + + /// Extensions are loaded, but the agent cannot create, reload, or manage them. + public static DiscoveredExtensionMode LoadOnly { get; } = new("load_only"); + + /// Extensions are loaded and the agent can create, reload, and manage them. + public static DiscoveredExtensionMode LoadAndAugment { get; } = new("load_and_augment"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(DiscoveredExtensionMode left, DiscoveredExtensionMode right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(DiscoveredExtensionMode left, DiscoveredExtensionMode right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is DiscoveredExtensionMode other && Equals(other); + + /// + public bool Equals(DiscoveredExtensionMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override DiscoveredExtensionMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, DiscoveredExtensionMode value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(DiscoveredExtensionMode)); + } + } +} + + +/// Which tier this directory belongs to. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct SkillDiscoveryScope : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public SkillDiscoveryScope(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// A project's repository skill directory. + public static SkillDiscoveryScope Project { get; } = new("project"); + + /// The user's personal Copilot skill directory. + public static SkillDiscoveryScope PersonalCopilot { get; } = new("personal-copilot"); + + /// The user's personal agents skill directory. + public static SkillDiscoveryScope PersonalAgents { get; } = new("personal-agents"); + + /// A configured custom skill directory. + public static SkillDiscoveryScope Custom { get; } = new("custom"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SkillDiscoveryScope left, SkillDiscoveryScope right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SkillDiscoveryScope left, SkillDiscoveryScope right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is SkillDiscoveryScope other && Equals(other); + + /// + public bool Equals(SkillDiscoveryScope other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override SkillDiscoveryScope Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, SkillDiscoveryScope value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SkillDiscoveryScope)); + } + } +} + + +/// Where the agent definition was loaded from. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct AgentInfoSource : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public AgentInfoSource(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Agent loaded from the user's personal agent configuration. + public static AgentInfoSource User { get; } = new("user"); + + /// Agent loaded from the current project's repository configuration. + public static AgentInfoSource Project { get; } = new("project"); + + /// Agent inherited from a parent project or workspace. + public static AgentInfoSource Inherited { get; } = new("inherited"); + + /// Agent provided by a remote runtime or service. + public static AgentInfoSource Remote { get; } = new("remote"); + + /// Agent contributed by an installed plugin. + public static AgentInfoSource Plugin { get; } = new("plugin"); + + /// Agent built into the Copilot runtime. + public static AgentInfoSource Builtin { get; } = new("builtin"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(AgentInfoSource left, AgentInfoSource right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(AgentInfoSource left, AgentInfoSource right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is AgentInfoSource other && Equals(other); + + /// + public bool Equals(AgentInfoSource other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override AgentInfoSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, AgentInfoSource value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentInfoSource)); + } + } +} + + +/// Which tier this directory belongs to. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct AgentDiscoveryPathScope : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public AgentDiscoveryPathScope(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// The user's personal agent configuration directory. + public static AgentDiscoveryPathScope User { get; } = new("user"); + + /// A project's repository agent directory. + public static AgentDiscoveryPathScope Project { get; } = new("project"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(AgentDiscoveryPathScope left, AgentDiscoveryPathScope right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(AgentDiscoveryPathScope left, AgentDiscoveryPathScope right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is AgentDiscoveryPathScope other && Equals(other); + + /// + public bool Equals(AgentDiscoveryPathScope other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override AgentDiscoveryPathScope Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, AgentDiscoveryPathScope value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentDiscoveryPathScope)); + } + } +} + + +/// Where this source lives — used for UI grouping. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct InstructionSourceLocation : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public InstructionSourceLocation(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Instructions live in user-level configuration. + public static InstructionSourceLocation User { get; } = new("user"); + + /// Instructions live in repository-level configuration. + public static InstructionSourceLocation Repository { get; } = new("repository"); + + /// Instructions live under the current working directory. + public static InstructionSourceLocation WorkingDirectory { get; } = new("working-directory"); + + /// Instructions live in plugin-provided configuration. + public static InstructionSourceLocation Plugin { get; } = new("plugin"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(InstructionSourceLocation left, InstructionSourceLocation right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(InstructionSourceLocation left, InstructionSourceLocation right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is InstructionSourceLocation other && Equals(other); + + /// + public bool Equals(InstructionSourceLocation other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override InstructionSourceLocation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, InstructionSourceLocation value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(InstructionSourceLocation)); + } + } +} + + +/// Category of instruction source — used for merge logic. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct InstructionSourceType : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public InstructionSourceType(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Instructions loaded from the user's home configuration. + public static InstructionSourceType Home { get; } = new("home"); + + /// Instructions loaded from repository-scoped files. + public static InstructionSourceType Repo { get; } = new("repo"); + + /// Instructions loaded from model-specific files. + public static InstructionSourceType Model { get; } = new("model"); + + /// Instructions loaded from VS Code instruction files. + public static InstructionSourceType Vscode { get; } = new("vscode"); + + /// Instructions discovered from nested agent files. + public static InstructionSourceType NestedAgents { get; } = new("nested-agents"); + + /// Instructions inherited from child instruction files. + public static InstructionSourceType ChildInstructions { get; } = new("child-instructions"); + + /// Instructions supplied by an installed plugin. + public static InstructionSourceType Plugin { get; } = new("plugin"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(InstructionSourceType left, InstructionSourceType right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(InstructionSourceType left, InstructionSourceType right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is InstructionSourceType other && Equals(other); + + /// + public bool Equals(InstructionSourceType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override InstructionSourceType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, InstructionSourceType value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(InstructionSourceType)); + } + } +} + + +/// Whether the target is a single file or a directory of instruction files. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct InstructionDiscoveryPathKind : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public InstructionDiscoveryPathKind(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// The target is a single instruction file. + public static InstructionDiscoveryPathKind File { get; } = new("file"); + + /// The target is a directory that holds instruction files. + public static InstructionDiscoveryPathKind Directory { get; } = new("directory"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(InstructionDiscoveryPathKind left, InstructionDiscoveryPathKind right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(InstructionDiscoveryPathKind left, InstructionDiscoveryPathKind right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is InstructionDiscoveryPathKind other && Equals(other); + + /// + public bool Equals(InstructionDiscoveryPathKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override InstructionDiscoveryPathKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, InstructionDiscoveryPathKind value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(InstructionDiscoveryPathKind)); + } + } +} + + +/// Which tier this target belongs to. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct InstructionDiscoveryPathLocation : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public InstructionDiscoveryPathLocation(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Instructions live in user-level configuration. + public static InstructionDiscoveryPathLocation User { get; } = new("user"); + + /// Instructions live in repository-level configuration. + public static InstructionDiscoveryPathLocation Repository { get; } = new("repository"); + + /// Instructions live under the current working directory. + public static InstructionDiscoveryPathLocation WorkingDirectory { get; } = new("working-directory"); + + /// Instructions live in plugin-provided configuration. + public static InstructionDiscoveryPathLocation Plugin { get; } = new("plugin"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(InstructionDiscoveryPathLocation left, InstructionDiscoveryPathLocation right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(InstructionDiscoveryPathLocation left, InstructionDiscoveryPathLocation right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is InstructionDiscoveryPathLocation other && Equals(other); + + /// + public bool Equals(InstructionDiscoveryPathLocation other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override InstructionDiscoveryPathLocation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, InstructionDiscoveryPathLocation value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(InstructionDiscoveryPathLocation)); + } + } +} + + +/// Optional completion hint for the input (e.g. 'directory' for filesystem path completion). +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct SlashCommandInputCompletion : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public SlashCommandInputCompletion(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Input should complete filesystem directories. + public static SlashCommandInputCompletion Directory { get; } = new("directory"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SlashCommandInputCompletion left, SlashCommandInputCompletion right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SlashCommandInputCompletion left, SlashCommandInputCompletion right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is SlashCommandInputCompletion other && Equals(other); + + /// + public bool Equals(SlashCommandInputCompletion other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override SlashCommandInputCompletion Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, SlashCommandInputCompletion value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SlashCommandInputCompletion)); + } + } +} + + +/// Coarse command category for grouping and behavior: runtime built-in, skill-backed command, or SDK/client-owned command. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct SlashCommandKind : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public SlashCommandKind(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Command implemented by the runtime. + public static SlashCommandKind Builtin { get; } = new("builtin"); + + /// Command backed by a skill. + public static SlashCommandKind Skill { get; } = new("skill"); + + /// Command registered by an SDK client or extension. + public static SlashCommandKind Client { get; } = new("client"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SlashCommandKind left, SlashCommandKind right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SlashCommandKind left, SlashCommandKind right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is SlashCommandKind other && Equals(other); + + /// + public bool Equals(SlashCommandKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override SlashCommandKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// + public override void Write(Utf8JsonWriter writer, SlashCommandKind value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SlashCommandKind)); + } + } } -/// Canvas close parameters sent to the provider. + +/// Path conventions used by this filesystem. [Experimental(Diagnostics.Experimental)] -public sealed class CanvasProviderCloseRequest +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct SessionFsSetProviderConventions : IEquatable { - /// Provider-local canvas identifier. - [JsonPropertyName("canvasId")] - public string CanvasId { get; set; } = string.Empty; - - /// Owning provider identifier. - [JsonPropertyName("extensionId")] - public string ExtensionId { get; set; } = string.Empty; + private readonly string? _value; - /// Host context supplied by the runtime. - [JsonPropertyName("host")] - public CanvasHostContext? Host { get; set; } + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public SessionFsSetProviderConventions(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Canvas instance identifier. - [JsonPropertyName("instanceId")] - public string InstanceId { get; set; } = string.Empty; + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Session context supplied by the runtime. - [JsonPropertyName("session")] - public CanvasSessionContext? Session { get; set; } + /// Paths use Windows path conventions. + public static SessionFsSetProviderConventions Windows { get; } = new("windows"); - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; -} + /// Paths use POSIX path conventions. + public static SessionFsSetProviderConventions Posix { get; } = new("posix"); -/// Canvas action invocation parameters sent to the provider. -[Experimental(Diagnostics.Experimental)] -public sealed class CanvasProviderInvokeActionRequest -{ - /// Action name to invoke. - [JsonPropertyName("actionName")] - public string ActionName { get; set; } = string.Empty; + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SessionFsSetProviderConventions left, SessionFsSetProviderConventions right) => left.Equals(right); - /// Provider-local canvas identifier. - [JsonPropertyName("canvasId")] - public string CanvasId { get; set; } = string.Empty; + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SessionFsSetProviderConventions left, SessionFsSetProviderConventions right) => !(left == right); - /// Owning provider identifier. - [JsonPropertyName("extensionId")] - public string ExtensionId { get; set; } = string.Empty; + /// + public override bool Equals(object? obj) => obj is SessionFsSetProviderConventions other && Equals(other); - /// Host context supplied by the runtime. - [JsonPropertyName("host")] - public CanvasHostContext? Host { get; set; } + /// + public bool Equals(SessionFsSetProviderConventions other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - /// Action input. - [JsonPropertyName("input")] - public JsonElement? Input { get; set; } + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - /// Canvas instance identifier. - [JsonPropertyName("instanceId")] - public string InstanceId { get; set; } = string.Empty; + /// + public override string ToString() => Value; - /// Session context supplied by the runtime. - [JsonPropertyName("session")] - public CanvasSessionContext? Session { get; set; } + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override SessionFsSetProviderConventions Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// + public override void Write(Utf8JsonWriter writer, SessionFsSetProviderConventions value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionFsSetProviderConventions)); + } + } } -/// Acknowledgement. Returning successfully simply means the SDK accepted the start frame; it does not imply the request will succeed. -[Experimental(Diagnostics.Experimental)] -public sealed class LlmInferenceHttpRequestStartResult -{ -} -/// The head of an outbound model-layer HTTP request. +/// Repository host type. [Experimental(Diagnostics.Experimental)] -public sealed class LlmInferenceHttpRequestStartRequest +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct SessionContextHostType : IEquatable { - /// Stable identity of the agent trajectory that issued this request. Present when the request originates from an agent turn; absent for requests outside any agent context. This is the same identity used by lifecycle and bridged session events and remains constant across turns and retries. - [JsonPropertyName("agentId")] - public string? AgentId { get; set; } + private readonly string? _value; - /// Identity of the agent invocation (one agentic loop) that issued this request. It remains fixed across physical retries within the invocation and is distinct from the stable trajectory `agentId`. A caller-supplied invocation id always takes precedence (this covers auxiliary calls that have no model call id). Otherwise, first-party CAPI requests fall back to the runtime's agent task id — the same value the runtime emits as the `X-Agent-Task-Id` header — while custom-provider requests fall back to the model call id. - [JsonPropertyName("agentInvocationId")] - public string? AgentInvocationId { get; set; } + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public SessionContextHostType(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Gets or sets the headers value. - [JsonPropertyName("headers")] - public IDictionary> Headers { get => field ??= new Dictionary>(); set; } + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Coarse classification of the interaction that produced this request. Open string for forward-compatibility; known values include `conversation-agent`, `conversation-subagent`, `conversation-sampling`, `conversation-background`, `conversation-compaction`, and `conversation-user`. Absent when the runtime did not classify the request. Comes from the runtime's per-request agent context independently of transport; on the CAPI transport the runtime derives the upstream `X-Interaction-Type` header from this same context. - [JsonPropertyName("interactionType")] - public string? InteractionType { get; set; } + /// Session repository is hosted on GitHub. + public static SessionContextHostType GitHub { get; } = new("github"); - /// HTTP method, e.g. GET, POST. - [JsonPropertyName("method")] - public string Method { get; set; } = string.Empty; + /// Session repository is hosted on Azure DevOps. + public static SessionContextHostType Ado { get; } = new("ado"); - /// Stable identity of the immediate parent trajectory. Present for child trajectories such as subagents and conversation-sampling requests; absent for root-agent and non-agent requests. - [JsonPropertyName("parentAgentId")] - public string? ParentAgentId { get; set; } + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SessionContextHostType left, SessionContextHostType right) => left.Equals(right); - /// Opaque runtime-minted id, unique per in-flight request. The SDK uses this to correlate httpRequestChunk frames and to address its httpResponseStart / httpResponseChunk replies back to the runtime. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SessionContextHostType left, SessionContextHostType right) => !(left == right); - /// Id of the runtime session that triggered this request, when one is in scope. Absent for requests issued outside any session (e.g. startup model-catalog or capability resolution). This is a payload field — not a dispatch key — because the client-global API is registered process-wide rather than per session. - [JsonPropertyName("sessionId")] - public string? SessionId { get; set; } + /// + public override bool Equals(object? obj) => obj is SessionContextHostType other && Equals(other); - /// Transport the runtime would otherwise use for this request. `http` (the default when absent) covers plain HTTP and SSE responses; `websocket` indicates a full-duplex message channel where each body chunk maps to one WebSocket message and the `binary` flag distinguishes text from binary frames. The SDK consumer uses this to decide whether to service the request with an HTTP client or a WebSocket client. It is the one piece of request metadata the consumer cannot reliably infer from the URL or headers alone. - [JsonPropertyName("transport")] - public LlmInferenceHttpRequestStartTransport? Transport { get; set; } + /// + public bool Equals(SessionContextHostType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - /// Absolute request URL. - [JsonPropertyName("url")] - public string Url { get; set; } = string.Empty; -} + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); -/// Acknowledgement. The SDK is free to ignore the ack and treat chunk delivery as fire-and-forget. -[Experimental(Diagnostics.Experimental)] -public sealed class LlmInferenceHttpRequestChunkResult -{ + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override SessionContextHostType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, SessionContextHostType value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionContextHostType)); + } + } } -/// A request body chunk or cancellation signal. + +/// Whether the remote task originated from CCA or CLI `--remote`. [Experimental(Diagnostics.Experimental)] -public sealed class LlmInferenceHttpRequestChunkRequest +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct RemoteSessionMetadataTaskType : IEquatable { - /// Identity of the agent invocation (one agentic loop) this body chunk belongs to, matching the `agentInvocationId` semantics on httpRequestStart. Carried per chunk so a persistent transport can attribute successive turns correctly: when a WebSocket connection is reused across turns, the httpRequestStart identity reflects only the turn that opened the connection, so each later turn stamps its own invocation id here. Absent when the runtime has no invocation context for the request, or on the plain-HTTP transport where every request has its own httpRequestStart. - [JsonPropertyName("agentInvocationId")] - public string? AgentInvocationId { get; set; } + private readonly string? _value; - /// When true, `data` is base64-encoded bytes. When absent or false, `data` is UTF-8 text. - [JsonPropertyName("binary")] - public bool? Binary { get; set; } + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public RemoteSessionMetadataTaskType(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// When true, the runtime is cancelling the in-flight request (e.g. upstream consumer aborted). `data` is ignored. Implies end-of-request. - [JsonPropertyName("cancel")] - public bool? Cancel { get; set; } + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Optional human-readable reason for the cancellation, propagated for logging. - [JsonPropertyName("cancelReason")] - public string? CancelReason { get; set; } + /// GitHub Copilot coding agent task. + public static RemoteSessionMetadataTaskType Cca { get; } = new("cca"); - /// Body byte range. UTF-8 text when `binary` is absent or false; base64-encoded bytes when `binary` is true. May be empty. - [JsonPropertyName("data")] - public string Data { get; set; } = string.Empty; + /// CLI remote task. + public static RemoteSessionMetadataTaskType Cli { get; } = new("cli"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(RemoteSessionMetadataTaskType left, RemoteSessionMetadataTaskType right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(RemoteSessionMetadataTaskType left, RemoteSessionMetadataTaskType right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is RemoteSessionMetadataTaskType other && Equals(other); + + /// + public bool Equals(RemoteSessionMetadataTaskType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; - /// When true, this is the final body chunk for the request. The SDK may rely on having received an end-marked chunk before treating the request body as complete. - [JsonPropertyName("end")] - public bool? End { get; set; } + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override RemoteSessionMetadataTaskType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } - /// Matches the requestId from the originating httpRequestStart frame. - [JsonPropertyName("requestId")] - public string RequestId { get; set; } = string.Empty; + /// + public override void Write(Utf8JsonWriter writer, RemoteSessionMetadataTaskType value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(RemoteSessionMetadataTaskType)); + } + } } -/// Client environment metadata describing the process that produced a telemetry event. + +/// Step status. [Experimental(Diagnostics.Experimental)] -public sealed class GitHubTelemetryClientInfo +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct SessionsOpenProgressStatus : IEquatable { - /// Copilot CLI version string. - [JsonPropertyName("cli_version")] - public string CliVersion { get; set; } = string.Empty; + private readonly string? _value; - /// Name of the client application. - [JsonPropertyName("client_name")] - public string? ClientName { get; set; } + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public SessionsOpenProgressStatus(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Type of client. - [JsonPropertyName("client_type")] - public string? ClientType { get; set; } + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Copilot subscription plan, when known. - [JsonPropertyName("copilot_plan")] - public string? CopilotPlan { get; set; } + /// The step has started and has not yet finished. + public static SessionsOpenProgressStatus InProgress { get; } = new("in-progress"); - /// Stable machine identifier for the device. - [JsonPropertyName("dev_device_id")] - public string? DevDeviceId { get; set; } + /// The step has completed successfully. + public static SessionsOpenProgressStatus Complete { get; } = new("complete"); - /// Whether the user is a GitHub/Microsoft staff member. - [JsonPropertyName("is_staff")] - public bool? IsStaff { get; set; } + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SessionsOpenProgressStatus left, SessionsOpenProgressStatus right) => left.Equals(right); - /// Node.js runtime version string. - [JsonPropertyName("node_version")] - public string NodeVersion { get; set; } = string.Empty; + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SessionsOpenProgressStatus left, SessionsOpenProgressStatus right) => !(left == right); - /// Operating system architecture (e.g. arm64, x64). - [JsonPropertyName("os_arch")] - public string OsArch { get; set; } = string.Empty; + /// + public override bool Equals(object? obj) => obj is SessionsOpenProgressStatus other && Equals(other); - /// Operating system platform (e.g. darwin, linux, win32). - [JsonPropertyName("os_platform")] - public string OsPlatform { get; set; } = string.Empty; + /// + public bool Equals(SessionsOpenProgressStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - /// Operating system version string. - [JsonPropertyName("os_version")] - public string OsVersion { get; set; } = string.Empty; + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override SessionsOpenProgressStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, SessionsOpenProgressStatus value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionsOpenProgressStatus)); + } + } } -/// A single telemetry event in the runtime's native GitHub-shaped telemetry format, forwarded verbatim to opted-in hosts. The `restricted` flag on the enclosing GitHubTelemetryNotification distinguishes standard from restricted events; the payload shape is identical for both. + +/// Handoff step. [Experimental(Diagnostics.Experimental)] -public sealed class GitHubTelemetryEvent +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct SessionsOpenProgressStep : IEquatable { - /// Client environment metadata. - [JsonPropertyName("client")] - public GitHubTelemetryClientInfo? Client { get; set; } + private readonly string? _value; - /// Copilot tracking ID for user-level attribution. - [JsonPropertyName("copilot_tracking_id")] - public string? CopilotTrackingId { get; set; } + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public SessionsOpenProgressStep(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Timestamp when the event was created (ISO 8601 format). - [JsonPropertyName("created_at")] - public string? CreatedAt { get; set; } + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Experiment assignment context. - [JsonPropertyName("exp_assignment_context")] - public string? ExpAssignmentContext { get; set; } + /// Loading the source session's events from the remote service. + public static SessionsOpenProgressStep LoadSession { get; } = new("load-session"); - /// Feature flags enabled for this session, as a map from flag to value. - [JsonPropertyName("features")] - public IDictionary? Features { get; set; } + /// Validating that the local repository matches the remote session's repository. + public static SessionsOpenProgressStep ValidateRepo { get; } = new("validate-repo"); - /// Event type/kind (e.g. get_completion_with_tools_turn, tool_call_executed). - [JsonPropertyName("kind")] - public string Kind { get; set; } = string.Empty; + /// Checking the local working tree for uncommitted changes that would block the handoff. + public static SessionsOpenProgressStep CheckChanges { get; } = new("check-changes"); - /// Numeric metrics as a map from key to value. - [JsonPropertyName("metrics")] - public IDictionary Metrics { get => field ??= new Dictionary(); set; } + /// Checking out the branch associated with the remote session in the local working tree. + public static SessionsOpenProgressStep CheckoutBranch { get; } = new("checkout-branch"); - /// Reference to the model call that produced this event. - [JsonPropertyName("model_call_id")] - public string? ModelCallId { get; set; } + /// Creating the new local session and seeding it with the source session's events. + public static SessionsOpenProgressStep CreateSession { get; } = new("create-session"); - /// String-valued properties as a map from key to value. - [JsonPropertyName("properties")] - public IDictionary Properties { get => field ??= new Dictionary(); set; } + /// Persisting the newly-created local session to disk. + public static SessionsOpenProgressStep SaveSession { get; } = new("save-session"); - /// Session identifier the event belongs to. - [JsonPropertyName("session_id")] - public string? SessionId { get; set; } -} + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SessionsOpenProgressStep left, SessionsOpenProgressStep right) => left.Equals(right); -/// Payload for a `gitHubTelemetry.event` notification: a single GitHub telemetry event the runtime forwards to a host connection that opted into telemetry forwarding during the `server.connect` handshake. -[Experimental(Diagnostics.Experimental)] -public sealed class GitHubTelemetryNotification -{ - /// The telemetry event, in the runtime's native GitHub-shaped telemetry format. - [JsonPropertyName("event")] - public GitHubTelemetryEvent Event { get => field ??= new(); set; } + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SessionsOpenProgressStep left, SessionsOpenProgressStep right) => !(left == right); - /// Whether this is a restricted telemetry event (cli.restricted_telemetry). Hosts must route restricted events to first-party Microsoft stores only. - [JsonPropertyName("restricted")] - public bool Restricted { get; set; } + /// + public override bool Equals(object? obj) => obj is SessionsOpenProgressStep other && Equals(other); - /// Session the telemetry event belongs to, when it is session-scoped. Omitted for sessionless events (for example, `server.sendTelemetry` calls with no session id), which are still forwarded to opted-in connections. - [JsonPropertyName("sessionId")] - public string? SessionId { get; set; } + /// + public bool Equals(SessionsOpenProgressStep other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override SessionsOpenProgressStep Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, SessionsOpenProgressStep value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionsOpenProgressStep)); + } + } } -/// Resolved Anthropic adaptive-thinking capability for a model. + +/// Outcome of the open request. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct AdaptiveThinkingSupport : IEquatable +public readonly struct SessionsOpenStatus : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public AdaptiveThinkingSupport(string value) + public SessionsOpenStatus(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// The model does not accept thinking.type='adaptive'. - public static AdaptiveThinkingSupport Unsupported { get; } = new("unsupported"); + /// A new session was created. + public static SessionsOpenStatus Created { get; } = new("created"); - /// The model accepts adaptive thinking but also accepts thinking.type='enabled'. - public static AdaptiveThinkingSupport Optional { get; } = new("optional"); + /// An existing session was loaded or reattached. + public static SessionsOpenStatus Resumed { get; } = new("resumed"); - /// The model only accepts adaptive thinking and rejects thinking.type='enabled' with HTTP 400 (e.g. opus-4.7/4.8). - public static AdaptiveThinkingSupport Required { get; } = new("required"); + /// No matching persisted session was found. + public static SessionsOpenStatus NotFound { get; } = new("not_found"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(AdaptiveThinkingSupport left, AdaptiveThinkingSupport right) => left.Equals(right); + /// Connected to an existing remote session. + public static SessionsOpenStatus Connected { get; } = new("connected"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(AdaptiveThinkingSupport left, AdaptiveThinkingSupport right) => !(left == right); + /// Remote session was handed off to a new local session. + public static SessionsOpenStatus HandedOff { get; } = new("handed_off"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SessionsOpenStatus left, SessionsOpenStatus right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SessionsOpenStatus left, SessionsOpenStatus right) => !(left == right); /// - public override bool Equals(object? obj) => obj is AdaptiveThinkingSupport other && Equals(other); + public override bool Equals(object? obj) => obj is SessionsOpenStatus other && Equals(other); /// - public bool Equals(AdaptiveThinkingSupport other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(SessionsOpenStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -13049,65 +16746,62 @@ public AdaptiveThinkingSupport(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override AdaptiveThinkingSupport Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override SessionsOpenStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, AdaptiveThinkingSupport value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, SessionsOpenStatus value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AdaptiveThinkingSupport)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionsOpenStatus)); } } } -/// Model capability category for grouping in the model picker. +/// Neutral SDK discriminator for the connected remote session kind. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct ModelPickerCategory : IEquatable +public readonly struct ConnectedRemoteSessionMetadataKind : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public ModelPickerCategory(string value) + public ConnectedRemoteSessionMetadataKind(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Lightweight model category optimized for faster, lower-cost interactions. - public static ModelPickerCategory Lightweight { get; } = new("lightweight"); - - /// Versatile model category suitable for a broad range of tasks. - public static ModelPickerCategory Versatile { get; } = new("versatile"); + /// Remote CLI session. + public static ConnectedRemoteSessionMetadataKind RemoteSession { get; } = new("remote-session"); - /// Powerful model category optimized for complex tasks. - public static ModelPickerCategory Powerful { get; } = new("powerful"); + /// GitHub Copilot coding agent session. + public static ConnectedRemoteSessionMetadataKind CodingAgent { get; } = new("coding-agent"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(ModelPickerCategory left, ModelPickerCategory right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ConnectedRemoteSessionMetadataKind left, ConnectedRemoteSessionMetadataKind right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(ModelPickerCategory left, ModelPickerCategory right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ConnectedRemoteSessionMetadataKind left, ConnectedRemoteSessionMetadataKind right) => !(left == right); /// - public override bool Equals(object? obj) => obj is ModelPickerCategory other && Equals(other); + public override bool Equals(object? obj) => obj is ConnectedRemoteSessionMetadataKind other && Equals(other); /// - public bool Equals(ModelPickerCategory other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(ConnectedRemoteSessionMetadataKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -13115,68 +16809,65 @@ public ModelPickerCategory(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override ModelPickerCategory Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override ConnectedRemoteSessionMetadataKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, ModelPickerCategory value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, ConnectedRemoteSessionMetadataKind value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ModelPickerCategory)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ConnectedRemoteSessionMetadataKind)); } } } -/// Relative cost tier for token-based billing users. +/// Which session sources to include. Defaults to `local` for backward compatibility. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct ModelPickerPriceCategory : IEquatable +public readonly struct SessionSource : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public ModelPickerPriceCategory(string value) + public SessionSource(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Lowest relative token cost tier. - public static ModelPickerPriceCategory Low { get; } = new("low"); - - /// Medium relative token cost tier. - public static ModelPickerPriceCategory Medium { get; } = new("medium"); + /// Return only local sessions. + public static SessionSource Local { get; } = new("local"); - /// High relative token cost tier. - public static ModelPickerPriceCategory High { get; } = new("high"); + /// Return only remote sessions. + public static SessionSource Remote { get; } = new("remote"); - /// Highest relative token cost tier. - public static ModelPickerPriceCategory VeryHigh { get; } = new("very_high"); + /// Return both local and remote sessions. + public static SessionSource All { get; } = new("all"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(ModelPickerPriceCategory left, ModelPickerPriceCategory right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SessionSource left, SessionSource right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(ModelPickerPriceCategory left, ModelPickerPriceCategory right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SessionSource left, SessionSource right) => !(left == right); /// - public override bool Equals(object? obj) => obj is ModelPickerPriceCategory other && Equals(other); + public override bool Equals(object? obj) => obj is SessionSource other && Equals(other); /// - public bool Equals(ModelPickerPriceCategory other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(SessionSource other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -13184,65 +16875,71 @@ public ModelPickerPriceCategory(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override ModelPickerPriceCategory Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override SessionSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, ModelPickerPriceCategory value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, SessionSource value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ModelPickerPriceCategory)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionSource)); } } } -/// Current policy state for this model. +/// Kind of attention required when status === "attention". Meaningful only when status === "attention". [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct ModelPolicyState : IEquatable +public readonly struct AgentRegistryLiveTargetEntryAttentionKind : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public ModelPolicyState(string value) + public AgentRegistryLiveTargetEntryAttentionKind(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// The model is enabled by policy. - public static ModelPolicyState Enabled { get; } = new("enabled"); + /// Session is blocked on an unrecoverable error. + public static AgentRegistryLiveTargetEntryAttentionKind Error { get; } = new("error"); - /// The model is disabled by policy. - public static ModelPolicyState Disabled { get; } = new("disabled"); + /// Session is waiting for a tool-permission decision. + public static AgentRegistryLiveTargetEntryAttentionKind Permission { get; } = new("permission"); - /// No explicit policy is configured for the model. - public static ModelPolicyState Unconfigured { get; } = new("unconfigured"); + /// Session is waiting for the user to approve or reject a plan. + public static AgentRegistryLiveTargetEntryAttentionKind ExitPlan { get; } = new("exit_plan"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(ModelPolicyState left, ModelPolicyState right) => left.Equals(right); + /// Session is waiting on an elicitation prompt. + public static AgentRegistryLiveTargetEntryAttentionKind Elicitation { get; } = new("elicitation"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(ModelPolicyState left, ModelPolicyState right) => !(left == right); + /// Session is waiting for free-form user input. + public static AgentRegistryLiveTargetEntryAttentionKind UserInput { get; } = new("user_input"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(AgentRegistryLiveTargetEntryAttentionKind left, AgentRegistryLiveTargetEntryAttentionKind right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(AgentRegistryLiveTargetEntryAttentionKind left, AgentRegistryLiveTargetEntryAttentionKind right) => !(left == right); /// - public override bool Equals(object? obj) => obj is ModelPolicyState other && Equals(other); + public override bool Equals(object? obj) => obj is AgentRegistryLiveTargetEntryAttentionKind other && Equals(other); /// - public bool Equals(ModelPolicyState other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(AgentRegistryLiveTargetEntryAttentionKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -13250,68 +16947,62 @@ public ModelPolicyState(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override ModelPolicyState Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override AgentRegistryLiveTargetEntryAttentionKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, ModelPolicyState value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, AgentRegistryLiveTargetEntryAttentionKind value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ModelPolicyState)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentRegistryLiveTargetEntryAttentionKind)); } } } -/// Server transport type: stdio, http, sse (deprecated), or memory. +/// Process kind tag for the registry entry. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct DiscoveredMcpServerType : IEquatable +public readonly struct AgentRegistryLiveTargetEntryKind : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public DiscoveredMcpServerType(string value) + public AgentRegistryLiveTargetEntryKind(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Server communicates over stdio with a local child process. - public static DiscoveredMcpServerType Stdio { get; } = new("stdio"); - - /// Server communicates over streamable HTTP. - public static DiscoveredMcpServerType Http { get; } = new("http"); - - /// Server communicates over Server-Sent Events (deprecated). - public static DiscoveredMcpServerType Sse { get; } = new("sse"); + /// Interactive Copilot CLI exposing a UI server (legacy/normal CLI process). + public static AgentRegistryLiveTargetEntryKind UiServer { get; } = new("ui-server"); - /// Server is backed by an in-memory runtime implementation. - public static DiscoveredMcpServerType Memory { get; } = new("memory"); + /// Headless `--server --managed-server` child spawned by a controller. + public static AgentRegistryLiveTargetEntryKind ManagedServer { get; } = new("managed-server"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(DiscoveredMcpServerType left, DiscoveredMcpServerType right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(AgentRegistryLiveTargetEntryKind left, AgentRegistryLiveTargetEntryKind right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(DiscoveredMcpServerType left, DiscoveredMcpServerType right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(AgentRegistryLiveTargetEntryKind left, AgentRegistryLiveTargetEntryKind right) => !(left == right); /// - public override bool Equals(object? obj) => obj is DiscoveredMcpServerType other && Equals(other); + public override bool Equals(object? obj) => obj is AgentRegistryLiveTargetEntryKind other && Equals(other); /// - public bool Equals(DiscoveredMcpServerType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(AgentRegistryLiveTargetEntryKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -13319,68 +17010,62 @@ public DiscoveredMcpServerType(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override DiscoveredMcpServerType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override AgentRegistryLiveTargetEntryKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, DiscoveredMcpServerType value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, AgentRegistryLiveTargetEntryKind value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(DiscoveredMcpServerType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentRegistryLiveTargetEntryKind)); } } } -/// Which tier this directory belongs to. +/// How the most recent turn ended (clean vs aborted). Lets the renderer distinguish done from done_cancelled. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct SkillDiscoveryScope : IEquatable +public readonly struct AgentRegistryLiveTargetEntryLastTerminalEvent : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public SkillDiscoveryScope(string value) + public AgentRegistryLiveTargetEntryLastTerminalEvent(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// A project's repository skill directory. - public static SkillDiscoveryScope Project { get; } = new("project"); - - /// The user's personal Copilot skill directory. - public static SkillDiscoveryScope PersonalCopilot { get; } = new("personal-copilot"); - - /// The user's personal agents skill directory. - public static SkillDiscoveryScope PersonalAgents { get; } = new("personal-agents"); + /// Last turn ended cleanly (model returned a final assistant message). + public static AgentRegistryLiveTargetEntryLastTerminalEvent TurnEnd { get; } = new("turn_end"); - /// A configured custom skill directory. - public static SkillDiscoveryScope Custom { get; } = new("custom"); + /// Last turn was aborted (e.g. user interrupted). + public static AgentRegistryLiveTargetEntryLastTerminalEvent Abort { get; } = new("abort"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(SkillDiscoveryScope left, SkillDiscoveryScope right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(AgentRegistryLiveTargetEntryLastTerminalEvent left, AgentRegistryLiveTargetEntryLastTerminalEvent right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(SkillDiscoveryScope left, SkillDiscoveryScope right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(AgentRegistryLiveTargetEntryLastTerminalEvent left, AgentRegistryLiveTargetEntryLastTerminalEvent right) => !(left == right); /// - public override bool Equals(object? obj) => obj is SkillDiscoveryScope other && Equals(other); + public override bool Equals(object? obj) => obj is AgentRegistryLiveTargetEntryLastTerminalEvent other && Equals(other); /// - public bool Equals(SkillDiscoveryScope other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(AgentRegistryLiveTargetEntryLastTerminalEvent other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -13388,74 +17073,68 @@ public SkillDiscoveryScope(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override SkillDiscoveryScope Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override AgentRegistryLiveTargetEntryLastTerminalEvent Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, SkillDiscoveryScope value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, AgentRegistryLiveTargetEntryLastTerminalEvent value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SkillDiscoveryScope)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentRegistryLiveTargetEntryLastTerminalEvent)); } } } -/// Where the agent definition was loaded from. +/// Coarse lifecycle status of the foreground session. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct AgentInfoSource : IEquatable +public readonly struct AgentRegistryLiveTargetEntryStatus : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public AgentInfoSource(string value) + public AgentRegistryLiveTargetEntryStatus(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Agent loaded from the user's personal agent configuration. - public static AgentInfoSource User { get; } = new("user"); - - /// Agent loaded from the current project's repository configuration. - public static AgentInfoSource Project { get; } = new("project"); - - /// Agent inherited from a parent project or workspace. - public static AgentInfoSource Inherited { get; } = new("inherited"); + /// Session is actively processing a turn. + public static AgentRegistryLiveTargetEntryStatus Working { get; } = new("working"); - /// Agent provided by a remote runtime or service. - public static AgentInfoSource Remote { get; } = new("remote"); + /// Session is idle, waiting for input. + public static AgentRegistryLiveTargetEntryStatus Waiting { get; } = new("waiting"); - /// Agent contributed by an installed plugin. - public static AgentInfoSource Plugin { get; } = new("plugin"); + /// Last turn completed successfully. + public static AgentRegistryLiveTargetEntryStatus Done { get; } = new("done"); - /// Agent built into the Copilot runtime. - public static AgentInfoSource Builtin { get; } = new("builtin"); + /// Session needs user attention (see attentionKind for the specific reason). + public static AgentRegistryLiveTargetEntryStatus Attention { get; } = new("attention"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(AgentInfoSource left, AgentInfoSource right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(AgentRegistryLiveTargetEntryStatus left, AgentRegistryLiveTargetEntryStatus right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(AgentInfoSource left, AgentInfoSource right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(AgentRegistryLiveTargetEntryStatus left, AgentRegistryLiveTargetEntryStatus right) => !(left == right); /// - public override bool Equals(object? obj) => obj is AgentInfoSource other && Equals(other); + public override bool Equals(object? obj) => obj is AgentRegistryLiveTargetEntryStatus other && Equals(other); /// - public bool Equals(AgentInfoSource other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(AgentRegistryLiveTargetEntryStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -13463,62 +17142,65 @@ public AgentInfoSource(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override AgentInfoSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override AgentRegistryLiveTargetEntryStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, AgentInfoSource value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, AgentRegistryLiveTargetEntryStatus value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentInfoSource)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentRegistryLiveTargetEntryStatus)); } } } -/// Which tier this directory belongs to. +/// Categorized reason for log-open failure. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct AgentDiscoveryPathScope : IEquatable +public readonly struct AgentRegistryLogCaptureOpenErrorReason : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public AgentDiscoveryPathScope(string value) + public AgentRegistryLogCaptureOpenErrorReason(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// The user's personal agent configuration directory. - public static AgentDiscoveryPathScope User { get; } = new("user"); + /// Filesystem permission denied opening the log file. + public static AgentRegistryLogCaptureOpenErrorReason Permission { get; } = new("permission"); - /// A project's repository agent directory. - public static AgentDiscoveryPathScope Project { get; } = new("project"); + /// No space left on device. + public static AgentRegistryLogCaptureOpenErrorReason DiskFull { get; } = new("disk_full"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(AgentDiscoveryPathScope left, AgentDiscoveryPathScope right) => left.Equals(right); + /// Other / uncategorized open failure. + public static AgentRegistryLogCaptureOpenErrorReason Other { get; } = new("other"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(AgentDiscoveryPathScope left, AgentDiscoveryPathScope right) => !(left == right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(AgentRegistryLogCaptureOpenErrorReason left, AgentRegistryLogCaptureOpenErrorReason right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(AgentRegistryLogCaptureOpenErrorReason left, AgentRegistryLogCaptureOpenErrorReason right) => !(left == right); /// - public override bool Equals(object? obj) => obj is AgentDiscoveryPathScope other && Equals(other); + public override bool Equals(object? obj) => obj is AgentRegistryLogCaptureOpenErrorReason other && Equals(other); /// - public bool Equals(AgentDiscoveryPathScope other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(AgentRegistryLogCaptureOpenErrorReason other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -13526,68 +17208,71 @@ public AgentDiscoveryPathScope(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override AgentDiscoveryPathScope Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override AgentRegistryLogCaptureOpenErrorReason Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, AgentDiscoveryPathScope value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, AgentRegistryLogCaptureOpenErrorReason value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentDiscoveryPathScope)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentRegistryLogCaptureOpenErrorReason)); } } } -/// Where this source lives — used for UI grouping. +/// Which parameter field was invalid. Omitted when the rejection is not field-specific. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct InstructionSourceLocation : IEquatable +public readonly struct AgentRegistrySpawnValidationErrorField : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public InstructionSourceLocation(string value) + public AgentRegistrySpawnValidationErrorField(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Instructions live in user-level configuration. - public static InstructionSourceLocation User { get; } = new("user"); + /// The cwd parameter. + public static AgentRegistrySpawnValidationErrorField Cwd { get; } = new("cwd"); - /// Instructions live in repository-level configuration. - public static InstructionSourceLocation Repository { get; } = new("repository"); + /// The session name parameter. + public static AgentRegistrySpawnValidationErrorField Name { get; } = new("name"); - /// Instructions live under the current working directory. - public static InstructionSourceLocation WorkingDirectory { get; } = new("working-directory"); + /// The agentName parameter. + public static AgentRegistrySpawnValidationErrorField AgentName { get; } = new("agentName"); - /// Instructions live in plugin-provided configuration. - public static InstructionSourceLocation Plugin { get; } = new("plugin"); + /// The model parameter. + public static AgentRegistrySpawnValidationErrorField Model { get; } = new("model"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(InstructionSourceLocation left, InstructionSourceLocation right) => left.Equals(right); + /// The permissionMode parameter. + public static AgentRegistrySpawnValidationErrorField PermissionMode { get; } = new("permissionMode"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(InstructionSourceLocation left, InstructionSourceLocation right) => !(left == right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(AgentRegistrySpawnValidationErrorField left, AgentRegistrySpawnValidationErrorField right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(AgentRegistrySpawnValidationErrorField left, AgentRegistrySpawnValidationErrorField right) => !(left == right); /// - public override bool Equals(object? obj) => obj is InstructionSourceLocation other && Equals(other); + public override bool Equals(object? obj) => obj is AgentRegistrySpawnValidationErrorField other && Equals(other); /// - public bool Equals(InstructionSourceLocation other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(AgentRegistrySpawnValidationErrorField other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -13595,77 +17280,74 @@ public InstructionSourceLocation(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override InstructionSourceLocation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override AgentRegistrySpawnValidationErrorField Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, InstructionSourceLocation value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, AgentRegistrySpawnValidationErrorField value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(InstructionSourceLocation)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentRegistrySpawnValidationErrorField)); } } } -/// Category of instruction source — used for merge logic. +/// Categorized reason for the rejection. Low-cardinality enum so telemetry can aggregate by reason without leaking raw paths or agent/model names. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct InstructionSourceType : IEquatable +public readonly struct AgentRegistrySpawnValidationErrorReason : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public InstructionSourceType(string value) + public AgentRegistrySpawnValidationErrorReason(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Instructions loaded from the user's home configuration. - public static InstructionSourceType Home { get; } = new("home"); - - /// Instructions loaded from repository-scoped files. - public static InstructionSourceType Repo { get; } = new("repo"); + /// Provided cwd does not exist on disk. + public static AgentRegistrySpawnValidationErrorReason CwdNotFound { get; } = new("cwd-not-found"); - /// Instructions loaded from model-specific files. - public static InstructionSourceType Model { get; } = new("model"); + /// Provided cwd exists but is not a directory. + public static AgentRegistrySpawnValidationErrorReason CwdNotDirectory { get; } = new("cwd-not-directory"); - /// Instructions loaded from VS Code instruction files. - public static InstructionSourceType Vscode { get; } = new("vscode"); + /// Session name failed validateSessionName. + public static AgentRegistrySpawnValidationErrorReason InvalidName { get; } = new("invalid-name"); - /// Instructions discovered from nested agent files. - public static InstructionSourceType NestedAgents { get; } = new("nested-agents"); + /// Requested agent name was not found in builtin or custom agents. + public static AgentRegistrySpawnValidationErrorReason UnknownAgent { get; } = new("unknown-agent"); - /// Instructions inherited from child instruction files. - public static InstructionSourceType ChildInstructions { get; } = new("child-instructions"); + /// Requested model is not available to this session. + public static AgentRegistrySpawnValidationErrorReason UnknownModel { get; } = new("unknown-model"); - /// Instructions supplied by an installed plugin. - public static InstructionSourceType Plugin { get; } = new("plugin"); + /// Caller asked for permissionMode='yolo' but the controller is not currently in allow-all mode. + public static AgentRegistrySpawnValidationErrorReason YoloNotAllowed { get; } = new("yolo-not-allowed"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(InstructionSourceType left, InstructionSourceType right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(AgentRegistrySpawnValidationErrorReason left, AgentRegistrySpawnValidationErrorReason right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(InstructionSourceType left, InstructionSourceType right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(AgentRegistrySpawnValidationErrorReason left, AgentRegistrySpawnValidationErrorReason right) => !(left == right); /// - public override bool Equals(object? obj) => obj is InstructionSourceType other && Equals(other); + public override bool Equals(object? obj) => obj is AgentRegistrySpawnValidationErrorReason other && Equals(other); /// - public bool Equals(InstructionSourceType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(AgentRegistrySpawnValidationErrorReason other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -13673,62 +17355,62 @@ public InstructionSourceType(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override InstructionSourceType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override AgentRegistrySpawnValidationErrorReason Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, InstructionSourceType value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, AgentRegistrySpawnValidationErrorReason value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(InstructionSourceType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentRegistrySpawnValidationErrorReason)); } } } -/// Whether the target is a single file or a directory of instruction files. +/// Permission posture for the new session. 'yolo' requires the controller-local session to currently be in allow-all mode. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct InstructionDiscoveryPathKind : IEquatable +public readonly struct AgentRegistrySpawnPermissionMode : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public InstructionDiscoveryPathKind(string value) + public AgentRegistrySpawnPermissionMode(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// The target is a single instruction file. - public static InstructionDiscoveryPathKind File { get; } = new("file"); + /// Standard permission posture (prompts for each request). + public static AgentRegistrySpawnPermissionMode Default { get; } = new("default"); - /// The target is a directory that holds instruction files. - public static InstructionDiscoveryPathKind Directory { get; } = new("directory"); + /// Full allow-all (requires the controller-local session to currently be in allow-all mode). + public static AgentRegistrySpawnPermissionMode Yolo { get; } = new("yolo"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(InstructionDiscoveryPathKind left, InstructionDiscoveryPathKind right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(AgentRegistrySpawnPermissionMode left, AgentRegistrySpawnPermissionMode right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(InstructionDiscoveryPathKind left, InstructionDiscoveryPathKind right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(AgentRegistrySpawnPermissionMode left, AgentRegistrySpawnPermissionMode right) => !(left == right); /// - public override bool Equals(object? obj) => obj is InstructionDiscoveryPathKind other && Equals(other); + public override bool Equals(object? obj) => obj is AgentRegistrySpawnPermissionMode other && Equals(other); /// - public bool Equals(InstructionDiscoveryPathKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(AgentRegistrySpawnPermissionMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -13736,68 +17418,68 @@ public InstructionDiscoveryPathKind(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override InstructionDiscoveryPathKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override AgentRegistrySpawnPermissionMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, InstructionDiscoveryPathKind value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, AgentRegistrySpawnPermissionMode value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(InstructionDiscoveryPathKind)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentRegistrySpawnPermissionMode)); } } } -/// Which tier this target belongs to. +/// The UI mode the agent was in when this message was sent. Defaults to the session's current mode. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct InstructionDiscoveryPathLocation : IEquatable +public readonly struct SendAgentMode : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public InstructionDiscoveryPathLocation(string value) + public SendAgentMode(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Instructions live in user-level configuration. - public static InstructionDiscoveryPathLocation User { get; } = new("user"); + /// The agent is responding interactively to the user. + public static SendAgentMode Interactive { get; } = new("interactive"); - /// Instructions live in repository-level configuration. - public static InstructionDiscoveryPathLocation Repository { get; } = new("repository"); + /// The agent is preparing a plan before making changes. + public static SendAgentMode Plan { get; } = new("plan"); - /// Instructions live under the current working directory. - public static InstructionDiscoveryPathLocation WorkingDirectory { get; } = new("working-directory"); + /// The agent is working autonomously toward task completion. + public static SendAgentMode Autopilot { get; } = new("autopilot"); - /// Instructions live in plugin-provided configuration. - public static InstructionDiscoveryPathLocation Plugin { get; } = new("plugin"); + /// The agent is in shell-focused UI mode. + public static SendAgentMode Shell { get; } = new("shell"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(InstructionDiscoveryPathLocation left, InstructionDiscoveryPathLocation right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SendAgentMode left, SendAgentMode right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(InstructionDiscoveryPathLocation left, InstructionDiscoveryPathLocation right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SendAgentMode left, SendAgentMode right) => !(left == right); /// - public override bool Equals(object? obj) => obj is InstructionDiscoveryPathLocation other && Equals(other); + public override bool Equals(object? obj) => obj is SendAgentMode other && Equals(other); /// - public bool Equals(InstructionDiscoveryPathLocation other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(SendAgentMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -13805,59 +17487,62 @@ public InstructionDiscoveryPathLocation(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override InstructionDiscoveryPathLocation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override SendAgentMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, InstructionDiscoveryPathLocation value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, SendAgentMode value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(InstructionDiscoveryPathLocation)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SendAgentMode)); } } } -/// Optional completion hint for the input (e.g. 'directory' for filesystem path completion). +/// How to deliver the message. `enqueue` (default) appends to the message queue. `immediate` interjects during an in-progress turn. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct SlashCommandInputCompletion : IEquatable +public readonly struct SendMode : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public SlashCommandInputCompletion(string value) + public SendMode(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Input should complete filesystem directories. - public static SlashCommandInputCompletion Directory { get; } = new("directory"); + /// Append the message to the normal session queue. + public static SendMode Enqueue { get; } = new("enqueue"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(SlashCommandInputCompletion left, SlashCommandInputCompletion right) => left.Equals(right); + /// Interject the message during the in-progress turn. + public static SendMode Immediate { get; } = new("immediate"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(SlashCommandInputCompletion left, SlashCommandInputCompletion right) => !(left == right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SendMode left, SendMode right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SendMode left, SendMode right) => !(left == right); /// - public override bool Equals(object? obj) => obj is SlashCommandInputCompletion other && Equals(other); + public override bool Equals(object? obj) => obj is SendMode other && Equals(other); /// - public bool Equals(SlashCommandInputCompletion other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(SendMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -13865,65 +17550,65 @@ public SlashCommandInputCompletion(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override SlashCommandInputCompletion Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override SendMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, SlashCommandInputCompletion value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, SendMode value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SlashCommandInputCompletion)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SendMode)); } } } -/// Coarse command category for grouping and behavior: runtime built-in, skill-backed command, or SDK/client-owned command. +/// Log severity level. Determines how the message is displayed in the timeline. Defaults to "info". [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct SlashCommandKind : IEquatable +public readonly struct SessionLogLevel : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public SlashCommandKind(string value) + public SessionLogLevel(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Command implemented by the runtime. - public static SlashCommandKind Builtin { get; } = new("builtin"); + /// Informational message. + public static SessionLogLevel Info { get; } = new("info"); - /// Command backed by a skill. - public static SlashCommandKind Skill { get; } = new("skill"); + /// Warning message that may require attention. + public static SessionLogLevel Warning { get; } = new("warning"); - /// Command registered by an SDK client or extension. - public static SlashCommandKind Client { get; } = new("client"); + /// Error message describing a failure. + public static SessionLogLevel Error { get; } = new("error"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(SlashCommandKind left, SlashCommandKind right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SessionLogLevel left, SessionLogLevel right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(SlashCommandKind left, SlashCommandKind right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SessionLogLevel left, SessionLogLevel right) => !(left == right); /// - public override bool Equals(object? obj) => obj is SlashCommandKind other && Equals(other); + public override bool Equals(object? obj) => obj is SessionLogLevel other && Equals(other); /// - public bool Equals(SlashCommandKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(SessionLogLevel other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -13931,62 +17616,77 @@ public SlashCommandKind(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override SlashCommandKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override SessionLogLevel Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, SlashCommandKind value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, SessionLogLevel value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SlashCommandKind)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionLogLevel)); } } } -/// Path conventions used by this filesystem. +/// Authentication type. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct SessionFsSetProviderConventions : IEquatable +public readonly struct AuthInfoType : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public SessionFsSetProviderConventions(string value) + public AuthInfoType(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Paths use Windows path conventions. - public static SessionFsSetProviderConventions Windows { get; } = new("windows"); + /// Authentication provided by a GitHub App HMAC credential. + public static AuthInfoType Hmac { get; } = new("hmac"); - /// Paths use POSIX path conventions. - public static SessionFsSetProviderConventions Posix { get; } = new("posix"); + /// Authentication resolved from environment-provided credentials. + public static AuthInfoType Env { get; } = new("env"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(SessionFsSetProviderConventions left, SessionFsSetProviderConventions right) => left.Equals(right); + /// Authentication from an interactive user sign-in. + public static AuthInfoType User { get; } = new("user"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(SessionFsSetProviderConventions left, SessionFsSetProviderConventions right) => !(left == right); + /// Authentication delegated to the GitHub CLI. + public static AuthInfoType GhCli { get; } = new("gh-cli"); + + /// Authentication from an API key credential. + public static AuthInfoType ApiKey { get; } = new("api-key"); + + /// Authentication from a GitHub token. + public static AuthInfoType Token { get; } = new("token"); + + /// Authentication from a Copilot API token. + public static AuthInfoType CopilotApiToken { get; } = new("copilot-api-token"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(AuthInfoType left, AuthInfoType right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(AuthInfoType left, AuthInfoType right) => !(left == right); /// - public override bool Equals(object? obj) => obj is SessionFsSetProviderConventions other && Equals(other); + public override bool Equals(object? obj) => obj is AuthInfoType other && Equals(other); /// - public bool Equals(SessionFsSetProviderConventions other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(AuthInfoType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -13994,62 +17694,68 @@ public SessionFsSetProviderConventions(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override SessionFsSetProviderConventions Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override AuthInfoType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, SessionFsSetProviderConventions value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, AuthInfoType value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionFsSetProviderConventions)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AuthInfoType)); } } } -/// Repository host type. +/// Source category for a collected debug bundle entry. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct SessionContextHostType : IEquatable +public readonly struct DebugCollectLogsSource : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public SessionContextHostType(string value) + public DebugCollectLogsSource(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Session repository is hosted on GitHub. - public static SessionContextHostType GitHub { get; } = new("github"); + /// Session event log. + public static DebugCollectLogsSource Events { get; } = new("events"); - /// Session repository is hosted on Azure DevOps. - public static SessionContextHostType Ado { get; } = new("ado"); + /// Process log for the session. + public static DebugCollectLogsSource ProcessLog { get; } = new("process-log"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(SessionContextHostType left, SessionContextHostType right) => left.Equals(right); + /// Interactive shell log for the session. + public static DebugCollectLogsSource ShellLog { get; } = new("shell-log"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(SessionContextHostType left, SessionContextHostType right) => !(left == right); + /// Caller-provided diagnostic entry. + public static DebugCollectLogsSource Additional { get; } = new("additional"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(DebugCollectLogsSource left, DebugCollectLogsSource right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(DebugCollectLogsSource left, DebugCollectLogsSource right) => !(left == right); /// - public override bool Equals(object? obj) => obj is SessionContextHostType other && Equals(other); + public override bool Equals(object? obj) => obj is DebugCollectLogsSource other && Equals(other); /// - public bool Equals(SessionContextHostType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(DebugCollectLogsSource other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -14057,62 +17763,62 @@ public SessionContextHostType(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override SessionContextHostType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override DebugCollectLogsSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, SessionContextHostType value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, DebugCollectLogsSource value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionContextHostType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(DebugCollectLogsSource)); } } } -/// Whether the remote task originated from CCA or CLI `--remote`. +/// Destination kind that was written. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct RemoteSessionMetadataTaskType : IEquatable +public readonly struct DebugCollectLogsResultKind : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public RemoteSessionMetadataTaskType(string value) + public DebugCollectLogsResultKind(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// GitHub Copilot coding agent task. - public static RemoteSessionMetadataTaskType Cca { get; } = new("cca"); + /// A .tgz archive was written. + public static DebugCollectLogsResultKind Archive { get; } = new("archive"); - /// CLI remote task. - public static RemoteSessionMetadataTaskType Cli { get; } = new("cli"); + /// A directory containing redacted files was written. + public static DebugCollectLogsResultKind Directory { get; } = new("directory"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(RemoteSessionMetadataTaskType left, RemoteSessionMetadataTaskType right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(DebugCollectLogsResultKind left, DebugCollectLogsResultKind right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(RemoteSessionMetadataTaskType left, RemoteSessionMetadataTaskType right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(DebugCollectLogsResultKind left, DebugCollectLogsResultKind right) => !(left == right); /// - public override bool Equals(object? obj) => obj is RemoteSessionMetadataTaskType other && Equals(other); + public override bool Equals(object? obj) => obj is DebugCollectLogsResultKind other && Equals(other); /// - public bool Equals(RemoteSessionMetadataTaskType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(DebugCollectLogsResultKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -14120,62 +17826,62 @@ public RemoteSessionMetadataTaskType(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override RemoteSessionMetadataTaskType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override DebugCollectLogsResultKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, RemoteSessionMetadataTaskType value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, DebugCollectLogsResultKind value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(RemoteSessionMetadataTaskType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(DebugCollectLogsResultKind)); } } } -/// Step status. +/// Kind of caller-provided debug log entry. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct SessionsOpenProgressStatus : IEquatable +public readonly struct DebugCollectLogsEntryKind : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public SessionsOpenProgressStatus(string value) + public DebugCollectLogsEntryKind(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// The step has started and has not yet finished. - public static SessionsOpenProgressStatus InProgress { get; } = new("in-progress"); + /// Include a single server-local file. + public static DebugCollectLogsEntryKind File { get; } = new("file"); - /// The step has completed successfully. - public static SessionsOpenProgressStatus Complete { get; } = new("complete"); + /// Include files from a server-local directory recursively. + public static DebugCollectLogsEntryKind Directory { get; } = new("directory"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(SessionsOpenProgressStatus left, SessionsOpenProgressStatus right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(DebugCollectLogsEntryKind left, DebugCollectLogsEntryKind right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(SessionsOpenProgressStatus left, SessionsOpenProgressStatus right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(DebugCollectLogsEntryKind left, DebugCollectLogsEntryKind right) => !(left == right); /// - public override bool Equals(object? obj) => obj is SessionsOpenProgressStatus other && Equals(other); + public override bool Equals(object? obj) => obj is DebugCollectLogsEntryKind other && Equals(other); /// - public bool Equals(SessionsOpenProgressStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(DebugCollectLogsEntryKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -14183,74 +17889,62 @@ public SessionsOpenProgressStatus(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override SessionsOpenProgressStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override DebugCollectLogsEntryKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, SessionsOpenProgressStatus value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, DebugCollectLogsEntryKind value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionsOpenProgressStatus)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(DebugCollectLogsEntryKind)); } } } -/// Handoff step. +/// How a collected debug entry should be redacted before being staged. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct SessionsOpenProgressStep : IEquatable +public readonly struct DebugCollectLogsRedaction : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public SessionsOpenProgressStep(string value) + public DebugCollectLogsRedaction(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Loading the source session's events from the remote service. - public static SessionsOpenProgressStep LoadSession { get; } = new("load-session"); - - /// Validating that the local repository matches the remote session's repository. - public static SessionsOpenProgressStep ValidateRepo { get; } = new("validate-repo"); - - /// Checking the local working tree for uncommitted changes that would block the handoff. - public static SessionsOpenProgressStep CheckChanges { get; } = new("check-changes"); - - /// Checking out the branch associated with the remote session in the local working tree. - public static SessionsOpenProgressStep CheckoutBranch { get; } = new("checkout-branch"); - - /// Creating the new local session and seeding it with the source session's events. - public static SessionsOpenProgressStep CreateSession { get; } = new("create-session"); + /// Redact the file as plain UTF-8 log text. + public static DebugCollectLogsRedaction PlainText { get; } = new("plain-text"); - /// Persisting the newly-created local session to disk. - public static SessionsOpenProgressStep SaveSession { get; } = new("save-session"); + /// Redact each non-empty line as a session event JSON object, falling back to plain-text redaction for malformed lines. + public static DebugCollectLogsRedaction EventsJsonl { get; } = new("events-jsonl"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(SessionsOpenProgressStep left, SessionsOpenProgressStep right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(DebugCollectLogsRedaction left, DebugCollectLogsRedaction right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(SessionsOpenProgressStep left, SessionsOpenProgressStep right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(DebugCollectLogsRedaction left, DebugCollectLogsRedaction right) => !(left == right); /// - public override bool Equals(object? obj) => obj is SessionsOpenProgressStep other && Equals(other); + public override bool Equals(object? obj) => obj is DebugCollectLogsRedaction other && Equals(other); /// - public bool Equals(SessionsOpenProgressStep other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(DebugCollectLogsRedaction other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -14258,71 +17952,65 @@ public SessionsOpenProgressStep(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override SessionsOpenProgressStep Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override DebugCollectLogsRedaction Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, SessionsOpenProgressStep value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, DebugCollectLogsRedaction value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionsOpenProgressStep)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(DebugCollectLogsRedaction)); } } } -/// Outcome of the open request. +/// Cumulative resource ceiling that stopped a factory run. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct SessionsOpenStatus : IEquatable +public readonly struct FactoryRunFailureKind : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public SessionsOpenStatus(string value) + public FactoryRunFailureKind(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// A new session was created. - public static SessionsOpenStatus Created { get; } = new("created"); - - /// An existing session was loaded or reattached. - public static SessionsOpenStatus Resumed { get; } = new("resumed"); - - /// No matching persisted session was found. - public static SessionsOpenStatus NotFound { get; } = new("not_found"); + /// The run admitted the approved maximum total number of subagents. + public static FactoryRunFailureKind MaxTotalSubagents { get; } = new("maxTotalSubagents"); - /// Connected to an existing remote session. - public static SessionsOpenStatus Connected { get; } = new("connected"); + /// The run reached the approved accumulated active-execution time in seconds. + public static FactoryRunFailureKind TimeoutSeconds { get; } = new("timeoutSeconds"); - /// Remote session was handed off to a new local session. - public static SessionsOpenStatus HandedOff { get; } = new("handed_off"); + /// The run's settled subagent model usage exceeded the approved AI-credit ceiling, or no headroom remained for another subagent. + public static FactoryRunFailureKind MaxAiCredits { get; } = new("maxAiCredits"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(SessionsOpenStatus left, SessionsOpenStatus right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(FactoryRunFailureKind left, FactoryRunFailureKind right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(SessionsOpenStatus left, SessionsOpenStatus right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(FactoryRunFailureKind left, FactoryRunFailureKind right) => !(left == right); /// - public override bool Equals(object? obj) => obj is SessionsOpenStatus other && Equals(other); + public override bool Equals(object? obj) => obj is FactoryRunFailureKind other && Equals(other); /// - public bool Equals(SessionsOpenStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(FactoryRunFailureKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -14330,62 +18018,86 @@ public SessionsOpenStatus(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override SessionsOpenStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override FactoryRunFailureKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, SessionsOpenStatus value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, FactoryRunFailureKind value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionsOpenStatus)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FactoryRunFailureKind)); } } } -/// Neutral SDK discriminator for the connected remote session kind. +/// Execution-critical factory storage operation. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct ConnectedRemoteSessionMetadataKind : IEquatable +public readonly struct FactoryDurableOperation : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public ConnectedRemoteSessionMetadataKind(string value) + public FactoryDurableOperation(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Remote CLI session. - public static ConnectedRemoteSessionMetadataKind RemoteSession { get; } = new("remote-session"); + /// Creating the durable run and declared phases. + public static FactoryDurableOperation CreateRun { get; } = new("createRun"); - /// GitHub Copilot coding agent session. - public static ConnectedRemoteSessionMetadataKind CodingAgent { get; } = new("coding-agent"); + /// Persisting the transition to running. + public static FactoryDurableOperation MarkRunStarted { get; } = new("markRunStarted"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(ConnectedRemoteSessionMetadataKind left, ConnectedRemoteSessionMetadataKind right) => left.Equals(right); + /// Persisting the terminal run envelope. + public static FactoryDurableOperation FinishRun { get; } = new("finishRun"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(ConnectedRemoteSessionMetadataKind left, ConnectedRemoteSessionMetadataKind right) => !(left == right); + /// Persisting subagent admission accounting. + public static FactoryDurableOperation ReserveAgent { get; } = new("reserveAgent"); + + /// Rolling back an uncommitted subagent admission. + public static FactoryDurableOperation ReleaseAgent { get; } = new("releaseAgent"); + + /// Persisting an idempotent model-usage charge. + public static FactoryDurableOperation ChargeCredit { get; } = new("chargeCredit"); + + /// Persisting active execution time. + public static FactoryDurableOperation AddElapsed { get; } = new("addElapsed"); + + /// Reading the authoritative AI-credit total. + public static FactoryDurableOperation ReconcileCreditTotal { get; } = new("reconcileCreditTotal"); + + /// Reading a journal entry without treating storage failure as a cache miss. + public static FactoryDurableOperation JournalGet { get; } = new("journalGet"); + + /// Persisting a journal entry before reporting success. + public static FactoryDurableOperation JournalPut { get; } = new("journalPut"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(FactoryDurableOperation left, FactoryDurableOperation right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(FactoryDurableOperation left, FactoryDurableOperation right) => !(left == right); /// - public override bool Equals(object? obj) => obj is ConnectedRemoteSessionMetadataKind other && Equals(other); + public override bool Equals(object? obj) => obj is FactoryDurableOperation other && Equals(other); /// - public bool Equals(ConnectedRemoteSessionMetadataKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(FactoryDurableOperation other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -14393,65 +18105,74 @@ public ConnectedRemoteSessionMetadataKind(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override ConnectedRemoteSessionMetadataKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override FactoryDurableOperation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, ConnectedRemoteSessionMetadataKind value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, FactoryDurableOperation value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ConnectedRemoteSessionMetadataKind)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FactoryDurableOperation)); } } } -/// Which session sources to include. Defaults to `local` for backward compatibility. +/// Current or terminal state of a factory run. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct SessionSource : IEquatable +public readonly struct FactoryRunStatus : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public SessionSource(string value) + public FactoryRunStatus(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Return only local sessions. - public static SessionSource Local { get; } = new("local"); + /// The run was minted and is awaiting approval. + public static FactoryRunStatus Pending { get; } = new("pending"); - /// Return only remote sessions. - public static SessionSource Remote { get; } = new("remote"); + /// The run is executing. + public static FactoryRunStatus Running { get; } = new("running"); - /// Return both local and remote sessions. - public static SessionSource All { get; } = new("all"); + /// The run completed successfully. + public static FactoryRunStatus Completed { get; } = new("completed"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(SessionSource left, SessionSource right) => left.Equals(right); + /// The run was interrupted while resource budget remained. + public static FactoryRunStatus Halted { get; } = new("halted"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(SessionSource left, SessionSource right) => !(left == right); + /// The run was cancelled before completion. + public static FactoryRunStatus Cancelled { get; } = new("cancelled"); + + /// The factory body failed or reached a cumulative resource ceiling. + public static FactoryRunStatus Error { get; } = new("error"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(FactoryRunStatus left, FactoryRunStatus right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(FactoryRunStatus left, FactoryRunStatus right) => !(left == right); /// - public override bool Equals(object? obj) => obj is SessionSource other && Equals(other); + public override bool Equals(object? obj) => obj is FactoryRunStatus other && Equals(other); /// - public bool Equals(SessionSource other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(FactoryRunStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -14459,71 +18180,68 @@ public SessionSource(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override SessionSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override FactoryRunStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, SessionSource value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, FactoryRunStatus value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionSource)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FactoryRunStatus)); } } } -/// Kind of attention required when status === "attention". Meaningful only when status === "attention". +/// Derived lifecycle state of a factory phase. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct AgentRegistryLiveTargetEntryAttentionKind : IEquatable +public readonly struct FactoryPhaseStatus : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public AgentRegistryLiveTargetEntryAttentionKind(string value) + public FactoryPhaseStatus(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . - public string Value => _value ?? string.Empty; - - /// Session is blocked on an unrecoverable error. - public static AgentRegistryLiveTargetEntryAttentionKind Error { get; } = new("error"); + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Session is waiting for a tool-permission decision. - public static AgentRegistryLiveTargetEntryAttentionKind Permission { get; } = new("permission"); + /// The phase has not been entered yet. + public static FactoryPhaseStatus Pending { get; } = new("pending"); - /// Session is waiting for the user to approve or reject a plan. - public static AgentRegistryLiveTargetEntryAttentionKind ExitPlan { get; } = new("exit_plan"); + /// The phase is currently entered and accumulating active time. + public static FactoryPhaseStatus Active { get; } = new("active"); - /// Session is waiting on an elicitation prompt. - public static AgentRegistryLiveTargetEntryAttentionKind Elicitation { get; } = new("elicitation"); + /// The phase was entered and has since been closed. + public static FactoryPhaseStatus Completed { get; } = new("completed"); - /// Session is waiting for free-form user input. - public static AgentRegistryLiveTargetEntryAttentionKind UserInput { get; } = new("user_input"); + /// The phase was never entered because a later phase was entered or the run reached a terminal state. + public static FactoryPhaseStatus Skipped { get; } = new("skipped"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(AgentRegistryLiveTargetEntryAttentionKind left, AgentRegistryLiveTargetEntryAttentionKind right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(FactoryPhaseStatus left, FactoryPhaseStatus right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(AgentRegistryLiveTargetEntryAttentionKind left, AgentRegistryLiveTargetEntryAttentionKind right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(FactoryPhaseStatus left, FactoryPhaseStatus right) => !(left == right); /// - public override bool Equals(object? obj) => obj is AgentRegistryLiveTargetEntryAttentionKind other && Equals(other); + public override bool Equals(object? obj) => obj is FactoryPhaseStatus other && Equals(other); /// - public bool Equals(AgentRegistryLiveTargetEntryAttentionKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(FactoryPhaseStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -14531,62 +18249,62 @@ public AgentRegistryLiveTargetEntryAttentionKind(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override AgentRegistryLiveTargetEntryAttentionKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override FactoryPhaseStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, AgentRegistryLiveTargetEntryAttentionKind value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, FactoryPhaseStatus value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentRegistryLiveTargetEntryAttentionKind)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FactoryPhaseStatus)); } } } -/// Process kind tag for the registry entry. +/// Kind of factory progress line. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct AgentRegistryLiveTargetEntryKind : IEquatable +public readonly struct FactoryLogLineKind : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public AgentRegistryLiveTargetEntryKind(string value) + public FactoryLogLineKind(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Interactive Copilot CLI exposing a UI server (legacy/normal CLI process). - public static AgentRegistryLiveTargetEntryKind UiServer { get; } = new("ui-server"); + /// A narrator log line. + public static FactoryLogLineKind Log { get; } = new("log"); - /// Headless `--server --managed-server` child spawned by a controller. - public static AgentRegistryLiveTargetEntryKind ManagedServer { get; } = new("managed-server"); + /// A named factory phase marker. + public static FactoryLogLineKind Phase { get; } = new("phase"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(AgentRegistryLiveTargetEntryKind left, AgentRegistryLiveTargetEntryKind right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(FactoryLogLineKind left, FactoryLogLineKind right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(AgentRegistryLiveTargetEntryKind left, AgentRegistryLiveTargetEntryKind right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(FactoryLogLineKind left, FactoryLogLineKind right) => !(left == right); /// - public override bool Equals(object? obj) => obj is AgentRegistryLiveTargetEntryKind other && Equals(other); + public override bool Equals(object? obj) => obj is FactoryLogLineKind other && Equals(other); /// - public bool Equals(AgentRegistryLiveTargetEntryKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(FactoryLogLineKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -14594,62 +18312,62 @@ public AgentRegistryLiveTargetEntryKind(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override AgentRegistryLiveTargetEntryKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override FactoryLogLineKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, AgentRegistryLiveTargetEntryKind value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, FactoryLogLineKind value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentRegistryLiveTargetEntryKind)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FactoryLogLineKind)); } } } -/// How the most recent turn ended (clean vs aborted). Lets the renderer distinguish done from done_cancelled. +/// Allowed values for the `WorkspacesWorkspaceDetailsHostType` enumeration. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct AgentRegistryLiveTargetEntryLastTerminalEvent : IEquatable +public readonly struct WorkspacesWorkspaceDetailsHostType : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public AgentRegistryLiveTargetEntryLastTerminalEvent(string value) + public WorkspacesWorkspaceDetailsHostType(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Last turn ended cleanly (model returned a final assistant message). - public static AgentRegistryLiveTargetEntryLastTerminalEvent TurnEnd { get; } = new("turn_end"); + /// Workspace repository is hosted on GitHub. + public static WorkspacesWorkspaceDetailsHostType GitHub { get; } = new("github"); - /// Last turn was aborted (e.g. user interrupted). - public static AgentRegistryLiveTargetEntryLastTerminalEvent Abort { get; } = new("abort"); + /// Workspace repository is hosted on Azure DevOps. + public static WorkspacesWorkspaceDetailsHostType Ado { get; } = new("ado"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(AgentRegistryLiveTargetEntryLastTerminalEvent left, AgentRegistryLiveTargetEntryLastTerminalEvent right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(WorkspacesWorkspaceDetailsHostType left, WorkspacesWorkspaceDetailsHostType right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(AgentRegistryLiveTargetEntryLastTerminalEvent left, AgentRegistryLiveTargetEntryLastTerminalEvent right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(WorkspacesWorkspaceDetailsHostType left, WorkspacesWorkspaceDetailsHostType right) => !(left == right); /// - public override bool Equals(object? obj) => obj is AgentRegistryLiveTargetEntryLastTerminalEvent other && Equals(other); + public override bool Equals(object? obj) => obj is WorkspacesWorkspaceDetailsHostType other && Equals(other); /// - public bool Equals(AgentRegistryLiveTargetEntryLastTerminalEvent other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(WorkspacesWorkspaceDetailsHostType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -14657,68 +18375,68 @@ public AgentRegistryLiveTargetEntryLastTerminalEvent(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override AgentRegistryLiveTargetEntryLastTerminalEvent Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override WorkspacesWorkspaceDetailsHostType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, AgentRegistryLiveTargetEntryLastTerminalEvent value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, WorkspacesWorkspaceDetailsHostType value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentRegistryLiveTargetEntryLastTerminalEvent)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(WorkspacesWorkspaceDetailsHostType)); } } } -/// Coarse lifecycle status of the foreground session. +/// Type of change represented by this file diff. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct AgentRegistryLiveTargetEntryStatus : IEquatable +public readonly struct WorkspaceDiffFileChangeType : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public AgentRegistryLiveTargetEntryStatus(string value) + public WorkspaceDiffFileChangeType(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Session is actively processing a turn. - public static AgentRegistryLiveTargetEntryStatus Working { get; } = new("working"); + /// The file was added. + public static WorkspaceDiffFileChangeType Added { get; } = new("added"); - /// Session is idle, waiting for input. - public static AgentRegistryLiveTargetEntryStatus Waiting { get; } = new("waiting"); + /// The file was modified. + public static WorkspaceDiffFileChangeType Modified { get; } = new("modified"); - /// Last turn completed successfully. - public static AgentRegistryLiveTargetEntryStatus Done { get; } = new("done"); + /// The file was deleted. + public static WorkspaceDiffFileChangeType Deleted { get; } = new("deleted"); - /// Session needs user attention (see attentionKind for the specific reason). - public static AgentRegistryLiveTargetEntryStatus Attention { get; } = new("attention"); + /// The file was renamed. + public static WorkspaceDiffFileChangeType Renamed { get; } = new("renamed"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(AgentRegistryLiveTargetEntryStatus left, AgentRegistryLiveTargetEntryStatus right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(WorkspaceDiffFileChangeType left, WorkspaceDiffFileChangeType right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(AgentRegistryLiveTargetEntryStatus left, AgentRegistryLiveTargetEntryStatus right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(WorkspaceDiffFileChangeType left, WorkspaceDiffFileChangeType right) => !(left == right); /// - public override bool Equals(object? obj) => obj is AgentRegistryLiveTargetEntryStatus other && Equals(other); + public override bool Equals(object? obj) => obj is WorkspaceDiffFileChangeType other && Equals(other); /// - public bool Equals(AgentRegistryLiveTargetEntryStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(WorkspaceDiffFileChangeType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -14726,65 +18444,65 @@ public AgentRegistryLiveTargetEntryStatus(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override AgentRegistryLiveTargetEntryStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override WorkspaceDiffFileChangeType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, AgentRegistryLiveTargetEntryStatus value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, WorkspaceDiffFileChangeType value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentRegistryLiveTargetEntryStatus)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(WorkspaceDiffFileChangeType)); } } } -/// Categorized reason for log-open failure. +/// Diff mode requested by the client. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct AgentRegistryLogCaptureOpenErrorReason : IEquatable +public readonly struct WorkspaceDiffMode : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public AgentRegistryLogCaptureOpenErrorReason(string value) + public WorkspaceDiffMode(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Filesystem permission denied opening the log file. - public static AgentRegistryLogCaptureOpenErrorReason Permission { get; } = new("permission"); + /// Return staged, unstaged, and untracked working tree changes. + public static WorkspaceDiffMode Unstaged { get; } = new("unstaged"); - /// No space left on device. - public static AgentRegistryLogCaptureOpenErrorReason DiskFull { get; } = new("disk_full"); + /// Return changes compared with the default branch. + public static WorkspaceDiffMode Branch { get; } = new("branch"); - /// Other / uncategorized open failure. - public static AgentRegistryLogCaptureOpenErrorReason Other { get; } = new("other"); + /// Return the cumulative diff of files Copilot changed this session (used in non-git workspaces). + public static WorkspaceDiffMode Session { get; } = new("session"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(AgentRegistryLogCaptureOpenErrorReason left, AgentRegistryLogCaptureOpenErrorReason right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(WorkspaceDiffMode left, WorkspaceDiffMode right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(AgentRegistryLogCaptureOpenErrorReason left, AgentRegistryLogCaptureOpenErrorReason right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(WorkspaceDiffMode left, WorkspaceDiffMode right) => !(left == right); /// - public override bool Equals(object? obj) => obj is AgentRegistryLogCaptureOpenErrorReason other && Equals(other); + public override bool Equals(object? obj) => obj is WorkspaceDiffMode other && Equals(other); /// - public bool Equals(AgentRegistryLogCaptureOpenErrorReason other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(WorkspaceDiffMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -14792,71 +18510,65 @@ public AgentRegistryLogCaptureOpenErrorReason(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override AgentRegistryLogCaptureOpenErrorReason Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override WorkspaceDiffMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, AgentRegistryLogCaptureOpenErrorReason value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, WorkspaceDiffMode value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentRegistryLogCaptureOpenErrorReason)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(WorkspaceDiffMode)); } } } -/// Which parameter field was invalid. Omitted when the rejection is not field-specific. +/// Reason a rewind read (rewind points, file-restore preview, or session diff) could not be answered from the session's file-change captures. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct AgentRegistrySpawnValidationErrorField : IEquatable +public readonly struct HistoryRewindUnavailableReason : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public AgentRegistrySpawnValidationErrorField(string value) + public HistoryRewindUnavailableReason(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// The cwd parameter. - public static AgentRegistrySpawnValidationErrorField Cwd { get; } = new("cwd"); - - /// The session name parameter. - public static AgentRegistrySpawnValidationErrorField Name { get; } = new("name"); - - /// The agentName parameter. - public static AgentRegistrySpawnValidationErrorField AgentName { get; } = new("agentName"); + /// The session did not opt into file-change tracking before its first turn. + public static HistoryRewindUnavailableReason FileChangeTrackingDisabled { get; } = new("file-change-tracking-disabled"); - /// The model parameter. - public static AgentRegistrySpawnValidationErrorField Model { get; } = new("model"); + /// The session still has work that may mutate files or history. Transient: the same request succeeds once the session settles, so callers should retry rather than treat it as a failure. + public static HistoryRewindUnavailableReason SessionBusy { get; } = new("session-busy"); - /// The permissionMode parameter. - public static AgentRegistrySpawnValidationErrorField PermissionMode { get; } = new("permissionMode"); + /// Remote-backed rewind routing is not supported. + public static HistoryRewindUnavailableReason UnsupportedRemoteSession { get; } = new("unsupported-remote-session"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(AgentRegistrySpawnValidationErrorField left, AgentRegistrySpawnValidationErrorField right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(HistoryRewindUnavailableReason left, HistoryRewindUnavailableReason right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(AgentRegistrySpawnValidationErrorField left, AgentRegistrySpawnValidationErrorField right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(HistoryRewindUnavailableReason left, HistoryRewindUnavailableReason right) => !(left == right); /// - public override bool Equals(object? obj) => obj is AgentRegistrySpawnValidationErrorField other && Equals(other); + public override bool Equals(object? obj) => obj is HistoryRewindUnavailableReason other && Equals(other); /// - public bool Equals(AgentRegistrySpawnValidationErrorField other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(HistoryRewindUnavailableReason other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -14864,74 +18576,62 @@ public AgentRegistrySpawnValidationErrorField(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override AgentRegistrySpawnValidationErrorField Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override HistoryRewindUnavailableReason Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, AgentRegistrySpawnValidationErrorField value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, HistoryRewindUnavailableReason value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentRegistrySpawnValidationErrorField)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(HistoryRewindUnavailableReason)); } } } -/// Categorized reason for the rejection. Low-cardinality enum so telemetry can aggregate by reason without leaking raw paths or agent/model names. +/// Whether task execution is synchronously awaited or managed in the background. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct AgentRegistrySpawnValidationErrorReason : IEquatable +public readonly struct TaskExecutionMode : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . - [JsonConstructor] - public AgentRegistrySpawnValidationErrorReason(string value) - { - ArgumentException.ThrowIfNullOrWhiteSpace(value); - _value = value; - } - - /// Gets the value associated with this . - public string Value => _value ?? string.Empty; - - /// Provided cwd does not exist on disk. - public static AgentRegistrySpawnValidationErrorReason CwdNotFound { get; } = new("cwd-not-found"); - - /// Provided cwd exists but is not a directory. - public static AgentRegistrySpawnValidationErrorReason CwdNotDirectory { get; } = new("cwd-not-directory"); - - /// Session name failed validateSessionName. - public static AgentRegistrySpawnValidationErrorReason InvalidName { get; } = new("invalid-name"); + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public TaskExecutionMode(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Requested agent name was not found in builtin or custom agents. - public static AgentRegistrySpawnValidationErrorReason UnknownAgent { get; } = new("unknown-agent"); + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Requested model is not available to this session. - public static AgentRegistrySpawnValidationErrorReason UnknownModel { get; } = new("unknown-model"); + /// The task was started with synchronous waiting. + public static TaskExecutionMode Sync { get; } = new("sync"); - /// Caller asked for permissionMode='yolo' but the controller is not currently in allow-all mode. - public static AgentRegistrySpawnValidationErrorReason YoloNotAllowed { get; } = new("yolo-not-allowed"); + /// The task is managed in the background. + public static TaskExecutionMode Background { get; } = new("background"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(AgentRegistrySpawnValidationErrorReason left, AgentRegistrySpawnValidationErrorReason right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(TaskExecutionMode left, TaskExecutionMode right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(AgentRegistrySpawnValidationErrorReason left, AgentRegistrySpawnValidationErrorReason right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(TaskExecutionMode left, TaskExecutionMode right) => !(left == right); /// - public override bool Equals(object? obj) => obj is AgentRegistrySpawnValidationErrorReason other && Equals(other); + public override bool Equals(object? obj) => obj is TaskExecutionMode other && Equals(other); /// - public bool Equals(AgentRegistrySpawnValidationErrorReason other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(TaskExecutionMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -14939,62 +18639,71 @@ public AgentRegistrySpawnValidationErrorReason(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override AgentRegistrySpawnValidationErrorReason Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override TaskExecutionMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, AgentRegistrySpawnValidationErrorReason value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, TaskExecutionMode value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentRegistrySpawnValidationErrorReason)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskExecutionMode)); } } } -/// Permission posture for the new session. 'yolo' requires the controller-local session to currently be in allow-all mode. +/// Current lifecycle status of the task. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct AgentRegistrySpawnPermissionMode : IEquatable +public readonly struct TaskStatus : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public AgentRegistrySpawnPermissionMode(string value) + public TaskStatus(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Standard permission posture (prompts for each request). - public static AgentRegistrySpawnPermissionMode Default { get; } = new("default"); + /// The task is actively executing. + public static TaskStatus Running { get; } = new("running"); - /// Full allow-all (requires the controller-local session to currently be in allow-all mode). - public static AgentRegistrySpawnPermissionMode Yolo { get; } = new("yolo"); + /// The task is waiting for additional input. + public static TaskStatus Idle { get; } = new("idle"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(AgentRegistrySpawnPermissionMode left, AgentRegistrySpawnPermissionMode right) => left.Equals(right); + /// The task finished successfully. + public static TaskStatus Completed { get; } = new("completed"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(AgentRegistrySpawnPermissionMode left, AgentRegistrySpawnPermissionMode right) => !(left == right); + /// The task finished with an error. + public static TaskStatus Failed { get; } = new("failed"); + + /// The task was cancelled before completion. + public static TaskStatus Cancelled { get; } = new("cancelled"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(TaskStatus left, TaskStatus right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(TaskStatus left, TaskStatus right) => !(left == right); /// - public override bool Equals(object? obj) => obj is AgentRegistrySpawnPermissionMode other && Equals(other); + public override bool Equals(object? obj) => obj is TaskStatus other && Equals(other); /// - public bool Equals(AgentRegistrySpawnPermissionMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(TaskStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -15002,68 +18711,62 @@ public AgentRegistrySpawnPermissionMode(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override AgentRegistrySpawnPermissionMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override TaskStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, AgentRegistrySpawnPermissionMode value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, TaskStatus value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AgentRegistrySpawnPermissionMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskStatus)); } } } -/// The UI mode the agent was in when this message was sent. Defaults to the session's current mode. +/// Whether the shell runs inside a managed PTY session or as an independent background process. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct SendAgentMode : IEquatable +public readonly struct TaskShellInfoAttachmentMode : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public SendAgentMode(string value) + public TaskShellInfoAttachmentMode(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// The agent is responding interactively to the user. - public static SendAgentMode Interactive { get; } = new("interactive"); - - /// The agent is preparing a plan before making changes. - public static SendAgentMode Plan { get; } = new("plan"); - - /// The agent is working autonomously toward task completion. - public static SendAgentMode Autopilot { get; } = new("autopilot"); + /// The shell runs in a managed PTY session. + public static TaskShellInfoAttachmentMode Attached { get; } = new("attached"); - /// The agent is in shell-focused UI mode. - public static SendAgentMode Shell { get; } = new("shell"); + /// The shell runs as an independent background process. + public static TaskShellInfoAttachmentMode Detached { get; } = new("detached"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(SendAgentMode left, SendAgentMode right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(TaskShellInfoAttachmentMode left, TaskShellInfoAttachmentMode right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(SendAgentMode left, SendAgentMode right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(TaskShellInfoAttachmentMode left, TaskShellInfoAttachmentMode right) => !(left == right); /// - public override bool Equals(object? obj) => obj is SendAgentMode other && Equals(other); + public override bool Equals(object? obj) => obj is TaskShellInfoAttachmentMode other && Equals(other); /// - public bool Equals(SendAgentMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(TaskShellInfoAttachmentMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -15071,62 +18774,62 @@ public SendAgentMode(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override SendAgentMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override TaskShellInfoAttachmentMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, SendAgentMode value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, TaskShellInfoAttachmentMode value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SendAgentMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskShellInfoAttachmentMode)); } } } -/// How to deliver the message. `enqueue` (default) appends to the message queue. `immediate` interjects during an in-progress turn. +/// Consumer allowed to call an MCP tool. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct SendMode : IEquatable +public readonly struct McpToolUiVisibility : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public SendMode(string value) + public McpToolUiVisibility(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Append the message to the normal session queue. - public static SendMode Enqueue { get; } = new("enqueue"); + /// The model may call the tool. + public static McpToolUiVisibility Model { get; } = new("model"); - /// Interject the message during the in-progress turn. - public static SendMode Immediate { get; } = new("immediate"); + /// An MCP App view may call the tool. + public static McpToolUiVisibility App { get; } = new("app"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(SendMode left, SendMode right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(McpToolUiVisibility left, McpToolUiVisibility right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(SendMode left, SendMode right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(McpToolUiVisibility left, McpToolUiVisibility right) => !(left == right); /// - public override bool Equals(object? obj) => obj is SendMode other && Equals(other); + public override bool Equals(object? obj) => obj is McpToolUiVisibility other && Equals(other); /// - public bool Equals(SendMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(McpToolUiVisibility other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -15134,65 +18837,65 @@ public SendMode(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override SendMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override McpToolUiVisibility Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, SendMode value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, McpToolUiVisibility value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SendMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpToolUiVisibility)); } } } -/// Log severity level. Determines how the message is displayed in the timeline. Defaults to "info". +/// Outcome of the sampling inference. 'success' produced a response; 'failure' encountered an error (including agent-side rejection by content filter or criteria); 'cancelled' the caller cancelled this execution via cancelSamplingExecution. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct SessionLogLevel : IEquatable +public readonly struct McpSamplingExecutionAction : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public SessionLogLevel(string value) + public McpSamplingExecutionAction(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Informational message. - public static SessionLogLevel Info { get; } = new("info"); + /// The sampling inference completed and produced a result. + public static McpSamplingExecutionAction Success { get; } = new("success"); - /// Warning message that may require attention. - public static SessionLogLevel Warning { get; } = new("warning"); + /// The sampling inference failed or was rejected. + public static McpSamplingExecutionAction Failure { get; } = new("failure"); - /// Error message describing a failure. - public static SessionLogLevel Error { get; } = new("error"); + /// The sampling inference was cancelled before completion. + public static McpSamplingExecutionAction Cancelled { get; } = new("cancelled"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(SessionLogLevel left, SessionLogLevel right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(McpSamplingExecutionAction left, McpSamplingExecutionAction right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(SessionLogLevel left, SessionLogLevel right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(McpSamplingExecutionAction left, McpSamplingExecutionAction right) => !(left == right); /// - public override bool Equals(object? obj) => obj is SessionLogLevel other && Equals(other); + public override bool Equals(object? obj) => obj is McpSamplingExecutionAction other && Equals(other); /// - public bool Equals(SessionLogLevel other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(McpSamplingExecutionAction other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -15200,77 +18903,62 @@ public SessionLogLevel(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override SessionLogLevel Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override McpSamplingExecutionAction Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, SessionLogLevel value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, McpSamplingExecutionAction value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionLogLevel)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpSamplingExecutionAction)); } } } -/// Authentication type. +/// How environment-variable values supplied to MCP servers are resolved. "direct" passes literal string values; "indirect" treats values as references (e.g. names of environment variables on the host) that the runtime resolves before launch. Defaults to the runtime's startup mode; clients that intentionally launch MCP servers with literal values (e.g. CLI prompt mode and ACP) set this to "direct". [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct AuthInfoType : IEquatable +public readonly struct McpSetEnvValueModeDetails : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public AuthInfoType(string value) + public McpSetEnvValueModeDetails(string value) { - ArgumentException.ThrowIfNullOrWhiteSpace(value); - _value = value; - } - - /// Gets the value associated with this . - public string Value => _value ?? string.Empty; - - /// Authentication provided by a GitHub App HMAC credential. - public static AuthInfoType Hmac { get; } = new("hmac"); - - /// Authentication resolved from environment-provided credentials. - public static AuthInfoType Env { get; } = new("env"); - - /// Authentication from an interactive user sign-in. - public static AuthInfoType User { get; } = new("user"); - - /// Authentication delegated to the GitHub CLI. - public static AuthInfoType GhCli { get; } = new("gh-cli"); + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } - /// Authentication from an API key credential. - public static AuthInfoType ApiKey { get; } = new("api-key"); + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; - /// Authentication from a GitHub token. - public static AuthInfoType Token { get; } = new("token"); + /// Treat MCP server environment values as literal strings. + public static McpSetEnvValueModeDetails Direct { get; } = new("direct"); - /// Authentication from a Copilot API token. - public static AuthInfoType CopilotApiToken { get; } = new("copilot-api-token"); + /// Treat MCP server environment values as host-side references to resolve before launch. + public static McpSetEnvValueModeDetails Indirect { get; } = new("indirect"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(AuthInfoType left, AuthInfoType right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(McpSetEnvValueModeDetails left, McpSetEnvValueModeDetails right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(AuthInfoType left, AuthInfoType right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(McpSetEnvValueModeDetails left, McpSetEnvValueModeDetails right) => !(left == right); /// - public override bool Equals(object? obj) => obj is AuthInfoType other && Equals(other); + public override bool Equals(object? obj) => obj is McpSetEnvValueModeDetails other && Equals(other); /// - public bool Equals(AuthInfoType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(McpSetEnvValueModeDetails other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -15278,68 +18966,62 @@ public AuthInfoType(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override AuthInfoType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override McpSetEnvValueModeDetails Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, AuthInfoType value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, McpSetEnvValueModeDetails value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AuthInfoType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpSetEnvValueModeDetails)); } } } -/// Source category for a collected debug bundle entry. +/// OAuth grant type override for this login. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct DebugCollectLogsSource : IEquatable +public readonly struct McpOauthLoginGrantType : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public DebugCollectLogsSource(string value) + public McpOauthLoginGrantType(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Session event log. - public static DebugCollectLogsSource Events { get; } = new("events"); - - /// Process log for the session. - public static DebugCollectLogsSource ProcessLog { get; } = new("process-log"); - - /// Interactive shell log for the session. - public static DebugCollectLogsSource ShellLog { get; } = new("shell-log"); + /// Interactive browser-based OAuth flow using an authorization code, typically with PKCE. + public static McpOauthLoginGrantType AuthorizationCode { get; } = new("authorization_code"); - /// Caller-provided diagnostic entry. - public static DebugCollectLogsSource Additional { get; } = new("additional"); + /// Headless OAuth flow where a confidential client authenticates directly with a client secret. + public static McpOauthLoginGrantType ClientCredentials { get; } = new("client_credentials"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(DebugCollectLogsSource left, DebugCollectLogsSource right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(McpOauthLoginGrantType left, McpOauthLoginGrantType right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(DebugCollectLogsSource left, DebugCollectLogsSource right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(McpOauthLoginGrantType left, McpOauthLoginGrantType right) => !(left == right); /// - public override bool Equals(object? obj) => obj is DebugCollectLogsSource other && Equals(other); + public override bool Equals(object? obj) => obj is McpOauthLoginGrantType other && Equals(other); /// - public bool Equals(DebugCollectLogsSource other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(McpOauthLoginGrantType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -15347,62 +19029,65 @@ public DebugCollectLogsSource(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override DebugCollectLogsSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override McpOauthLoginGrantType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, DebugCollectLogsSource value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, McpOauthLoginGrantType value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(DebugCollectLogsSource)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpOauthLoginGrantType)); } } } -/// Destination kind that was written. +/// Allowed values for the `McpAppsSetHostContextDetailsAvailableDisplayMode` enumeration. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct DebugCollectLogsResultKind : IEquatable +public readonly struct McpAppsSetHostContextDetailsAvailableDisplayMode : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public DebugCollectLogsResultKind(string value) + public McpAppsSetHostContextDetailsAvailableDisplayMode(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// A .tgz archive was written. - public static DebugCollectLogsResultKind Archive { get; } = new("archive"); + /// Rendered inline within the host conversation surface. + public static McpAppsSetHostContextDetailsAvailableDisplayMode Inline { get; } = new("inline"); - /// A directory containing redacted files was written. - public static DebugCollectLogsResultKind Directory { get; } = new("directory"); + /// Rendered as a fullscreen overlay. + public static McpAppsSetHostContextDetailsAvailableDisplayMode Fullscreen { get; } = new("fullscreen"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(DebugCollectLogsResultKind left, DebugCollectLogsResultKind right) => left.Equals(right); + /// Rendered as a picture-in-picture floating panel. + public static McpAppsSetHostContextDetailsAvailableDisplayMode Pip { get; } = new("pip"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(DebugCollectLogsResultKind left, DebugCollectLogsResultKind right) => !(left == right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(McpAppsSetHostContextDetailsAvailableDisplayMode left, McpAppsSetHostContextDetailsAvailableDisplayMode right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(McpAppsSetHostContextDetailsAvailableDisplayMode left, McpAppsSetHostContextDetailsAvailableDisplayMode right) => !(left == right); /// - public override bool Equals(object? obj) => obj is DebugCollectLogsResultKind other && Equals(other); + public override bool Equals(object? obj) => obj is McpAppsSetHostContextDetailsAvailableDisplayMode other && Equals(other); /// - public bool Equals(DebugCollectLogsResultKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(McpAppsSetHostContextDetailsAvailableDisplayMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -15410,62 +19095,65 @@ public DebugCollectLogsResultKind(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override DebugCollectLogsResultKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override McpAppsSetHostContextDetailsAvailableDisplayMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, DebugCollectLogsResultKind value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, McpAppsSetHostContextDetailsAvailableDisplayMode value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(DebugCollectLogsResultKind)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpAppsSetHostContextDetailsAvailableDisplayMode)); } } } -/// Kind of caller-provided debug log entry. +/// Current display mode (SEP-1865). [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct DebugCollectLogsEntryKind : IEquatable +public readonly struct McpAppsSetHostContextDetailsDisplayMode : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public DebugCollectLogsEntryKind(string value) + public McpAppsSetHostContextDetailsDisplayMode(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Include a single server-local file. - public static DebugCollectLogsEntryKind File { get; } = new("file"); + /// Rendered inline within the host conversation surface. + public static McpAppsSetHostContextDetailsDisplayMode Inline { get; } = new("inline"); - /// Include files from a server-local directory recursively. - public static DebugCollectLogsEntryKind Directory { get; } = new("directory"); + /// Rendered as a fullscreen overlay. + public static McpAppsSetHostContextDetailsDisplayMode Fullscreen { get; } = new("fullscreen"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(DebugCollectLogsEntryKind left, DebugCollectLogsEntryKind right) => left.Equals(right); + /// Rendered as a picture-in-picture floating panel. + public static McpAppsSetHostContextDetailsDisplayMode Pip { get; } = new("pip"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(DebugCollectLogsEntryKind left, DebugCollectLogsEntryKind right) => !(left == right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(McpAppsSetHostContextDetailsDisplayMode left, McpAppsSetHostContextDetailsDisplayMode right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(McpAppsSetHostContextDetailsDisplayMode left, McpAppsSetHostContextDetailsDisplayMode right) => !(left == right); /// - public override bool Equals(object? obj) => obj is DebugCollectLogsEntryKind other && Equals(other); + public override bool Equals(object? obj) => obj is McpAppsSetHostContextDetailsDisplayMode other && Equals(other); /// - public bool Equals(DebugCollectLogsEntryKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(McpAppsSetHostContextDetailsDisplayMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -15473,62 +19161,65 @@ public DebugCollectLogsEntryKind(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override DebugCollectLogsEntryKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override McpAppsSetHostContextDetailsDisplayMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, DebugCollectLogsEntryKind value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, McpAppsSetHostContextDetailsDisplayMode value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(DebugCollectLogsEntryKind)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpAppsSetHostContextDetailsDisplayMode)); } } } -/// How a collected debug entry should be redacted before being staged. +/// Platform type for responsive design. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct DebugCollectLogsRedaction : IEquatable +public readonly struct McpAppsSetHostContextDetailsPlatform : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public DebugCollectLogsRedaction(string value) + public McpAppsSetHostContextDetailsPlatform(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Redact the file as plain UTF-8 log text. - public static DebugCollectLogsRedaction PlainText { get; } = new("plain-text"); + /// Host runs in a web browser. + public static McpAppsSetHostContextDetailsPlatform Web { get; } = new("web"); - /// Redact each non-empty line as a session event JSON object, falling back to plain-text redaction for malformed lines. - public static DebugCollectLogsRedaction EventsJsonl { get; } = new("events-jsonl"); + /// Host runs as a desktop application. + public static McpAppsSetHostContextDetailsPlatform Desktop { get; } = new("desktop"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(DebugCollectLogsRedaction left, DebugCollectLogsRedaction right) => left.Equals(right); + /// Host runs on a mobile device. + public static McpAppsSetHostContextDetailsPlatform Mobile { get; } = new("mobile"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(DebugCollectLogsRedaction left, DebugCollectLogsRedaction right) => !(left == right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(McpAppsSetHostContextDetailsPlatform left, McpAppsSetHostContextDetailsPlatform right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(McpAppsSetHostContextDetailsPlatform left, McpAppsSetHostContextDetailsPlatform right) => !(left == right); /// - public override bool Equals(object? obj) => obj is DebugCollectLogsRedaction other && Equals(other); + public override bool Equals(object? obj) => obj is McpAppsSetHostContextDetailsPlatform other && Equals(other); /// - public bool Equals(DebugCollectLogsRedaction other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(McpAppsSetHostContextDetailsPlatform other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -15536,62 +19227,62 @@ public DebugCollectLogsRedaction(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override DebugCollectLogsRedaction Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override McpAppsSetHostContextDetailsPlatform Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, DebugCollectLogsRedaction value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, McpAppsSetHostContextDetailsPlatform value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(DebugCollectLogsRedaction)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpAppsSetHostContextDetailsPlatform)); } } } -/// Cumulative resource ceiling that stopped a factory run. +/// UI theme preference per SEP-1865. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct FactoryRunFailureKind : IEquatable +public readonly struct McpAppsSetHostContextDetailsTheme : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public FactoryRunFailureKind(string value) + public McpAppsSetHostContextDetailsTheme(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// The run admitted the approved maximum total number of subagents. - public static FactoryRunFailureKind MaxTotalSubagents { get; } = new("maxTotalSubagents"); + /// Light UI theme. + public static McpAppsSetHostContextDetailsTheme Light { get; } = new("light"); - /// The run reached the approved timeout deadline. - public static FactoryRunFailureKind Timeout { get; } = new("timeout"); + /// Dark UI theme. + public static McpAppsSetHostContextDetailsTheme Dark { get; } = new("dark"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(FactoryRunFailureKind left, FactoryRunFailureKind right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(McpAppsSetHostContextDetailsTheme left, McpAppsSetHostContextDetailsTheme right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(FactoryRunFailureKind left, FactoryRunFailureKind right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(McpAppsSetHostContextDetailsTheme left, McpAppsSetHostContextDetailsTheme right) => !(left == right); /// - public override bool Equals(object? obj) => obj is FactoryRunFailureKind other && Equals(other); + public override bool Equals(object? obj) => obj is McpAppsSetHostContextDetailsTheme other && Equals(other); /// - public bool Equals(FactoryRunFailureKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(McpAppsSetHostContextDetailsTheme other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -15599,74 +19290,65 @@ public FactoryRunFailureKind(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override FactoryRunFailureKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override McpAppsSetHostContextDetailsTheme Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, FactoryRunFailureKind value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, McpAppsSetHostContextDetailsTheme value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FactoryRunFailureKind)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpAppsSetHostContextDetailsTheme)); } } } -/// Current or terminal state of a factory run. +/// Allowed values for the `McpAppsHostContextDetailsAvailableDisplayMode` enumeration. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct FactoryRunStatus : IEquatable +public readonly struct McpAppsHostContextDetailsAvailableDisplayMode : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public FactoryRunStatus(string value) + public McpAppsHostContextDetailsAvailableDisplayMode(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// The run was minted and is awaiting approval. - public static FactoryRunStatus Pending { get; } = new("pending"); - - /// The run is executing. - public static FactoryRunStatus Running { get; } = new("running"); - - /// The run completed successfully. - public static FactoryRunStatus Completed { get; } = new("completed"); - - /// The run was interrupted while resource budget remained. - public static FactoryRunStatus Halted { get; } = new("halted"); + /// Rendered inline within the host conversation surface. + public static McpAppsHostContextDetailsAvailableDisplayMode Inline { get; } = new("inline"); - /// The run was cancelled before completion. - public static FactoryRunStatus Cancelled { get; } = new("cancelled"); + /// Rendered as a fullscreen overlay. + public static McpAppsHostContextDetailsAvailableDisplayMode Fullscreen { get; } = new("fullscreen"); - /// The factory body failed or reached a cumulative resource ceiling. - public static FactoryRunStatus Error { get; } = new("error"); + /// Rendered as a picture-in-picture floating panel. + public static McpAppsHostContextDetailsAvailableDisplayMode Pip { get; } = new("pip"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(FactoryRunStatus left, FactoryRunStatus right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(McpAppsHostContextDetailsAvailableDisplayMode left, McpAppsHostContextDetailsAvailableDisplayMode right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(FactoryRunStatus left, FactoryRunStatus right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(McpAppsHostContextDetailsAvailableDisplayMode left, McpAppsHostContextDetailsAvailableDisplayMode right) => !(left == right); /// - public override bool Equals(object? obj) => obj is FactoryRunStatus other && Equals(other); + public override bool Equals(object? obj) => obj is McpAppsHostContextDetailsAvailableDisplayMode other && Equals(other); /// - public bool Equals(FactoryRunStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(McpAppsHostContextDetailsAvailableDisplayMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -15674,62 +19356,65 @@ public FactoryRunStatus(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override FactoryRunStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override McpAppsHostContextDetailsAvailableDisplayMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, FactoryRunStatus value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, McpAppsHostContextDetailsAvailableDisplayMode value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FactoryRunStatus)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpAppsHostContextDetailsAvailableDisplayMode)); } } } -/// Kind of factory progress line. +/// Current display mode (SEP-1865). [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct FactoryLogLineKind : IEquatable +public readonly struct McpAppsHostContextDetailsDisplayMode : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public FactoryLogLineKind(string value) + public McpAppsHostContextDetailsDisplayMode(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// A narrator log line. - public static FactoryLogLineKind Log { get; } = new("log"); + /// Rendered inline within the host conversation surface. + public static McpAppsHostContextDetailsDisplayMode Inline { get; } = new("inline"); - /// A named factory phase marker. - public static FactoryLogLineKind Phase { get; } = new("phase"); + /// Rendered as a fullscreen overlay. + public static McpAppsHostContextDetailsDisplayMode Fullscreen { get; } = new("fullscreen"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(FactoryLogLineKind left, FactoryLogLineKind right) => left.Equals(right); + /// Rendered as a picture-in-picture floating panel. + public static McpAppsHostContextDetailsDisplayMode Pip { get; } = new("pip"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(FactoryLogLineKind left, FactoryLogLineKind right) => !(left == right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(McpAppsHostContextDetailsDisplayMode left, McpAppsHostContextDetailsDisplayMode right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(McpAppsHostContextDetailsDisplayMode left, McpAppsHostContextDetailsDisplayMode right) => !(left == right); /// - public override bool Equals(object? obj) => obj is FactoryLogLineKind other && Equals(other); + public override bool Equals(object? obj) => obj is McpAppsHostContextDetailsDisplayMode other && Equals(other); /// - public bool Equals(FactoryLogLineKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(McpAppsHostContextDetailsDisplayMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -15737,62 +19422,65 @@ public FactoryLogLineKind(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override FactoryLogLineKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override McpAppsHostContextDetailsDisplayMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, FactoryLogLineKind value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, McpAppsHostContextDetailsDisplayMode value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FactoryLogLineKind)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpAppsHostContextDetailsDisplayMode)); } } } -/// Allowed values for the `WorkspacesWorkspaceDetailsHostType` enumeration. +/// Platform type for responsive design. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct WorkspacesWorkspaceDetailsHostType : IEquatable +public readonly struct McpAppsHostContextDetailsPlatform : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public WorkspacesWorkspaceDetailsHostType(string value) + public McpAppsHostContextDetailsPlatform(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Workspace repository is hosted on GitHub. - public static WorkspacesWorkspaceDetailsHostType GitHub { get; } = new("github"); + /// Host runs in a web browser. + public static McpAppsHostContextDetailsPlatform Web { get; } = new("web"); - /// Workspace repository is hosted on Azure DevOps. - public static WorkspacesWorkspaceDetailsHostType Ado { get; } = new("ado"); + /// Host runs as a desktop application. + public static McpAppsHostContextDetailsPlatform Desktop { get; } = new("desktop"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(WorkspacesWorkspaceDetailsHostType left, WorkspacesWorkspaceDetailsHostType right) => left.Equals(right); + /// Host runs on a mobile device. + public static McpAppsHostContextDetailsPlatform Mobile { get; } = new("mobile"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(WorkspacesWorkspaceDetailsHostType left, WorkspacesWorkspaceDetailsHostType right) => !(left == right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(McpAppsHostContextDetailsPlatform left, McpAppsHostContextDetailsPlatform right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(McpAppsHostContextDetailsPlatform left, McpAppsHostContextDetailsPlatform right) => !(left == right); /// - public override bool Equals(object? obj) => obj is WorkspacesWorkspaceDetailsHostType other && Equals(other); + public override bool Equals(object? obj) => obj is McpAppsHostContextDetailsPlatform other && Equals(other); /// - public bool Equals(WorkspacesWorkspaceDetailsHostType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(McpAppsHostContextDetailsPlatform other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -15800,68 +19488,62 @@ public WorkspacesWorkspaceDetailsHostType(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override WorkspacesWorkspaceDetailsHostType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override McpAppsHostContextDetailsPlatform Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, WorkspacesWorkspaceDetailsHostType value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, McpAppsHostContextDetailsPlatform value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(WorkspacesWorkspaceDetailsHostType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpAppsHostContextDetailsPlatform)); } } } -/// Type of change represented by this file diff. +/// UI theme preference per SEP-1865. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct WorkspaceDiffFileChangeType : IEquatable +public readonly struct McpAppsHostContextDetailsTheme : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public WorkspaceDiffFileChangeType(string value) + public McpAppsHostContextDetailsTheme(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// The file was added. - public static WorkspaceDiffFileChangeType Added { get; } = new("added"); - - /// The file was modified. - public static WorkspaceDiffFileChangeType Modified { get; } = new("modified"); - - /// The file was deleted. - public static WorkspaceDiffFileChangeType Deleted { get; } = new("deleted"); + /// Light UI theme. + public static McpAppsHostContextDetailsTheme Light { get; } = new("light"); - /// The file was renamed. - public static WorkspaceDiffFileChangeType Renamed { get; } = new("renamed"); + /// Dark UI theme. + public static McpAppsHostContextDetailsTheme Dark { get; } = new("dark"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(WorkspaceDiffFileChangeType left, WorkspaceDiffFileChangeType right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(McpAppsHostContextDetailsTheme left, McpAppsHostContextDetailsTheme right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(WorkspaceDiffFileChangeType left, WorkspaceDiffFileChangeType right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(McpAppsHostContextDetailsTheme left, McpAppsHostContextDetailsTheme right) => !(left == right); /// - public override bool Equals(object? obj) => obj is WorkspaceDiffFileChangeType other && Equals(other); + public override bool Equals(object? obj) => obj is McpAppsHostContextDetailsTheme other && Equals(other); /// - public bool Equals(WorkspaceDiffFileChangeType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(McpAppsHostContextDetailsTheme other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -15869,65 +19551,62 @@ public WorkspaceDiffFileChangeType(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override WorkspaceDiffFileChangeType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override McpAppsHostContextDetailsTheme Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, WorkspaceDiffFileChangeType value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, McpAppsHostContextDetailsTheme value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(WorkspaceDiffFileChangeType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpAppsHostContextDetailsTheme)); } } } -/// Diff mode requested by the client. +/// Transport to be used for provider requests. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct WorkspaceDiffMode : IEquatable +public readonly struct ProviderEndpointTransport : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public WorkspaceDiffMode(string value) + public ProviderEndpointTransport(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Return staged, unstaged, and untracked working tree changes. - public static WorkspaceDiffMode Unstaged { get; } = new("unstaged"); - - /// Return changes compared with the default branch. - public static WorkspaceDiffMode Branch { get; } = new("branch"); + /// HTTP request/streaming transport. + public static ProviderEndpointTransport Http { get; } = new("http"); - /// Return the cumulative diff of files Copilot changed this session (used in non-git workspaces). - public static WorkspaceDiffMode Session { get; } = new("session"); + /// WebSocket transport. + public static ProviderEndpointTransport Websockets { get; } = new("websockets"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(WorkspaceDiffMode left, WorkspaceDiffMode right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ProviderEndpointTransport left, ProviderEndpointTransport right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(WorkspaceDiffMode left, WorkspaceDiffMode right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ProviderEndpointTransport left, ProviderEndpointTransport right) => !(left == right); /// - public override bool Equals(object? obj) => obj is WorkspaceDiffMode other && Equals(other); + public override bool Equals(object? obj) => obj is ProviderEndpointTransport other && Equals(other); /// - public bool Equals(WorkspaceDiffMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(ProviderEndpointTransport other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -15935,62 +19614,65 @@ public WorkspaceDiffMode(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override WorkspaceDiffMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override ProviderEndpointTransport Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, WorkspaceDiffMode value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, ProviderEndpointTransport value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(WorkspaceDiffMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ProviderEndpointTransport)); } } } -/// Whether task execution is synchronously awaited or managed in the background. +/// Provider family. Matches the `type` field of a BYOK provider config. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct TaskExecutionMode : IEquatable +public readonly struct ProviderEndpointType : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public TaskExecutionMode(string value) + public ProviderEndpointType(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// The task was started with synchronous waiting. - public static TaskExecutionMode Sync { get; } = new("sync"); + /// OpenAI-compatible endpoint (use the OpenAI client library). + public static ProviderEndpointType Openai { get; } = new("openai"); + + /// Azure OpenAI endpoint (use the OpenAI client library with the Azure base URL). + public static ProviderEndpointType Azure { get; } = new("azure"); - /// The task is managed in the background. - public static TaskExecutionMode Background { get; } = new("background"); + /// Anthropic endpoint (use the Anthropic client library). + public static ProviderEndpointType Anthropic { get; } = new("anthropic"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(TaskExecutionMode left, TaskExecutionMode right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ProviderEndpointType left, ProviderEndpointType right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(TaskExecutionMode left, TaskExecutionMode right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ProviderEndpointType left, ProviderEndpointType right) => !(left == right); /// - public override bool Equals(object? obj) => obj is TaskExecutionMode other && Equals(other); + public override bool Equals(object? obj) => obj is ProviderEndpointType other && Equals(other); /// - public bool Equals(TaskExecutionMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(ProviderEndpointType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -15998,71 +19680,62 @@ public TaskExecutionMode(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override TaskExecutionMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override ProviderEndpointType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, TaskExecutionMode value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, ProviderEndpointType value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskExecutionMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ProviderEndpointType)); } } } -/// Current lifecycle status of the task. +/// Wire API to be used, when required for the provider type. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct TaskStatus : IEquatable +public readonly struct ProviderEndpointWireApi : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public TaskStatus(string value) + public ProviderEndpointWireApi(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// The task is actively executing. - public static TaskStatus Running { get; } = new("running"); - - /// The task is waiting for additional input. - public static TaskStatus Idle { get; } = new("idle"); - - /// The task finished successfully. - public static TaskStatus Completed { get; } = new("completed"); - - /// The task finished with an error. - public static TaskStatus Failed { get; } = new("failed"); + /// Classic chat-completions request shape. + public static ProviderEndpointWireApi Completions { get; } = new("completions"); - /// The task was cancelled before completion. - public static TaskStatus Cancelled { get; } = new("cancelled"); + /// Newer responses request shape. + public static ProviderEndpointWireApi Responses { get; } = new("responses"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(TaskStatus left, TaskStatus right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ProviderEndpointWireApi left, ProviderEndpointWireApi right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(TaskStatus left, TaskStatus right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ProviderEndpointWireApi left, ProviderEndpointWireApi right) => !(left == right); /// - public override bool Equals(object? obj) => obj is TaskStatus other && Equals(other); + public override bool Equals(object? obj) => obj is ProviderEndpointWireApi other && Equals(other); /// - public bool Equals(TaskStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(ProviderEndpointWireApi other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -16070,62 +19743,62 @@ public TaskStatus(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override TaskStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override ProviderEndpointWireApi Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, TaskStatus value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, ProviderEndpointWireApi value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskStatus)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ProviderEndpointWireApi)); } } } -/// Whether the shell runs inside a managed PTY session or as an independent background process. +/// Provider transport. Defaults to "http". [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct TaskShellInfoAttachmentMode : IEquatable +public readonly struct ProviderConfigTransport : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public TaskShellInfoAttachmentMode(string value) + public ProviderConfigTransport(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// The shell runs in a managed PTY session. - public static TaskShellInfoAttachmentMode Attached { get; } = new("attached"); + /// HTTP request/streaming transport. + public static ProviderConfigTransport Http { get; } = new("http"); - /// The shell runs as an independent background process. - public static TaskShellInfoAttachmentMode Detached { get; } = new("detached"); + /// WebSocket transport. + public static ProviderConfigTransport Websockets { get; } = new("websockets"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(TaskShellInfoAttachmentMode left, TaskShellInfoAttachmentMode right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ProviderConfigTransport left, ProviderConfigTransport right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(TaskShellInfoAttachmentMode left, TaskShellInfoAttachmentMode right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ProviderConfigTransport left, ProviderConfigTransport right) => !(left == right); /// - public override bool Equals(object? obj) => obj is TaskShellInfoAttachmentMode other && Equals(other); + public override bool Equals(object? obj) => obj is ProviderConfigTransport other && Equals(other); /// - public bool Equals(TaskShellInfoAttachmentMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(ProviderConfigTransport other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -16133,62 +19806,65 @@ public TaskShellInfoAttachmentMode(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override TaskShellInfoAttachmentMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override ProviderConfigTransport Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, TaskShellInfoAttachmentMode value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, ProviderConfigTransport value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskShellInfoAttachmentMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ProviderConfigTransport)); } } } -/// Consumer allowed to call an MCP tool. +/// Provider type. Defaults to "openai" for generic OpenAI-compatible APIs. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct McpToolUiVisibility : IEquatable +public readonly struct ProviderConfigType : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public McpToolUiVisibility(string value) + public ProviderConfigType(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// The model may call the tool. - public static McpToolUiVisibility Model { get; } = new("model"); + /// Generic OpenAI-compatible API. + public static ProviderConfigType Openai { get; } = new("openai"); - /// An MCP App view may call the tool. - public static McpToolUiVisibility App { get; } = new("app"); + /// Azure OpenAI Service endpoint. + public static ProviderConfigType Azure { get; } = new("azure"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(McpToolUiVisibility left, McpToolUiVisibility right) => left.Equals(right); + /// Anthropic API endpoint. + public static ProviderConfigType Anthropic { get; } = new("anthropic"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(McpToolUiVisibility left, McpToolUiVisibility right) => !(left == right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ProviderConfigType left, ProviderConfigType right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ProviderConfigType left, ProviderConfigType right) => !(left == right); /// - public override bool Equals(object? obj) => obj is McpToolUiVisibility other && Equals(other); + public override bool Equals(object? obj) => obj is ProviderConfigType other && Equals(other); /// - public bool Equals(McpToolUiVisibility other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(ProviderConfigType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -16196,65 +19872,62 @@ public McpToolUiVisibility(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override McpToolUiVisibility Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override ProviderConfigType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, McpToolUiVisibility value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, ProviderConfigType value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpToolUiVisibility)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ProviderConfigType)); } } } -/// Outcome of the sampling inference. 'success' produced a response; 'failure' encountered an error (including agent-side rejection by content filter or criteria); 'cancelled' the caller cancelled this execution via cancelSamplingExecution. +/// Wire API format (openai/azure only). Defaults to "completions". [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct McpSamplingExecutionAction : IEquatable +public readonly struct ProviderConfigWireApi : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public McpSamplingExecutionAction(string value) + public ProviderConfigWireApi(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// The sampling inference completed and produced a result. - public static McpSamplingExecutionAction Success { get; } = new("success"); - - /// The sampling inference failed or was rejected. - public static McpSamplingExecutionAction Failure { get; } = new("failure"); + /// OpenAI Chat Completions wire format. + public static ProviderConfigWireApi Completions { get; } = new("completions"); - /// The sampling inference was cancelled before completion. - public static McpSamplingExecutionAction Cancelled { get; } = new("cancelled"); + /// OpenAI Responses API wire format. + public static ProviderConfigWireApi Responses { get; } = new("responses"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(McpSamplingExecutionAction left, McpSamplingExecutionAction right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ProviderConfigWireApi left, ProviderConfigWireApi right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(McpSamplingExecutionAction left, McpSamplingExecutionAction right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ProviderConfigWireApi left, ProviderConfigWireApi right) => !(left == right); /// - public override bool Equals(object? obj) => obj is McpSamplingExecutionAction other && Equals(other); + public override bool Equals(object? obj) => obj is ProviderConfigWireApi other && Equals(other); /// - public bool Equals(McpSamplingExecutionAction other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(ProviderConfigWireApi other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -16262,62 +19935,62 @@ public McpSamplingExecutionAction(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override McpSamplingExecutionAction Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override ProviderConfigWireApi Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, McpSamplingExecutionAction value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, ProviderConfigWireApi value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpSamplingExecutionAction)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ProviderConfigWireApi)); } } } -/// How environment-variable values supplied to MCP servers are resolved. "direct" passes literal string values; "indirect" treats values as references (e.g. names of environment variables on the host) that the runtime resolves before launch. Defaults to the runtime's startup mode; clients that intentionally launch MCP servers with literal values (e.g. CLI prompt mode and ACP) set this to "direct". +/// Allowed values for the `OptionsUpdateAdditionalContentExclusionPolicyScope` enumeration. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct McpSetEnvValueModeDetails : IEquatable +public readonly struct OptionsUpdateAdditionalContentExclusionPolicyScope : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public McpSetEnvValueModeDetails(string value) + public OptionsUpdateAdditionalContentExclusionPolicyScope(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Treat MCP server environment values as literal strings. - public static McpSetEnvValueModeDetails Direct { get; } = new("direct"); + /// The content exclusion policy applies to the current repository. + public static OptionsUpdateAdditionalContentExclusionPolicyScope Repo { get; } = new("repo"); - /// Treat MCP server environment values as host-side references to resolve before launch. - public static McpSetEnvValueModeDetails Indirect { get; } = new("indirect"); + /// The content exclusion policy applies across all repositories. + public static OptionsUpdateAdditionalContentExclusionPolicyScope All { get; } = new("all"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(McpSetEnvValueModeDetails left, McpSetEnvValueModeDetails right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(OptionsUpdateAdditionalContentExclusionPolicyScope left, OptionsUpdateAdditionalContentExclusionPolicyScope right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(McpSetEnvValueModeDetails left, McpSetEnvValueModeDetails right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(OptionsUpdateAdditionalContentExclusionPolicyScope left, OptionsUpdateAdditionalContentExclusionPolicyScope right) => !(left == right); /// - public override bool Equals(object? obj) => obj is McpSetEnvValueModeDetails other && Equals(other); + public override bool Equals(object? obj) => obj is OptionsUpdateAdditionalContentExclusionPolicyScope other && Equals(other); /// - public bool Equals(McpSetEnvValueModeDetails other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(OptionsUpdateAdditionalContentExclusionPolicyScope other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -16325,62 +19998,62 @@ public McpSetEnvValueModeDetails(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override McpSetEnvValueModeDetails Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override OptionsUpdateAdditionalContentExclusionPolicyScope Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, McpSetEnvValueModeDetails value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, OptionsUpdateAdditionalContentExclusionPolicyScope value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpSetEnvValueModeDetails)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(OptionsUpdateAdditionalContentExclusionPolicyScope)); } } } -/// OAuth grant type override for this login. +/// Context tier for models with tiered pricing. The session uses this to derive effective `modelCapabilitiesOverrides` so compaction, truncation, token display, and request limits honor the selected tier. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct McpOauthLoginGrantType : IEquatable +public readonly struct OptionsUpdateContextTier : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public McpOauthLoginGrantType(string value) + public OptionsUpdateContextTier(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Interactive browser-based OAuth flow using an authorization code, typically with PKCE. - public static McpOauthLoginGrantType AuthorizationCode { get; } = new("authorization_code"); + /// Use the model's default context tier and its standard token limits / pricing. + public static OptionsUpdateContextTier Default { get; } = new("default"); - /// Headless OAuth flow where a confidential client authenticates directly with a client secret. - public static McpOauthLoginGrantType ClientCredentials { get; } = new("client_credentials"); + /// Use the model's long-context tier (when available) so larger inputs are accepted and tier-specific pricing applies. + public static OptionsUpdateContextTier LongContext { get; } = new("long_context"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(McpOauthLoginGrantType left, McpOauthLoginGrantType right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(OptionsUpdateContextTier left, OptionsUpdateContextTier right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(McpOauthLoginGrantType left, McpOauthLoginGrantType right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(OptionsUpdateContextTier left, OptionsUpdateContextTier right) => !(left == right); /// - public override bool Equals(object? obj) => obj is McpOauthLoginGrantType other && Equals(other); + public override bool Equals(object? obj) => obj is OptionsUpdateContextTier other && Equals(other); /// - public bool Equals(McpOauthLoginGrantType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(OptionsUpdateContextTier other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -16388,65 +20061,62 @@ public McpOauthLoginGrantType(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override McpOauthLoginGrantType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override OptionsUpdateContextTier Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, McpOauthLoginGrantType value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, OptionsUpdateContextTier value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpOauthLoginGrantType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(OptionsUpdateContextTier)); } } } -/// Allowed values for the `McpAppsSetHostContextDetailsAvailableDisplayMode` enumeration. +/// How env values are passed to MCP servers (`direct` inlines literal values; `indirect` resolves at launch). [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct McpAppsSetHostContextDetailsAvailableDisplayMode : IEquatable +public readonly struct OptionsUpdateEnvValueMode : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public McpAppsSetHostContextDetailsAvailableDisplayMode(string value) + public OptionsUpdateEnvValueMode(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Rendered inline within the host conversation surface. - public static McpAppsSetHostContextDetailsAvailableDisplayMode Inline { get; } = new("inline"); - - /// Rendered as a fullscreen overlay. - public static McpAppsSetHostContextDetailsAvailableDisplayMode Fullscreen { get; } = new("fullscreen"); + /// Pass MCP server environment values as literal strings. + public static OptionsUpdateEnvValueMode Direct { get; } = new("direct"); - /// Rendered as a picture-in-picture floating panel. - public static McpAppsSetHostContextDetailsAvailableDisplayMode Pip { get; } = new("pip"); + /// Resolve MCP server environment values from host-side references. + public static OptionsUpdateEnvValueMode Indirect { get; } = new("indirect"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(McpAppsSetHostContextDetailsAvailableDisplayMode left, McpAppsSetHostContextDetailsAvailableDisplayMode right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(OptionsUpdateEnvValueMode left, OptionsUpdateEnvValueMode right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(McpAppsSetHostContextDetailsAvailableDisplayMode left, McpAppsSetHostContextDetailsAvailableDisplayMode right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(OptionsUpdateEnvValueMode left, OptionsUpdateEnvValueMode right) => !(left == right); /// - public override bool Equals(object? obj) => obj is McpAppsSetHostContextDetailsAvailableDisplayMode other && Equals(other); + public override bool Equals(object? obj) => obj is OptionsUpdateEnvValueMode other && Equals(other); /// - public bool Equals(McpAppsSetHostContextDetailsAvailableDisplayMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(OptionsUpdateEnvValueMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -16454,65 +20124,65 @@ public McpAppsSetHostContextDetailsAvailableDisplayMode(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override McpAppsSetHostContextDetailsAvailableDisplayMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override OptionsUpdateEnvValueMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, McpAppsSetHostContextDetailsAvailableDisplayMode value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, OptionsUpdateEnvValueMode value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpAppsSetHostContextDetailsAvailableDisplayMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(OptionsUpdateEnvValueMode)); } } } -/// Current display mode (SEP-1865). +/// Reasoning summary mode for supported model clients. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct McpAppsSetHostContextDetailsDisplayMode : IEquatable +public readonly struct OptionsUpdateReasoningSummary : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public McpAppsSetHostContextDetailsDisplayMode(string value) + public OptionsUpdateReasoningSummary(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Rendered inline within the host conversation surface. - public static McpAppsSetHostContextDetailsDisplayMode Inline { get; } = new("inline"); + /// Do not request reasoning summaries from the model. + public static OptionsUpdateReasoningSummary None { get; } = new("none"); - /// Rendered as a fullscreen overlay. - public static McpAppsSetHostContextDetailsDisplayMode Fullscreen { get; } = new("fullscreen"); + /// Request a concise summary of model reasoning. + public static OptionsUpdateReasoningSummary Concise { get; } = new("concise"); - /// Rendered as a picture-in-picture floating panel. - public static McpAppsSetHostContextDetailsDisplayMode Pip { get; } = new("pip"); + /// Request a detailed summary of model reasoning. + public static OptionsUpdateReasoningSummary Detailed { get; } = new("detailed"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(McpAppsSetHostContextDetailsDisplayMode left, McpAppsSetHostContextDetailsDisplayMode right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(OptionsUpdateReasoningSummary left, OptionsUpdateReasoningSummary right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(McpAppsSetHostContextDetailsDisplayMode left, McpAppsSetHostContextDetailsDisplayMode right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(OptionsUpdateReasoningSummary left, OptionsUpdateReasoningSummary right) => !(left == right); /// - public override bool Equals(object? obj) => obj is McpAppsSetHostContextDetailsDisplayMode other && Equals(other); + public override bool Equals(object? obj) => obj is OptionsUpdateReasoningSummary other && Equals(other); /// - public bool Equals(McpAppsSetHostContextDetailsDisplayMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(OptionsUpdateReasoningSummary other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -16520,65 +20190,89 @@ public McpAppsSetHostContextDetailsDisplayMode(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override McpAppsSetHostContextDetailsDisplayMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override OptionsUpdateReasoningSummary Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, McpAppsSetHostContextDetailsDisplayMode value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, OptionsUpdateReasoningSummary value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpAppsSetHostContextDetailsDisplayMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(OptionsUpdateReasoningSummary)); } } } -/// Platform type for responsive design. +/// Session capability enabled for this session. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct McpAppsSetHostContextDetailsPlatform : IEquatable +public readonly struct SessionCapability : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public McpAppsSetHostContextDetailsPlatform(string value) + public SessionCapability(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Host runs in a web browser. - public static McpAppsSetHostContextDetailsPlatform Web { get; } = new("web"); + /// TUI-specific prompt hints such as keyboard shortcuts. + public static SessionCapability TuiHints { get; } = new("tui-hints"); - /// Host runs as a desktop application. - public static McpAppsSetHostContextDetailsPlatform Desktop { get; } = new("desktop"); + /// Plan-mode handling and instructions. + public static SessionCapability PlanMode { get; } = new("plan-mode"); - /// Host runs on a mobile device. - public static McpAppsSetHostContextDetailsPlatform Mobile { get; } = new("mobile"); + /// Memory tool and memories prompt section. + public static SessionCapability Memory { get; } = new("memory"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(McpAppsSetHostContextDetailsPlatform left, McpAppsSetHostContextDetailsPlatform right) => left.Equals(right); + /// Copilot CLI documentation tool and prompt section. + public static SessionCapability CliDocumentation { get; } = new("cli-documentation"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(McpAppsSetHostContextDetailsPlatform left, McpAppsSetHostContextDetailsPlatform right) => !(left == right); + /// Interactive ask_user tool support. + public static SessionCapability AskUser { get; } = new("ask-user"); + + /// Interactive CLI identity and behavior. + public static SessionCapability InteractiveMode { get; } = new("interactive-mode"); + + /// Automatic hidden system notifications. + public static SessionCapability SystemNotifications { get; } = new("system-notifications"); + + /// SDK elicitation support. + public static SessionCapability Elicitation { get; } = new("elicitation"); + + /// Cross-session history tools and session-store SQL prompt/tool metadata. + public static SessionCapability SessionStore { get; } = new("session-store"); + + /// MCP Apps UI passthrough. + public static SessionCapability McpApps { get; } = new("mcp-apps"); + + /// Host-provided canvas rendering support. + public static SessionCapability CanvasRenderer { get; } = new("canvas-renderer"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SessionCapability left, SessionCapability right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SessionCapability left, SessionCapability right) => !(left == right); /// - public override bool Equals(object? obj) => obj is McpAppsSetHostContextDetailsPlatform other && Equals(other); + public override bool Equals(object? obj) => obj is SessionCapability other && Equals(other); /// - public bool Equals(McpAppsSetHostContextDetailsPlatform other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(SessionCapability other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -16586,62 +20280,62 @@ public McpAppsSetHostContextDetailsPlatform(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override McpAppsSetHostContextDetailsPlatform Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override SessionCapability Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, McpAppsSetHostContextDetailsPlatform value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, SessionCapability value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpAppsSetHostContextDetailsPlatform)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionCapability)); } } } -/// UI theme preference per SEP-1865. +/// Controls automatic non-interactive profile loading where supported. Explicit initScripts are unaffected. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct McpAppsSetHostContextDetailsTheme : IEquatable +public readonly struct ShellInitProfile : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public McpAppsSetHostContextDetailsTheme(string value) + public ShellInitProfile(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Light UI theme. - public static McpAppsSetHostContextDetailsTheme Light { get; } = new("light"); + /// Disable automatic non-interactive profile loading. Explicit initScripts still run. + public static ShellInitProfile None { get; } = new("none"); - /// Dark UI theme. - public static McpAppsSetHostContextDetailsTheme Dark { get; } = new("dark"); + /// Allow automatic non-interactive profile loading when supported. Explicit initScripts still run. + public static ShellInitProfile NonInteractive { get; } = new("non-interactive"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(McpAppsSetHostContextDetailsTheme left, McpAppsSetHostContextDetailsTheme right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ShellInitProfile left, ShellInitProfile right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(McpAppsSetHostContextDetailsTheme left, McpAppsSetHostContextDetailsTheme right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ShellInitProfile left, ShellInitProfile right) => !(left == right); /// - public override bool Equals(object? obj) => obj is McpAppsSetHostContextDetailsTheme other && Equals(other); + public override bool Equals(object? obj) => obj is ShellInitProfile other && Equals(other); /// - public bool Equals(McpAppsSetHostContextDetailsTheme other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(ShellInitProfile other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -16649,65 +20343,62 @@ public McpAppsSetHostContextDetailsTheme(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override McpAppsSetHostContextDetailsTheme Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override ShellInitProfile Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, McpAppsSetHostContextDetailsTheme value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, ShellInitProfile value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpAppsSetHostContextDetailsTheme)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ShellInitProfile)); } } } -/// Allowed values for the `McpAppsHostContextDetailsAvailableDisplayMode` enumeration. +/// Supported built-in shells for initialization scripts. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct McpAppsHostContextDetailsAvailableDisplayMode : IEquatable +public readonly struct ShellInitScriptShell : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public McpAppsHostContextDetailsAvailableDisplayMode(string value) + public ShellInitScriptShell(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Rendered inline within the host conversation surface. - public static McpAppsHostContextDetailsAvailableDisplayMode Inline { get; } = new("inline"); - - /// Rendered as a fullscreen overlay. - public static McpAppsHostContextDetailsAvailableDisplayMode Fullscreen { get; } = new("fullscreen"); + /// Source the script in the built-in Bash shell on macOS and Linux. + public static ShellInitScriptShell Bash { get; } = new("bash"); - /// Rendered as a picture-in-picture floating panel. - public static McpAppsHostContextDetailsAvailableDisplayMode Pip { get; } = new("pip"); + /// Source the script in the built-in PowerShell shell on Windows. + public static ShellInitScriptShell Powershell { get; } = new("powershell"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(McpAppsHostContextDetailsAvailableDisplayMode left, McpAppsHostContextDetailsAvailableDisplayMode right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ShellInitScriptShell left, ShellInitScriptShell right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(McpAppsHostContextDetailsAvailableDisplayMode left, McpAppsHostContextDetailsAvailableDisplayMode right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ShellInitScriptShell left, ShellInitScriptShell right) => !(left == right); /// - public override bool Equals(object? obj) => obj is McpAppsHostContextDetailsAvailableDisplayMode other && Equals(other); + public override bool Equals(object? obj) => obj is ShellInitScriptShell other && Equals(other); /// - public bool Equals(McpAppsHostContextDetailsAvailableDisplayMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(ShellInitScriptShell other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -16715,65 +20406,62 @@ public McpAppsHostContextDetailsAvailableDisplayMode(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override McpAppsHostContextDetailsAvailableDisplayMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override ShellInitScriptShell Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, McpAppsHostContextDetailsAvailableDisplayMode value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, ShellInitScriptShell value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpAppsHostContextDetailsAvailableDisplayMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ShellInitScriptShell)); } } } -/// Current display mode (SEP-1865). +/// Controls how availableTools (allowlist) and excludedTools (denylist) combine when both are set. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct McpAppsHostContextDetailsDisplayMode : IEquatable +public readonly struct OptionsUpdateToolFilterPrecedence : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public McpAppsHostContextDetailsDisplayMode(string value) + public OptionsUpdateToolFilterPrecedence(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Rendered inline within the host conversation surface. - public static McpAppsHostContextDetailsDisplayMode Inline { get; } = new("inline"); - - /// Rendered as a fullscreen overlay. - public static McpAppsHostContextDetailsDisplayMode Fullscreen { get; } = new("fullscreen"); + /// If availableTools is set, it is the only constraint that applies (excludedTools is ignored). Preserves CLI / pre-existing client behavior. Default. + public static OptionsUpdateToolFilterPrecedence Available { get; } = new("available"); - /// Rendered as a picture-in-picture floating panel. - public static McpAppsHostContextDetailsDisplayMode Pip { get; } = new("pip"); + /// A tool is enabled if and only if it matches the allowlist (or the allowlist is unset) AND it does not match the denylist. Makes 'all except X' expressible by combining the two lists. + public static OptionsUpdateToolFilterPrecedence Excluded { get; } = new("excluded"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(McpAppsHostContextDetailsDisplayMode left, McpAppsHostContextDetailsDisplayMode right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(OptionsUpdateToolFilterPrecedence left, OptionsUpdateToolFilterPrecedence right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(McpAppsHostContextDetailsDisplayMode left, McpAppsHostContextDetailsDisplayMode right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(OptionsUpdateToolFilterPrecedence left, OptionsUpdateToolFilterPrecedence right) => !(left == right); /// - public override bool Equals(object? obj) => obj is McpAppsHostContextDetailsDisplayMode other && Equals(other); + public override bool Equals(object? obj) => obj is OptionsUpdateToolFilterPrecedence other && Equals(other); /// - public bool Equals(McpAppsHostContextDetailsDisplayMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(OptionsUpdateToolFilterPrecedence other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -16781,65 +20469,68 @@ public McpAppsHostContextDetailsDisplayMode(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override McpAppsHostContextDetailsDisplayMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override OptionsUpdateToolFilterPrecedence Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, McpAppsHostContextDetailsDisplayMode value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, OptionsUpdateToolFilterPrecedence value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpAppsHostContextDetailsDisplayMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(OptionsUpdateToolFilterPrecedence)); } } } -/// Platform type for responsive design. +/// Discovery source: project (.github/extensions/), user (~/.copilot/extensions/), plugin (installed plugin), or session (session-state/<id>/extensions/). [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct McpAppsHostContextDetailsPlatform : IEquatable +public readonly struct ExtensionSource : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public McpAppsHostContextDetailsPlatform(string value) + public ExtensionSource(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Host runs in a web browser. - public static McpAppsHostContextDetailsPlatform Web { get; } = new("web"); + /// Extension discovered from the current project's .github/extensions directory. + public static ExtensionSource Project { get; } = new("project"); - /// Host runs as a desktop application. - public static McpAppsHostContextDetailsPlatform Desktop { get; } = new("desktop"); + /// Extension discovered from the user's ~/.copilot/extensions directory. + public static ExtensionSource User { get; } = new("user"); - /// Host runs on a mobile device. - public static McpAppsHostContextDetailsPlatform Mobile { get; } = new("mobile"); + /// Extension contributed by an installed plugin. + public static ExtensionSource Plugin { get; } = new("plugin"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(McpAppsHostContextDetailsPlatform left, McpAppsHostContextDetailsPlatform right) => left.Equals(right); + /// Extension discovered from the current session's state directory (loaded only for this session). + public static ExtensionSource Session { get; } = new("session"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(McpAppsHostContextDetailsPlatform left, McpAppsHostContextDetailsPlatform right) => !(left == right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ExtensionSource left, ExtensionSource right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ExtensionSource left, ExtensionSource right) => !(left == right); /// - public override bool Equals(object? obj) => obj is McpAppsHostContextDetailsPlatform other && Equals(other); + public override bool Equals(object? obj) => obj is ExtensionSource other && Equals(other); /// - public bool Equals(McpAppsHostContextDetailsPlatform other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(ExtensionSource other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -16847,62 +20538,68 @@ public McpAppsHostContextDetailsPlatform(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override McpAppsHostContextDetailsPlatform Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override ExtensionSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, McpAppsHostContextDetailsPlatform value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, ExtensionSource value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpAppsHostContextDetailsPlatform)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ExtensionSource)); } } } -/// UI theme preference per SEP-1865. +/// Current status: running, disabled, failed, or starting. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct McpAppsHostContextDetailsTheme : IEquatable +public readonly struct ExtensionStatus : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public McpAppsHostContextDetailsTheme(string value) + public ExtensionStatus(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Light UI theme. - public static McpAppsHostContextDetailsTheme Light { get; } = new("light"); + /// The extension process is running. + public static ExtensionStatus Running { get; } = new("running"); - /// Dark UI theme. - public static McpAppsHostContextDetailsTheme Dark { get; } = new("dark"); + /// The extension is installed but disabled. + public static ExtensionStatus Disabled { get; } = new("disabled"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(McpAppsHostContextDetailsTheme left, McpAppsHostContextDetailsTheme right) => left.Equals(right); + /// The extension failed to start or crashed. + public static ExtensionStatus Failed { get; } = new("failed"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(McpAppsHostContextDetailsTheme left, McpAppsHostContextDetailsTheme right) => !(left == right); + /// The extension process is starting. + public static ExtensionStatus Starting { get; } = new("starting"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ExtensionStatus left, ExtensionStatus right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ExtensionStatus left, ExtensionStatus right) => !(left == right); /// - public override bool Equals(object? obj) => obj is McpAppsHostContextDetailsTheme other && Equals(other); + public override bool Equals(object? obj) => obj is ExtensionStatus other && Equals(other); /// - public bool Equals(McpAppsHostContextDetailsTheme other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(ExtensionStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -16910,62 +20607,65 @@ public McpAppsHostContextDetailsTheme(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override McpAppsHostContextDetailsTheme Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override ExtensionStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, McpAppsHostContextDetailsTheme value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, ExtensionStatus value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpAppsHostContextDetailsTheme)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ExtensionStatus)); } } } -/// Transport to be used for provider requests. +/// Type of GitHub reference. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct ProviderEndpointTransport : IEquatable +public readonly struct PushAttachmentGitHubReferenceType : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public ProviderEndpointTransport(string value) + public PushAttachmentGitHubReferenceType(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// HTTP request/streaming transport. - public static ProviderEndpointTransport Http { get; } = new("http"); + /// GitHub issue reference. + public static PushAttachmentGitHubReferenceType Issue { get; } = new("issue"); - /// WebSocket transport. - public static ProviderEndpointTransport Websockets { get; } = new("websockets"); + /// GitHub pull request reference. + public static PushAttachmentGitHubReferenceType Pr { get; } = new("pr"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(ProviderEndpointTransport left, ProviderEndpointTransport right) => left.Equals(right); + /// GitHub discussion reference. + public static PushAttachmentGitHubReferenceType Discussion { get; } = new("discussion"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(ProviderEndpointTransport left, ProviderEndpointTransport right) => !(left == right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(PushAttachmentGitHubReferenceType left, PushAttachmentGitHubReferenceType right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(PushAttachmentGitHubReferenceType left, PushAttachmentGitHubReferenceType right) => !(left == right); /// - public override bool Equals(object? obj) => obj is ProviderEndpointTransport other && Equals(other); + public override bool Equals(object? obj) => obj is PushAttachmentGitHubReferenceType other && Equals(other); /// - public bool Equals(ProviderEndpointTransport other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(PushAttachmentGitHubReferenceType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -16973,65 +20673,65 @@ public ProviderEndpointTransport(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override ProviderEndpointTransport Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override PushAttachmentGitHubReferenceType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, ProviderEndpointTransport value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, PushAttachmentGitHubReferenceType value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ProviderEndpointTransport)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PushAttachmentGitHubReferenceType)); } } } -/// Provider family. Matches the `type` field of a BYOK provider config. +/// Context tier override for matching subagents. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct ProviderEndpointType : IEquatable +public readonly struct SubagentSettingsEntryContextTier : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public ProviderEndpointType(string value) + public SubagentSettingsEntryContextTier(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// OpenAI-compatible endpoint (use the OpenAI client library). - public static ProviderEndpointType Openai { get; } = new("openai"); + /// Inherit the parent session's effective context tier at dispatch time. + public static SubagentSettingsEntryContextTier Inherit { get; } = new("inherit"); - /// Azure OpenAI endpoint (use the OpenAI client library with the Azure base URL). - public static ProviderEndpointType Azure { get; } = new("azure"); + /// Use the model's default context window. + public static SubagentSettingsEntryContextTier Default { get; } = new("default"); - /// Anthropic endpoint (use the Anthropic client library). - public static ProviderEndpointType Anthropic { get; } = new("anthropic"); + /// Pin the subagent to the long-context tier when supported. + public static SubagentSettingsEntryContextTier LongContext { get; } = new("long_context"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(ProviderEndpointType left, ProviderEndpointType right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SubagentSettingsEntryContextTier left, SubagentSettingsEntryContextTier right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(ProviderEndpointType left, ProviderEndpointType right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SubagentSettingsEntryContextTier left, SubagentSettingsEntryContextTier right) => !(left == right); /// - public override bool Equals(object? obj) => obj is ProviderEndpointType other && Equals(other); + public override bool Equals(object? obj) => obj is SubagentSettingsEntryContextTier other && Equals(other); /// - public bool Equals(ProviderEndpointType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(SubagentSettingsEntryContextTier other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -17039,62 +20739,65 @@ public ProviderEndpointType(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override ProviderEndpointType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override SubagentSettingsEntryContextTier Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, ProviderEndpointType value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, SubagentSettingsEntryContextTier value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ProviderEndpointType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SubagentSettingsEntryContextTier)); } } } -/// Wire API to be used, when required for the provider type. +/// The user's response: accept (submitted), decline (rejected), or cancel (dismissed). [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct ProviderEndpointWireApi : IEquatable +public readonly struct UIElicitationResponseAction : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public ProviderEndpointWireApi(string value) + public UIElicitationResponseAction(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Classic chat-completions request shape. - public static ProviderEndpointWireApi Completions { get; } = new("completions"); + /// The user submitted the requested form values. + public static UIElicitationResponseAction Accept { get; } = new("accept"); - /// Newer responses request shape. - public static ProviderEndpointWireApi Responses { get; } = new("responses"); + /// The user explicitly declined to provide the requested input. + public static UIElicitationResponseAction Decline { get; } = new("decline"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(ProviderEndpointWireApi left, ProviderEndpointWireApi right) => left.Equals(right); + /// The user dismissed the elicitation request. + public static UIElicitationResponseAction Cancel { get; } = new("cancel"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(ProviderEndpointWireApi left, ProviderEndpointWireApi right) => !(left == right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(UIElicitationResponseAction left, UIElicitationResponseAction right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(UIElicitationResponseAction left, UIElicitationResponseAction right) => !(left == right); /// - public override bool Equals(object? obj) => obj is ProviderEndpointWireApi other && Equals(other); + public override bool Equals(object? obj) => obj is UIElicitationResponseAction other && Equals(other); /// - public bool Equals(ProviderEndpointWireApi other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(UIElicitationResponseAction other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -17102,62 +20805,65 @@ public ProviderEndpointWireApi(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override ProviderEndpointWireApi Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override UIElicitationResponseAction Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, ProviderEndpointWireApi value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, UIElicitationResponseAction value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ProviderEndpointWireApi)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(UIElicitationResponseAction)); } } } -/// Provider transport. Defaults to "http". +/// User's choice for auto-mode switching: yes (allow this turn), yes_always (allow + persist as setting), or no (decline). [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct ProviderConfigTransport : IEquatable +public readonly struct UIAutoModeSwitchResponse : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public ProviderConfigTransport(string value) + public UIAutoModeSwitchResponse(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// HTTP request/streaming transport. - public static ProviderConfigTransport Http { get; } = new("http"); + /// Allow the automatic mode switch for this turn. + public static UIAutoModeSwitchResponse Yes { get; } = new("yes"); - /// WebSocket transport. - public static ProviderConfigTransport Websockets { get; } = new("websockets"); + /// Allow this mode switch and persist the preference. + public static UIAutoModeSwitchResponse YesAlways { get; } = new("yes_always"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(ProviderConfigTransport left, ProviderConfigTransport right) => left.Equals(right); + /// Decline the automatic mode switch. + public static UIAutoModeSwitchResponse No { get; } = new("no"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(ProviderConfigTransport left, ProviderConfigTransport right) => !(left == right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(UIAutoModeSwitchResponse left, UIAutoModeSwitchResponse right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(UIAutoModeSwitchResponse left, UIAutoModeSwitchResponse right) => !(left == right); /// - public override bool Equals(object? obj) => obj is ProviderConfigTransport other && Equals(other); + public override bool Equals(object? obj) => obj is UIAutoModeSwitchResponse other && Equals(other); /// - public bool Equals(ProviderConfigTransport other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(UIAutoModeSwitchResponse other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -17165,65 +20871,68 @@ public ProviderConfigTransport(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override ProviderConfigTransport Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override UIAutoModeSwitchResponse Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, ProviderConfigTransport value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, UIAutoModeSwitchResponse value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ProviderConfigTransport)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(UIAutoModeSwitchResponse)); } } } -/// Provider type. Defaults to "openai" for generic OpenAI-compatible APIs. +/// User action selected for an exhausted session limit. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct ProviderConfigType : IEquatable +public readonly struct UISessionLimitsExhaustedResponseAction : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public ProviderConfigType(string value) + public UISessionLimitsExhaustedResponseAction(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Generic OpenAI-compatible API. - public static ProviderConfigType Openai { get; } = new("openai"); + /// Increase the current max by an exact AI Credits amount. + public static UISessionLimitsExhaustedResponseAction Add { get; } = new("add"); - /// Azure OpenAI Service endpoint. - public static ProviderConfigType Azure { get; } = new("azure"); + /// Set a new absolute max AI Credits value. + public static UISessionLimitsExhaustedResponseAction Set { get; } = new("set"); - /// Anthropic API endpoint. - public static ProviderConfigType Anthropic { get; } = new("anthropic"); + /// Remove the current session limit. + public static UISessionLimitsExhaustedResponseAction Unset { get; } = new("unset"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(ProviderConfigType left, ProviderConfigType right) => left.Equals(right); + /// Leave the limit unchanged and cancel the blocked model request. + public static UISessionLimitsExhaustedResponseAction Cancel { get; } = new("cancel"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(ProviderConfigType left, ProviderConfigType right) => !(left == right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(UISessionLimitsExhaustedResponseAction left, UISessionLimitsExhaustedResponseAction right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(UISessionLimitsExhaustedResponseAction left, UISessionLimitsExhaustedResponseAction right) => !(left == right); /// - public override bool Equals(object? obj) => obj is ProviderConfigType other && Equals(other); + public override bool Equals(object? obj) => obj is UISessionLimitsExhaustedResponseAction other && Equals(other); /// - public bool Equals(ProviderConfigType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(UISessionLimitsExhaustedResponseAction other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -17231,62 +20940,68 @@ public ProviderConfigType(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override ProviderConfigType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override UISessionLimitsExhaustedResponseAction Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, ProviderConfigType value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, UISessionLimitsExhaustedResponseAction value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ProviderConfigType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(UISessionLimitsExhaustedResponseAction)); } } } -/// Wire API format (openai/azure only). Defaults to "completions". +/// The action the user selected. Defaults to 'autopilot' when autoApproveEdits is true, otherwise 'interactive'. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct ProviderConfigWireApi : IEquatable +public readonly struct UIExitPlanModeAction : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public ProviderConfigWireApi(string value) + public UIExitPlanModeAction(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// OpenAI Chat Completions wire format. - public static ProviderConfigWireApi Completions { get; } = new("completions"); + /// Exit plan mode without starting implementation. + public static UIExitPlanModeAction ExitOnly { get; } = new("exit_only"); - /// OpenAI Responses API wire format. - public static ProviderConfigWireApi Responses { get; } = new("responses"); + /// Exit plan mode and continue interactively. + public static UIExitPlanModeAction Interactive { get; } = new("interactive"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(ProviderConfigWireApi left, ProviderConfigWireApi right) => left.Equals(right); + /// Exit plan mode and continue in autopilot mode. + public static UIExitPlanModeAction Autopilot { get; } = new("autopilot"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(ProviderConfigWireApi left, ProviderConfigWireApi right) => !(left == right); + /// Exit plan mode and continue in autopilot mode with parallel subagent execution. + public static UIExitPlanModeAction AutopilotFleet { get; } = new("autopilot_fleet"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(UIExitPlanModeAction left, UIExitPlanModeAction right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(UIExitPlanModeAction left, UIExitPlanModeAction right) => !(left == right); /// - public override bool Equals(object? obj) => obj is ProviderConfigWireApi other && Equals(other); + public override bool Equals(object? obj) => obj is UIExitPlanModeAction other && Equals(other); /// - public bool Equals(ProviderConfigWireApi other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(UIExitPlanModeAction other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -17294,62 +21009,62 @@ public ProviderConfigWireApi(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override ProviderConfigWireApi Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override UIExitPlanModeAction Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, ProviderConfigWireApi value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, UIExitPlanModeAction value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ProviderConfigWireApi)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(UIExitPlanModeAction)); } } } -/// Allowed values for the `OptionsUpdateAdditionalContentExclusionPolicyScope` enumeration. +/// Allowed values for the `PermissionsConfigureAdditionalContentExclusionPolicyScope` enumeration. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct OptionsUpdateAdditionalContentExclusionPolicyScope : IEquatable +public readonly struct PermissionsConfigureAdditionalContentExclusionPolicyScope : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public OptionsUpdateAdditionalContentExclusionPolicyScope(string value) + public PermissionsConfigureAdditionalContentExclusionPolicyScope(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; /// The content exclusion policy applies to the current repository. - public static OptionsUpdateAdditionalContentExclusionPolicyScope Repo { get; } = new("repo"); + public static PermissionsConfigureAdditionalContentExclusionPolicyScope Repo { get; } = new("repo"); /// The content exclusion policy applies across all repositories. - public static OptionsUpdateAdditionalContentExclusionPolicyScope All { get; } = new("all"); + public static PermissionsConfigureAdditionalContentExclusionPolicyScope All { get; } = new("all"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(OptionsUpdateAdditionalContentExclusionPolicyScope left, OptionsUpdateAdditionalContentExclusionPolicyScope right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(PermissionsConfigureAdditionalContentExclusionPolicyScope left, PermissionsConfigureAdditionalContentExclusionPolicyScope right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(OptionsUpdateAdditionalContentExclusionPolicyScope left, OptionsUpdateAdditionalContentExclusionPolicyScope right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(PermissionsConfigureAdditionalContentExclusionPolicyScope left, PermissionsConfigureAdditionalContentExclusionPolicyScope right) => !(left == right); /// - public override bool Equals(object? obj) => obj is OptionsUpdateAdditionalContentExclusionPolicyScope other && Equals(other); + public override bool Equals(object? obj) => obj is PermissionsConfigureAdditionalContentExclusionPolicyScope other && Equals(other); /// - public bool Equals(OptionsUpdateAdditionalContentExclusionPolicyScope other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(PermissionsConfigureAdditionalContentExclusionPolicyScope other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -17357,62 +21072,65 @@ public OptionsUpdateAdditionalContentExclusionPolicyScope(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override OptionsUpdateAdditionalContentExclusionPolicyScope Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override PermissionsConfigureAdditionalContentExclusionPolicyScope Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, OptionsUpdateAdditionalContentExclusionPolicyScope value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, PermissionsConfigureAdditionalContentExclusionPolicyScope value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(OptionsUpdateAdditionalContentExclusionPolicyScope)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionsConfigureAdditionalContentExclusionPolicyScope)); } } } -/// Context tier for models with tiered pricing. The session uses this to derive effective `modelCapabilitiesOverrides` so compaction, truncation, token display, and request limits honor the selected tier. +/// Disposition of a permission request as observed by the responding client. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct OptionsUpdateContextTier : IEquatable +public readonly struct PermissionDecisionOutcome : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public OptionsUpdateContextTier(string value) + public PermissionDecisionOutcome(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Use the model's default context tier and its standard token limits / pricing. - public static OptionsUpdateContextTier Default { get; } = new("default"); + /// The request was approved automatically without a new human decision. + public static PermissionDecisionOutcome AutoApproved { get; } = new("auto_approved"); - /// Use the model's long-context tier (when available) so larger inputs are accepted and tier-specific pricing applies. - public static OptionsUpdateContextTier LongContext { get; } = new("long_context"); + /// The request was denied without an interactive user decision; source records why. + public static PermissionDecisionOutcome AutopilotDenied { get; } = new("autopilot_denied"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(OptionsUpdateContextTier left, OptionsUpdateContextTier right) => left.Equals(right); + /// The response came from an interactive user prompt. + public static PermissionDecisionOutcome PromptedUser { get; } = new("prompted_user"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(OptionsUpdateContextTier left, OptionsUpdateContextTier right) => !(left == right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(PermissionDecisionOutcome left, PermissionDecisionOutcome right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(PermissionDecisionOutcome left, PermissionDecisionOutcome right) => !(left == right); /// - public override bool Equals(object? obj) => obj is OptionsUpdateContextTier other && Equals(other); + public override bool Equals(object? obj) => obj is PermissionDecisionOutcome other && Equals(other); /// - public bool Equals(OptionsUpdateContextTier other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(PermissionDecisionOutcome other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -17420,62 +21138,68 @@ public OptionsUpdateContextTier(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override OptionsUpdateContextTier Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override PermissionDecisionOutcome Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, OptionsUpdateContextTier value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, PermissionDecisionOutcome value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(OptionsUpdateContextTier)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionDecisionOutcome)); } } } -/// How env values are passed to MCP servers (`direct` inlines literal values; `indirect` resolves at launch). +/// Controlled reason or actor responsible for a permission response. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct OptionsUpdateEnvValueMode : IEquatable +public readonly struct PermissionDecisionSource : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public OptionsUpdateEnvValueMode(string value) + public PermissionDecisionSource(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Pass MCP server environment values as literal strings. - public static OptionsUpdateEnvValueMode Direct { get; } = new("direct"); + /// The response followed the auto-approval judge recommendation. + public static PermissionDecisionSource JudgeRecommendation { get; } = new("judge_recommendation"); - /// Resolve MCP server environment values from host-side references. - public static OptionsUpdateEnvValueMode Indirect { get; } = new("indirect"); + /// A human supplied the response through an interactive prompt. + public static PermissionDecisionSource HumanResponse { get; } = new("human_response"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(OptionsUpdateEnvValueMode left, OptionsUpdateEnvValueMode right) => left.Equals(right); + /// The host applied a standing policy or override rather than a judge recommendation or human decision. + public static PermissionDecisionSource HostPolicy { get; } = new("host_policy"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(OptionsUpdateEnvValueMode left, OptionsUpdateEnvValueMode right) => !(left == right); + /// The host denied the request because no interactive user response was available. + public static PermissionDecisionSource UnattendedFallback { get; } = new("unattended_fallback"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(PermissionDecisionSource left, PermissionDecisionSource right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(PermissionDecisionSource left, PermissionDecisionSource right) => !(left == right); /// - public override bool Equals(object? obj) => obj is OptionsUpdateEnvValueMode other && Equals(other); + public override bool Equals(object? obj) => obj is PermissionDecisionSource other && Equals(other); /// - public bool Equals(OptionsUpdateEnvValueMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(PermissionDecisionSource other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -17483,65 +21207,68 @@ public OptionsUpdateEnvValueMode(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override OptionsUpdateEnvValueMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override PermissionDecisionSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, OptionsUpdateEnvValueMode value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, PermissionDecisionSource value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(OptionsUpdateEnvValueMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionDecisionSource)); } } } -/// Reasoning summary mode for supported model clients. +/// Client surface that submitted a permission response. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct OptionsUpdateReasoningSummary : IEquatable +public readonly struct PermissionDecisionSurface : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public OptionsUpdateReasoningSummary(string value) + public PermissionDecisionSurface(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Do not request reasoning summaries from the model. - public static OptionsUpdateReasoningSummary None { get; } = new("none"); + /// The interactive Copilot CLI terminal UI. + public static PermissionDecisionSurface Tui { get; } = new("tui"); - /// Request a concise summary of model reasoning. - public static OptionsUpdateReasoningSummary Concise { get; } = new("concise"); + /// The non-interactive Copilot CLI prompt mode. + public static PermissionDecisionSurface PromptMode { get; } = new("prompt_mode"); - /// Request a detailed summary of model reasoning. - public static OptionsUpdateReasoningSummary Detailed { get; } = new("detailed"); + /// The Copilot App client. + public static PermissionDecisionSurface CopilotApp { get; } = new("copilot_app"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(OptionsUpdateReasoningSummary left, OptionsUpdateReasoningSummary right) => left.Equals(right); + /// A generic Copilot SDK client. + public static PermissionDecisionSurface Sdk { get; } = new("sdk"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(OptionsUpdateReasoningSummary left, OptionsUpdateReasoningSummary right) => !(left == right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(PermissionDecisionSurface left, PermissionDecisionSurface right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(PermissionDecisionSurface left, PermissionDecisionSurface right) => !(left == right); /// - public override bool Equals(object? obj) => obj is OptionsUpdateReasoningSummary other && Equals(other); + public override bool Equals(object? obj) => obj is PermissionDecisionSurface other && Equals(other); /// - public bool Equals(OptionsUpdateReasoningSummary other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(PermissionDecisionSurface other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -17549,89 +21276,68 @@ public OptionsUpdateReasoningSummary(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override OptionsUpdateReasoningSummary Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override PermissionDecisionSurface Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, OptionsUpdateReasoningSummary value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, PermissionDecisionSurface value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(OptionsUpdateReasoningSummary)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionDecisionSurface)); } } } -/// Session capability enabled for this session. +/// Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct SessionCapability : IEquatable +public readonly struct PermissionsSetApproveAllSource : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public SessionCapability(string value) + public PermissionsSetApproveAllSource(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// TUI-specific prompt hints such as keyboard shortcuts. - public static SessionCapability TuiHints { get; } = new("tui-hints"); - - /// Plan-mode handling and instructions. - public static SessionCapability PlanMode { get; } = new("plan-mode"); - - /// Memory tool and memories prompt section. - public static SessionCapability Memory { get; } = new("memory"); - - /// Copilot CLI documentation tool and prompt section. - public static SessionCapability CliDocumentation { get; } = new("cli-documentation"); - - /// Interactive ask_user tool support. - public static SessionCapability AskUser { get; } = new("ask-user"); - - /// Interactive CLI identity and behavior. - public static SessionCapability InteractiveMode { get; } = new("interactive-mode"); - - /// Automatic hidden system notifications. - public static SessionCapability SystemNotifications { get; } = new("system-notifications"); - - /// SDK elicitation support. - public static SessionCapability Elicitation { get; } = new("elicitation"); + /// Allow-all was enabled from a CLI command-line flag. + public static PermissionsSetApproveAllSource CliFlag { get; } = new("cli_flag"); - /// Cross-session history tools and session-store SQL prompt/tool metadata. - public static SessionCapability SessionStore { get; } = new("session-store"); + /// Allow-all was enabled by a slash command. + public static PermissionsSetApproveAllSource SlashCommand { get; } = new("slash_command"); - /// MCP Apps UI passthrough. - public static SessionCapability McpApps { get; } = new("mcp-apps"); + /// Allow-all was enabled by confirming autopilot behavior. + public static PermissionsSetApproveAllSource AutopilotConfirmation { get; } = new("autopilot_confirmation"); - /// Host-provided canvas rendering support. - public static SessionCapability CanvasRenderer { get; } = new("canvas-renderer"); + /// Allow-all was enabled through an RPC caller. + public static PermissionsSetApproveAllSource Rpc { get; } = new("rpc"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(SessionCapability left, SessionCapability right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(PermissionsSetApproveAllSource left, PermissionsSetApproveAllSource right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(SessionCapability left, SessionCapability right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(PermissionsSetApproveAllSource left, PermissionsSetApproveAllSource right) => !(left == right); /// - public override bool Equals(object? obj) => obj is SessionCapability other && Equals(other); + public override bool Equals(object? obj) => obj is PermissionsSetApproveAllSource other && Equals(other); /// - public bool Equals(SessionCapability other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(PermissionsSetApproveAllSource other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -17639,62 +21345,65 @@ public SessionCapability(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override SessionCapability Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override PermissionsSetApproveAllSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, SessionCapability value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, PermissionsSetApproveAllSource value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionCapability)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionsSetApproveAllSource)); } } } -/// Controls how availableTools (allowlist) and excludedTools (denylist) combine when both are set. +/// Current or requested allow-all mode. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct OptionsUpdateToolFilterPrecedence : IEquatable +public readonly struct PermissionsAllowAllMode : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public OptionsUpdateToolFilterPrecedence(string value) + public PermissionsAllowAllMode(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// If availableTools is set, it is the only constraint that applies (excludedTools is ignored). Preserves CLI / pre-existing client behavior. Default. - public static OptionsUpdateToolFilterPrecedence Available { get; } = new("available"); + /// Permission requests follow the normal approval flow. + public static PermissionsAllowAllMode Off { get; } = new("off"); - /// A tool is enabled if and only if it matches the allowlist (or the allowlist is unset) AND it does not match the denylist. Makes 'all except X' expressible by combining the two lists. - public static OptionsUpdateToolFilterPrecedence Excluded { get; } = new("excluded"); + /// Tool, path, and URL permission requests are automatically approved. + public static PermissionsAllowAllMode On { get; } = new("on"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(OptionsUpdateToolFilterPrecedence left, OptionsUpdateToolFilterPrecedence right) => left.Equals(right); + /// Permission requests follow the normal approval flow with an LLM advisory recommendation attached; clients may choose to auto-approve requests the judge evaluated as acceptable. + public static PermissionsAllowAllMode Auto { get; } = new("auto"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(OptionsUpdateToolFilterPrecedence left, OptionsUpdateToolFilterPrecedence right) => !(left == right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(PermissionsAllowAllMode left, PermissionsAllowAllMode right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(PermissionsAllowAllMode left, PermissionsAllowAllMode right) => !(left == right); /// - public override bool Equals(object? obj) => obj is OptionsUpdateToolFilterPrecedence other && Equals(other); + public override bool Equals(object? obj) => obj is PermissionsAllowAllMode other && Equals(other); /// - public bool Equals(OptionsUpdateToolFilterPrecedence other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(PermissionsAllowAllMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -17702,68 +21411,68 @@ public OptionsUpdateToolFilterPrecedence(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override OptionsUpdateToolFilterPrecedence Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override PermissionsAllowAllMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, OptionsUpdateToolFilterPrecedence value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, PermissionsAllowAllMode value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(OptionsUpdateToolFilterPrecedence)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionsAllowAllMode)); } } } -/// Discovery source: project (.github/extensions/), user (~/.copilot/extensions/), plugin (installed plugin), or session (session-state/<id>/extensions/). +/// Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct ExtensionSource : IEquatable +public readonly struct PermissionsSetAllowAllSource : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public ExtensionSource(string value) + public PermissionsSetAllowAllSource(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Extension discovered from the current project's .github/extensions directory. - public static ExtensionSource Project { get; } = new("project"); + /// Allow-all was enabled from a CLI command-line flag. + public static PermissionsSetAllowAllSource CliFlag { get; } = new("cli_flag"); - /// Extension discovered from the user's ~/.copilot/extensions directory. - public static ExtensionSource User { get; } = new("user"); + /// Allow-all was enabled by a slash command. + public static PermissionsSetAllowAllSource SlashCommand { get; } = new("slash_command"); - /// Extension contributed by an installed plugin. - public static ExtensionSource Plugin { get; } = new("plugin"); + /// Allow-all was enabled by confirming autopilot behavior. + public static PermissionsSetAllowAllSource AutopilotConfirmation { get; } = new("autopilot_confirmation"); - /// Extension discovered from the current session's state directory (loaded only for this session). - public static ExtensionSource Session { get; } = new("session"); + /// Allow-all was enabled through an RPC caller. + public static PermissionsSetAllowAllSource Rpc { get; } = new("rpc"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(ExtensionSource left, ExtensionSource right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(PermissionsSetAllowAllSource left, PermissionsSetAllowAllSource right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(ExtensionSource left, ExtensionSource right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(PermissionsSetAllowAllSource left, PermissionsSetAllowAllSource right) => !(left == right); /// - public override bool Equals(object? obj) => obj is ExtensionSource other && Equals(other); + public override bool Equals(object? obj) => obj is PermissionsSetAllowAllSource other && Equals(other); /// - public bool Equals(ExtensionSource other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(PermissionsSetAllowAllSource other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -17771,68 +21480,62 @@ public ExtensionSource(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override ExtensionSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override PermissionsSetAllowAllSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, ExtensionSource value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, PermissionsSetAllowAllSource value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ExtensionSource)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionsSetAllowAllSource)); } } } -/// Current status: running, disabled, failed, or starting. +/// Whether the change applies to ephemeral session-scoped rules (cleared at session end) or to location-scoped rules persisted via the location-permissions config file. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct ExtensionStatus : IEquatable +public readonly struct PermissionsModifyRulesScope : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public ExtensionStatus(string value) + public PermissionsModifyRulesScope(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// The extension process is running. - public static ExtensionStatus Running { get; } = new("running"); - - /// The extension is installed but disabled. - public static ExtensionStatus Disabled { get; } = new("disabled"); - - /// The extension failed to start or crashed. - public static ExtensionStatus Failed { get; } = new("failed"); + /// Apply the rule change only to this session. + public static PermissionsModifyRulesScope Session { get; } = new("session"); - /// The extension process is starting. - public static ExtensionStatus Starting { get; } = new("starting"); + /// Persist the rule change for this project location. + public static PermissionsModifyRulesScope Location { get; } = new("location"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(ExtensionStatus left, ExtensionStatus right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(PermissionsModifyRulesScope left, PermissionsModifyRulesScope right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(ExtensionStatus left, ExtensionStatus right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(PermissionsModifyRulesScope left, PermissionsModifyRulesScope right) => !(left == right); /// - public override bool Equals(object? obj) => obj is ExtensionStatus other && Equals(other); + public override bool Equals(object? obj) => obj is PermissionsModifyRulesScope other && Equals(other); /// - public bool Equals(ExtensionStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(PermissionsModifyRulesScope other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -17840,65 +21543,62 @@ public ExtensionStatus(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override ExtensionStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override PermissionsModifyRulesScope Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, ExtensionStatus value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, PermissionsModifyRulesScope value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ExtensionStatus)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionsModifyRulesScope)); } } } -/// Type of GitHub reference. +/// Whether the location is a git repo or directory. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct PushAttachmentGitHubReferenceType : IEquatable +public readonly struct PermissionLocationType : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public PushAttachmentGitHubReferenceType(string value) + public PermissionLocationType(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// GitHub issue reference. - public static PushAttachmentGitHubReferenceType Issue { get; } = new("issue"); - - /// GitHub pull request reference. - public static PushAttachmentGitHubReferenceType Pr { get; } = new("pr"); + /// The permission location is persisted at the git repository root. + public static PermissionLocationType Repo { get; } = new("repo"); - /// GitHub discussion reference. - public static PushAttachmentGitHubReferenceType Discussion { get; } = new("discussion"); + /// The permission location is persisted at the working directory. + public static PermissionLocationType Dir { get; } = new("dir"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(PushAttachmentGitHubReferenceType left, PushAttachmentGitHubReferenceType right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(PermissionLocationType left, PermissionLocationType right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(PushAttachmentGitHubReferenceType left, PushAttachmentGitHubReferenceType right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(PermissionLocationType left, PermissionLocationType right) => !(left == right); /// - public override bool Equals(object? obj) => obj is PushAttachmentGitHubReferenceType other && Equals(other); + public override bool Equals(object? obj) => obj is PermissionLocationType other && Equals(other); /// - public bool Equals(PushAttachmentGitHubReferenceType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(PermissionLocationType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -17906,65 +21606,65 @@ public PushAttachmentGitHubReferenceType(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override PushAttachmentGitHubReferenceType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override PermissionLocationType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, PushAttachmentGitHubReferenceType value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, PermissionLocationType value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PushAttachmentGitHubReferenceType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionLocationType)); } } } -/// Context tier override for matching subagents. +/// The current agent mode for this session (e.g., 'interactive', 'plan', 'autopilot'). [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct SubagentSettingsEntryContextTier : IEquatable +public readonly struct MetadataSnapshotCurrentMode : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public SubagentSettingsEntryContextTier(string value) + public MetadataSnapshotCurrentMode(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Inherit the parent session's effective context tier at dispatch time. - public static SubagentSettingsEntryContextTier Inherit { get; } = new("inherit"); + /// The agent is responding interactively to the user. + public static MetadataSnapshotCurrentMode Interactive { get; } = new("interactive"); - /// Use the model's default context window. - public static SubagentSettingsEntryContextTier Default { get; } = new("default"); + /// The agent is preparing a plan before making changes. + public static MetadataSnapshotCurrentMode Plan { get; } = new("plan"); - /// Pin the subagent to the long-context tier when supported. - public static SubagentSettingsEntryContextTier LongContext { get; } = new("long_context"); + /// The agent is working autonomously toward task completion. + public static MetadataSnapshotCurrentMode Autopilot { get; } = new("autopilot"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(SubagentSettingsEntryContextTier left, SubagentSettingsEntryContextTier right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(MetadataSnapshotCurrentMode left, MetadataSnapshotCurrentMode right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(SubagentSettingsEntryContextTier left, SubagentSettingsEntryContextTier right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(MetadataSnapshotCurrentMode left, MetadataSnapshotCurrentMode right) => !(left == right); /// - public override bool Equals(object? obj) => obj is SubagentSettingsEntryContextTier other && Equals(other); + public override bool Equals(object? obj) => obj is MetadataSnapshotCurrentMode other && Equals(other); /// - public bool Equals(SubagentSettingsEntryContextTier other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(MetadataSnapshotCurrentMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -17972,65 +21672,62 @@ public SubagentSettingsEntryContextTier(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override SubagentSettingsEntryContextTier Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override MetadataSnapshotCurrentMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, SubagentSettingsEntryContextTier value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, MetadataSnapshotCurrentMode value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SubagentSettingsEntryContextTier)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(MetadataSnapshotCurrentMode)); } } } -/// The user's response: accept (submitted), decline (rejected), or cancel (dismissed). +/// Whether the remote task originated from Copilot Coding Agent (cca) or a CLI `--remote` invocation. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct UIElicitationResponseAction : IEquatable +public readonly struct MetadataSnapshotRemoteMetadataTaskType : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public UIElicitationResponseAction(string value) + public MetadataSnapshotRemoteMetadataTaskType(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// The user submitted the requested form values. - public static UIElicitationResponseAction Accept { get; } = new("accept"); - - /// The user explicitly declined to provide the requested input. - public static UIElicitationResponseAction Decline { get; } = new("decline"); + /// Remote task originated from Copilot Coding Agent. + public static MetadataSnapshotRemoteMetadataTaskType Cca { get; } = new("cca"); - /// The user dismissed the elicitation request. - public static UIElicitationResponseAction Cancel { get; } = new("cancel"); + /// Remote task originated from a CLI remote-session invocation. + public static MetadataSnapshotRemoteMetadataTaskType Cli { get; } = new("cli"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(UIElicitationResponseAction left, UIElicitationResponseAction right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(MetadataSnapshotRemoteMetadataTaskType left, MetadataSnapshotRemoteMetadataTaskType right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(UIElicitationResponseAction left, UIElicitationResponseAction right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(MetadataSnapshotRemoteMetadataTaskType left, MetadataSnapshotRemoteMetadataTaskType right) => !(left == right); /// - public override bool Equals(object? obj) => obj is UIElicitationResponseAction other && Equals(other); + public override bool Equals(object? obj) => obj is MetadataSnapshotRemoteMetadataTaskType other && Equals(other); /// - public bool Equals(UIElicitationResponseAction other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(MetadataSnapshotRemoteMetadataTaskType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -18038,65 +21735,62 @@ public UIElicitationResponseAction(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override UIElicitationResponseAction Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override MetadataSnapshotRemoteMetadataTaskType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, UIElicitationResponseAction value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, MetadataSnapshotRemoteMetadataTaskType value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(UIElicitationResponseAction)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(MetadataSnapshotRemoteMetadataTaskType)); } } } -/// User's choice for auto-mode switching: yes (allow this turn), yes_always (allow + persist as setting), or no (decline). +/// Repository host type, if known. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct UIAutoModeSwitchResponse : IEquatable +public readonly struct WorkspaceSummaryHostType : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public UIAutoModeSwitchResponse(string value) + public WorkspaceSummaryHostType(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Allow the automatic mode switch for this turn. - public static UIAutoModeSwitchResponse Yes { get; } = new("yes"); - - /// Allow this mode switch and persist the preference. - public static UIAutoModeSwitchResponse YesAlways { get; } = new("yes_always"); + /// Workspace summary repository is hosted on GitHub. + public static WorkspaceSummaryHostType GitHub { get; } = new("github"); - /// Decline the automatic mode switch. - public static UIAutoModeSwitchResponse No { get; } = new("no"); + /// Workspace summary repository is hosted on Azure DevOps. + public static WorkspaceSummaryHostType Ado { get; } = new("ado"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(UIAutoModeSwitchResponse left, UIAutoModeSwitchResponse right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(WorkspaceSummaryHostType left, WorkspaceSummaryHostType right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(UIAutoModeSwitchResponse left, UIAutoModeSwitchResponse right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(WorkspaceSummaryHostType left, WorkspaceSummaryHostType right) => !(left == right); /// - public override bool Equals(object? obj) => obj is UIAutoModeSwitchResponse other && Equals(other); + public override bool Equals(object? obj) => obj is WorkspaceSummaryHostType other && Equals(other); /// - public bool Equals(UIAutoModeSwitchResponse other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(WorkspaceSummaryHostType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -18104,68 +21798,62 @@ public UIAutoModeSwitchResponse(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override UIAutoModeSwitchResponse Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override WorkspaceSummaryHostType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, UIAutoModeSwitchResponse value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, WorkspaceSummaryHostType value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(UIAutoModeSwitchResponse)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(WorkspaceSummaryHostType)); } } } -/// User action selected for an exhausted session limit. +/// Hosting platform type of the repository. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct UISessionLimitsExhaustedResponseAction : IEquatable +public readonly struct SessionWorkingDirectoryContextHostType : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public UISessionLimitsExhaustedResponseAction(string value) + public SessionWorkingDirectoryContextHostType(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Increase the current max by an exact AI Credits amount. - public static UISessionLimitsExhaustedResponseAction Add { get; } = new("add"); - - /// Set a new absolute max AI Credits value. - public static UISessionLimitsExhaustedResponseAction Set { get; } = new("set"); - - /// Remove the current session limit. - public static UISessionLimitsExhaustedResponseAction Unset { get; } = new("unset"); + /// The working directory repository is hosted on GitHub. + public static SessionWorkingDirectoryContextHostType GitHub { get; } = new("github"); - /// Leave the limit unchanged and cancel the blocked model request. - public static UISessionLimitsExhaustedResponseAction Cancel { get; } = new("cancel"); + /// The working directory repository is hosted on Azure DevOps. + public static SessionWorkingDirectoryContextHostType Ado { get; } = new("ado"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(UISessionLimitsExhaustedResponseAction left, UISessionLimitsExhaustedResponseAction right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SessionWorkingDirectoryContextHostType left, SessionWorkingDirectoryContextHostType right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(UISessionLimitsExhaustedResponseAction left, UISessionLimitsExhaustedResponseAction right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SessionWorkingDirectoryContextHostType left, SessionWorkingDirectoryContextHostType right) => !(left == right); /// - public override bool Equals(object? obj) => obj is UISessionLimitsExhaustedResponseAction other && Equals(other); + public override bool Equals(object? obj) => obj is SessionWorkingDirectoryContextHostType other && Equals(other); /// - public bool Equals(UISessionLimitsExhaustedResponseAction other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(SessionWorkingDirectoryContextHostType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -18173,131 +21861,113 @@ public UISessionLimitsExhaustedResponseAction(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override UISessionLimitsExhaustedResponseAction Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override SessionWorkingDirectoryContextHostType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, UISessionLimitsExhaustedResponseAction value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, SessionWorkingDirectoryContextHostType value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(UISessionLimitsExhaustedResponseAction)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionWorkingDirectoryContextHostType)); } } } -/// The action the user selected. Defaults to 'autopilot' when autoApproveEdits is true, otherwise 'interactive'. +/// Rust-owned settings predicates exposed across the SDK boundary. Raw feature-flag names are intentionally not part of the contract. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct UIExitPlanModeAction : IEquatable +public readonly struct SessionSettingsPredicateName : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public UIExitPlanModeAction(string value) + public SessionSettingsPredicateName(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Exit plan mode without starting implementation. - public static UIExitPlanModeAction ExitOnly { get; } = new("exit_only"); + /// Whether the security-tools feature flag enables security tool wiring. + public static SessionSettingsPredicateName SecurityToolsEnabled { get; } = new("securityToolsEnabled"); - /// Exit plan mode and continue interactively. - public static UIExitPlanModeAction Interactive { get; } = new("interactive"); + /// Whether third-party security tools should receive the security prompt. + public static SessionSettingsPredicateName ThirdPartySecurityPromptEnabled { get; } = new("thirdPartySecurityPromptEnabled"); - /// Exit plan mode and continue in autopilot mode. - public static UIExitPlanModeAction Autopilot { get; } = new("autopilot"); + /// Whether validation may run in parallel. + public static SessionSettingsPredicateName ParallelValidationEnabled { get; } = new("parallelValidationEnabled"); - /// Exit plan mode and continue in autopilot mode with parallel subagent execution. - public static UIExitPlanModeAction AutopilotFleet { get; } = new("autopilot_fleet"); + /// Whether runtime timing telemetry is enabled. + public static SessionSettingsPredicateName RuntimeTimingTelemetryEnabled { get; } = new("runtimeTimingTelemetryEnabled"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(UIExitPlanModeAction left, UIExitPlanModeAction right) => left.Equals(right); + /// Whether the co-author hook is enabled. + public static SessionSettingsPredicateName CoAuthorHookEnabled { get; } = new("coAuthorHookEnabled"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(UIExitPlanModeAction left, UIExitPlanModeAction right) => !(left == right); + /// Whether Chronicle integration is enabled. + public static SessionSettingsPredicateName ChronicleEnabled { get; } = new("chronicleEnabled"); - /// - public override bool Equals(object? obj) => obj is UIExitPlanModeAction other && Equals(other); + /// Whether content-exclusion policy may self-fetch data. + public static SessionSettingsPredicateName ContentExclusionSelfFetchEnabled { get; } = new("contentExclusionSelfFetchEnabled"); - /// - public bool Equals(UIExitPlanModeAction other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + /// Whether Claude Opus token-limit caps should be applied. + public static SessionSettingsPredicateName CapClaudeOpusTokenLimitsEnabled { get; } = new("capClaudeOpusTokenLimitsEnabled"); - /// - public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + /// Whether code-review behavior is enabled. + public static SessionSettingsPredicateName CodeReviewFeatureEnabled { get; } = new("codeReviewFeatureEnabled"); - /// - public override string ToString() => Value; + /// Whether CCA should use the TypeScript autofind behavior. + public static SessionSettingsPredicateName CcaUseTsAutofindEnabled { get; } = new("ccaUseTsAutofindEnabled"); - /// Provides a for serializing instances. - [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter - { - /// - public override UIExitPlanModeAction Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); - } + /// Whether the dependency checker is enabled. + public static SessionSettingsPredicateName DependencyCheckerEnabled { get; } = new("dependencyCheckerEnabled"); - /// - public override void Write(Utf8JsonWriter writer, UIExitPlanModeAction value, JsonSerializerOptions options) - { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(UIExitPlanModeAction)); - } - } -} + /// Whether the Dependabot checker is enabled. + public static SessionSettingsPredicateName DependabotCheckerEnabled { get; } = new("dependabotCheckerEnabled"); + /// Whether the CodeQL checker is enabled. + public static SessionSettingsPredicateName CodeqlCheckerEnabled { get; } = new("codeqlCheckerEnabled"); -/// Allowed values for the `PermissionsConfigureAdditionalContentExclusionPolicyScope` enumeration. -[Experimental(Diagnostics.Experimental)] -[JsonConverter(typeof(Converter))] -[DebuggerDisplay("{Value,nq}")] -public readonly struct PermissionsConfigureAdditionalContentExclusionPolicyScope : IEquatable -{ - private readonly string? _value; + /// Whether trivial-change handling is enabled. + public static SessionSettingsPredicateName TrivialChangeEnabled { get; } = new("trivialChangeEnabled"); - /// Initializes a new instance of the struct. - /// The value to associate with this . - [JsonConstructor] - public PermissionsConfigureAdditionalContentExclusionPolicyScope(string value) - { - ArgumentException.ThrowIfNullOrWhiteSpace(value); - _value = value; - } + /// Whether trivial-change skip behavior is enabled. + public static SessionSettingsPredicateName TrivialChangeSkipEnabled { get; } = new("trivialChangeSkipEnabled"); - /// Gets the value associated with this . - public string Value => _value ?? string.Empty; + /// Whether trivial-change handling is enabled for code review. + public static SessionSettingsPredicateName TrivialChangeEnabledForCodeReview { get; } = new("trivialChangeEnabledForCodeReview"); - /// The content exclusion policy applies to the current repository. - public static PermissionsConfigureAdditionalContentExclusionPolicyScope Repo { get; } = new("repo"); + /// Whether trivial-change skip behavior is enabled for code review. + public static SessionSettingsPredicateName TrivialChangeSkipEnabledForCodeReview { get; } = new("trivialChangeSkipEnabledForCodeReview"); - /// The content exclusion policy applies across all repositories. - public static PermissionsConfigureAdditionalContentExclusionPolicyScope All { get; } = new("all"); + /// Whether trivial-change handling is enabled for a specific tool. + public static SessionSettingsPredicateName TrivialChangeEnabledForTool { get; } = new("trivialChangeEnabledForTool"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(PermissionsConfigureAdditionalContentExclusionPolicyScope left, PermissionsConfigureAdditionalContentExclusionPolicyScope right) => left.Equals(right); + /// Whether trivial-change skip behavior is enabled for a specific tool. + public static SessionSettingsPredicateName TrivialChangeSkipEnabledForTool { get; } = new("trivialChangeSkipEnabledForTool"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(PermissionsConfigureAdditionalContentExclusionPolicyScope left, PermissionsConfigureAdditionalContentExclusionPolicyScope right) => !(left == right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SessionSettingsPredicateName left, SessionSettingsPredicateName right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SessionSettingsPredicateName left, SessionSettingsPredicateName right) => !(left == right); /// - public override bool Equals(object? obj) => obj is PermissionsConfigureAdditionalContentExclusionPolicyScope other && Equals(other); + public override bool Equals(object? obj) => obj is SessionSettingsPredicateName other && Equals(other); /// - public bool Equals(PermissionsConfigureAdditionalContentExclusionPolicyScope other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(SessionSettingsPredicateName other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -18305,68 +21975,65 @@ public PermissionsConfigureAdditionalContentExclusionPolicyScope(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override PermissionsConfigureAdditionalContentExclusionPolicyScope Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override SessionSettingsPredicateName Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, PermissionsConfigureAdditionalContentExclusionPolicyScope value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, SessionSettingsPredicateName value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionsConfigureAdditionalContentExclusionPolicyScope)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionSettingsPredicateName)); } } } -/// Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers. +/// Signal to send (default: SIGTERM). [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct PermissionsSetApproveAllSource : IEquatable +public readonly struct ShellKillSignal : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public PermissionsSetApproveAllSource(string value) + public ShellKillSignal(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Allow-all was enabled from a CLI command-line flag. - public static PermissionsSetApproveAllSource CliFlag { get; } = new("cli_flag"); - - /// Allow-all was enabled by a slash command. - public static PermissionsSetApproveAllSource SlashCommand { get; } = new("slash_command"); + /// Request graceful process termination. + public static ShellKillSignal SIGTERM { get; } = new("SIGTERM"); - /// Allow-all was enabled by confirming autopilot behavior. - public static PermissionsSetApproveAllSource AutopilotConfirmation { get; } = new("autopilot_confirmation"); + /// Forcefully terminate the process. + public static ShellKillSignal SIGKILL { get; } = new("SIGKILL"); - /// Allow-all was enabled through an RPC caller. - public static PermissionsSetApproveAllSource Rpc { get; } = new("rpc"); + /// Send an interrupt signal to the process. + public static ShellKillSignal SIGINT { get; } = new("SIGINT"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(PermissionsSetApproveAllSource left, PermissionsSetApproveAllSource right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ShellKillSignal left, ShellKillSignal right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(PermissionsSetApproveAllSource left, PermissionsSetApproveAllSource right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ShellKillSignal left, ShellKillSignal right) => !(left == right); /// - public override bool Equals(object? obj) => obj is PermissionsSetApproveAllSource other && Equals(other); + public override bool Equals(object? obj) => obj is ShellKillSignal other && Equals(other); /// - public bool Equals(PermissionsSetApproveAllSource other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(ShellKillSignal other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -18374,65 +22041,61 @@ public PermissionsSetApproveAllSource(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override PermissionsSetApproveAllSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override ShellKillSignal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, PermissionsSetApproveAllSource value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, ShellKillSignal value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionsSetApproveAllSource)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ShellKillSignal)); } } } -/// Current or requested allow-all mode. -[Experimental(Diagnostics.Experimental)] +/// What initiated this compaction request, recorded as the `trigger` on the persisted `session.compaction_start` / `session.compaction_complete` events. When absent, the compaction is persisted without trigger attribution (initiator unknown). [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct PermissionsAllowAllMode : IEquatable +public readonly struct SessionHistoryCompactRequestTrigger : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public PermissionsAllowAllMode(string value) + public SessionHistoryCompactRequestTrigger(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Permission requests follow the normal approval flow. - public static PermissionsAllowAllMode Off { get; } = new("off"); - - /// Tool, path, and URL permission requests are automatically approved. - public static PermissionsAllowAllMode On { get; } = new("on"); + /// User-requested compaction, e.g. the /compact command or a direct history.compact call. + public static SessionHistoryCompactRequestTrigger Manual { get; } = new("manual"); - /// Permission requests follow the normal approval flow with an LLM advisory recommendation attached; clients may choose to auto-approve requests the judge evaluated as acceptable. - public static PermissionsAllowAllMode Auto { get; } = new("auto"); + /// Compaction requested while switching to a model with a smaller context window. + public static SessionHistoryCompactRequestTrigger ModelSwitch { get; } = new("model_switch"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(PermissionsAllowAllMode left, PermissionsAllowAllMode right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SessionHistoryCompactRequestTrigger left, SessionHistoryCompactRequestTrigger right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(PermissionsAllowAllMode left, PermissionsAllowAllMode right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SessionHistoryCompactRequestTrigger left, SessionHistoryCompactRequestTrigger right) => !(left == right); /// - public override bool Equals(object? obj) => obj is PermissionsAllowAllMode other && Equals(other); + public override bool Equals(object? obj) => obj is SessionHistoryCompactRequestTrigger other && Equals(other); /// - public bool Equals(PermissionsAllowAllMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(SessionHistoryCompactRequestTrigger other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -18440,68 +22103,65 @@ public PermissionsAllowAllMode(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override PermissionsAllowAllMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override SessionHistoryCompactRequestTrigger Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, PermissionsAllowAllMode value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, SessionHistoryCompactRequestTrigger value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionsAllowAllMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionHistoryCompactRequestTrigger)); } } } -/// Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers. +/// Aggregate file change represented by a rewind preview. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct PermissionsSetAllowAllSource : IEquatable +public readonly struct HistoryRewindChangeType : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public PermissionsSetAllowAllSource(string value) + public HistoryRewindChangeType(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Allow-all was enabled from a CLI command-line flag. - public static PermissionsSetAllowAllSource CliFlag { get; } = new("cli_flag"); - - /// Allow-all was enabled by a slash command. - public static PermissionsSetAllowAllSource SlashCommand { get; } = new("slash_command"); + /// The discarded turns created the file. + public static HistoryRewindChangeType Created { get; } = new("created"); - /// Allow-all was enabled by confirming autopilot behavior. - public static PermissionsSetAllowAllSource AutopilotConfirmation { get; } = new("autopilot_confirmation"); + /// The discarded turns deleted the file. + public static HistoryRewindChangeType Deleted { get; } = new("deleted"); - /// Allow-all was enabled through an RPC caller. - public static PermissionsSetAllowAllSource Rpc { get; } = new("rpc"); + /// The discarded turns modified the file. + public static HistoryRewindChangeType Modified { get; } = new("modified"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(PermissionsSetAllowAllSource left, PermissionsSetAllowAllSource right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(HistoryRewindChangeType left, HistoryRewindChangeType right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(PermissionsSetAllowAllSource left, PermissionsSetAllowAllSource right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(HistoryRewindChangeType left, HistoryRewindChangeType right) => !(left == right); /// - public override bool Equals(object? obj) => obj is PermissionsSetAllowAllSource other && Equals(other); + public override bool Equals(object? obj) => obj is HistoryRewindChangeType other && Equals(other); /// - public bool Equals(PermissionsSetAllowAllSource other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(HistoryRewindChangeType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -18509,62 +22169,83 @@ public PermissionsSetAllowAllSource(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override PermissionsSetAllowAllSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override HistoryRewindChangeType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, PermissionsSetAllowAllSource value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, HistoryRewindChangeType value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionsSetAllowAllSource)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(HistoryRewindChangeType)); } } } -/// Whether the change applies to ephemeral session-scoped rules (cleared at session end) or to location-scoped rules persisted via the location-permissions config file. +/// Outcome of a rewind request. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct PermissionsModifyRulesScope : IEquatable +public readonly struct HistoryRewindOutcome : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public PermissionsModifyRulesScope(string value) + public HistoryRewindOutcome(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Apply the rule change only to this session. - public static PermissionsModifyRulesScope Session { get; } = new("session"); + /// The requested rewind completed; reachable in either mode. + public static HistoryRewindOutcome Success { get; } = new("success"); - /// Persist the rule change for this project location. - public static PermissionsModifyRulesScope Location { get; } = new("location"); + /// The session still has work that may mutate files or history; reachable in either mode. + public static HistoryRewindOutcome SessionBusy { get; } = new("session-busy"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(PermissionsModifyRulesScope left, PermissionsModifyRulesScope right) => left.Equals(right); + /// A conversation-and-files rewind was requested for a session that did not enable capture; conversation-only rewinds never produce this. + public static HistoryRewindOutcome FileChangeTrackingDisabled { get; } = new("file-change-tracking-disabled"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(PermissionsModifyRulesScope left, PermissionsModifyRulesScope right) => !(left == right); + /// Remote-backed rewind routing is not supported; reachable in either mode. + public static HistoryRewindOutcome UnsupportedRemoteSession { get; } = new("unsupported-remote-session"); + + /// File restore failed and all applied file changes were rolled back; only conversation-and-files rewinds produce this. + public static HistoryRewindOutcome FilesRolledBack { get; } = new("files-rolled-back"); + + /// File restore failed and its rollback could not fully restore the pre-rewind state; only conversation-and-files rewinds produce this. + public static HistoryRewindOutcome RollbackIncomplete { get; } = new("rollback-incomplete"); + + /// Conversation truncation failed. In conversation-and-files mode any files that were restored are left in place because conversation history cannot be un-truncated; in conversation-only mode no files are restored. Consult restoredFiles for what, if anything, was applied. + public static HistoryRewindOutcome TruncationFailed { get; } = new("truncation-failed"); + + /// The conversation was rewound (and, in conversation-and-files mode, captured files were restored), but persisted checkpoints could not be cleaned up; reachable in either mode. + public static HistoryRewindOutcome CheckpointCleanupFailed { get; } = new("checkpoint-cleanup-failed"); + + /// Files and conversation were rewound, but obsolete file snapshots could not be removed; only conversation-and-files rewinds produce this. + public static HistoryRewindOutcome SnapshotPruneFailed { get; } = new("snapshot-prune-failed"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(HistoryRewindOutcome left, HistoryRewindOutcome right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(HistoryRewindOutcome left, HistoryRewindOutcome right) => !(left == right); /// - public override bool Equals(object? obj) => obj is PermissionsModifyRulesScope other && Equals(other); + public override bool Equals(object? obj) => obj is HistoryRewindOutcome other && Equals(other); /// - public bool Equals(PermissionsModifyRulesScope other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(HistoryRewindOutcome other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -18572,62 +22253,62 @@ public PermissionsModifyRulesScope(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override PermissionsModifyRulesScope Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override HistoryRewindOutcome Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, PermissionsModifyRulesScope value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, HistoryRewindOutcome value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionsModifyRulesScope)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(HistoryRewindOutcome)); } } } -/// Whether the location is a git repo or directory. +/// Reason a captured file was not restored. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct PermissionLocationType : IEquatable +public readonly struct HistoryFileRestoreSkipReason : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public PermissionLocationType(string value) + public HistoryFileRestoreSkipReason(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// The permission location is persisted at the git repository root. - public static PermissionLocationType Repo { get; } = new("repo"); + /// The file changed after Copilot's last captured write. + public static HistoryFileRestoreSkipReason UserModified { get; } = new("user-modified"); - /// The permission location is persisted at the working directory. - public static PermissionLocationType Dir { get; } = new("dir"); + /// A faithful preimage was not captured. + public static HistoryFileRestoreSkipReason SkippedCapture { get; } = new("skipped-capture"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(PermissionLocationType left, PermissionLocationType right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(HistoryFileRestoreSkipReason left, HistoryFileRestoreSkipReason right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(PermissionLocationType left, PermissionLocationType right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(HistoryFileRestoreSkipReason left, HistoryFileRestoreSkipReason right) => !(left == right); /// - public override bool Equals(object? obj) => obj is PermissionLocationType other && Equals(other); + public override bool Equals(object? obj) => obj is HistoryFileRestoreSkipReason other && Equals(other); /// - public bool Equals(PermissionLocationType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(HistoryFileRestoreSkipReason other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -18635,65 +22316,62 @@ public PermissionLocationType(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override PermissionLocationType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override HistoryFileRestoreSkipReason Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, PermissionLocationType value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, HistoryFileRestoreSkipReason value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionLocationType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(HistoryFileRestoreSkipReason)); } } } -/// The current agent mode for this session (e.g., 'interactive', 'plan', 'autopilot'). +/// Scope of a rewind operation. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct MetadataSnapshotCurrentMode : IEquatable +public readonly struct HistoryRewindMode : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public MetadataSnapshotCurrentMode(string value) + public HistoryRewindMode(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// The agent is responding interactively to the user. - public static MetadataSnapshotCurrentMode Interactive { get; } = new("interactive"); - - /// The agent is preparing a plan before making changes. - public static MetadataSnapshotCurrentMode Plan { get; } = new("plan"); + /// Discard conversation events while leaving files unchanged. + public static HistoryRewindMode Conversation { get; } = new("conversation"); - /// The agent is working autonomously toward task completion. - public static MetadataSnapshotCurrentMode Autopilot { get; } = new("autopilot"); + /// Discard conversation events and restore captured files changed by those turns. + public static HistoryRewindMode ConversationAndFiles { get; } = new("conversation-and-files"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(MetadataSnapshotCurrentMode left, MetadataSnapshotCurrentMode right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(HistoryRewindMode left, HistoryRewindMode right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(MetadataSnapshotCurrentMode left, MetadataSnapshotCurrentMode right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(HistoryRewindMode left, HistoryRewindMode right) => !(left == right); /// - public override bool Equals(object? obj) => obj is MetadataSnapshotCurrentMode other && Equals(other); + public override bool Equals(object? obj) => obj is HistoryRewindMode other && Equals(other); /// - public bool Equals(MetadataSnapshotCurrentMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(HistoryRewindMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -18701,62 +22379,62 @@ public MetadataSnapshotCurrentMode(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override MetadataSnapshotCurrentMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override HistoryRewindMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, MetadataSnapshotCurrentMode value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, HistoryRewindMode value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(MetadataSnapshotCurrentMode)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(HistoryRewindMode)); } } } -/// Whether the remote task originated from Copilot Coding Agent (cca) or a CLI `--remote` invocation. +/// Whether this item is a queued user message or a queued slash command / model change. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct MetadataSnapshotRemoteMetadataTaskType : IEquatable +public readonly struct QueuePendingItemsKind : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public MetadataSnapshotRemoteMetadataTaskType(string value) + public QueuePendingItemsKind(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Remote task originated from Copilot Coding Agent. - public static MetadataSnapshotRemoteMetadataTaskType Cca { get; } = new("cca"); + /// A queued user message. + public static QueuePendingItemsKind Message { get; } = new("message"); - /// Remote task originated from a CLI remote-session invocation. - public static MetadataSnapshotRemoteMetadataTaskType Cli { get; } = new("cli"); + /// A queued slash command or model-change command. + public static QueuePendingItemsKind Command { get; } = new("command"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(MetadataSnapshotRemoteMetadataTaskType left, MetadataSnapshotRemoteMetadataTaskType right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(QueuePendingItemsKind left, QueuePendingItemsKind right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(MetadataSnapshotRemoteMetadataTaskType left, MetadataSnapshotRemoteMetadataTaskType right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(QueuePendingItemsKind left, QueuePendingItemsKind right) => !(left == right); /// - public override bool Equals(object? obj) => obj is MetadataSnapshotRemoteMetadataTaskType other && Equals(other); + public override bool Equals(object? obj) => obj is QueuePendingItemsKind other && Equals(other); /// - public bool Equals(MetadataSnapshotRemoteMetadataTaskType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(QueuePendingItemsKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -18764,62 +22442,62 @@ public MetadataSnapshotRemoteMetadataTaskType(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override MetadataSnapshotRemoteMetadataTaskType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override QueuePendingItemsKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, MetadataSnapshotRemoteMetadataTaskType value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, QueuePendingItemsKind value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(MetadataSnapshotRemoteMetadataTaskType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(QueuePendingItemsKind)); } } } -/// Repository host type, if known. +/// Cursor status: 'ok' means the cursor was applied successfully; 'expired' means the cursor referred to an event that no longer exists in history (e.g. truncated or compacted away) and the read fell back to a boundary of the remaining history (the beginning for a forward read, the tail for a backward read). The fallback page is a fresh boundary snapshot, not a continuation of the requested cursor, so it may overlap already-rendered events; on 'expired' a consumer should reset/rebase its pagination state (or deduplicate by event id) before continuing from the returned cursor. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct WorkspaceSummaryHostType : IEquatable +public readonly struct EventsCursorStatus : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public WorkspaceSummaryHostType(string value) + public EventsCursorStatus(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Workspace summary repository is hosted on GitHub. - public static WorkspaceSummaryHostType GitHub { get; } = new("github"); + /// The cursor was applied successfully. + public static EventsCursorStatus Ok { get; } = new("ok"); - /// Workspace summary repository is hosted on Azure DevOps. - public static WorkspaceSummaryHostType Ado { get; } = new("ado"); + /// The cursor referred to history that is no longer available. + public static EventsCursorStatus Expired { get; } = new("expired"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(WorkspaceSummaryHostType left, WorkspaceSummaryHostType right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(EventsCursorStatus left, EventsCursorStatus right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(WorkspaceSummaryHostType left, WorkspaceSummaryHostType right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(EventsCursorStatus left, EventsCursorStatus right) => !(left == right); /// - public override bool Equals(object? obj) => obj is WorkspaceSummaryHostType other && Equals(other); + public override bool Equals(object? obj) => obj is EventsCursorStatus other && Equals(other); /// - public bool Equals(WorkspaceSummaryHostType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(EventsCursorStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -18827,62 +22505,62 @@ public WorkspaceSummaryHostType(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override WorkspaceSummaryHostType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override EventsCursorStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, WorkspaceSummaryHostType value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, EventsCursorStatus value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(WorkspaceSummaryHostType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(EventsCursorStatus)); } } } -/// Hosting platform type of the repository. +/// Agent-scope filter: 'primary' returns only main-agent events plus events whose type starts with 'subagent.' (matching the typed-subscription default behavior); 'all' returns events from all agents (matching wildcard-subscription behavior). Default is 'all' to preserve wildcard semantics for catch-up callers. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct SessionWorkingDirectoryContextHostType : IEquatable +public readonly struct EventsAgentScope : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public SessionWorkingDirectoryContextHostType(string value) + public EventsAgentScope(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// The working directory repository is hosted on GitHub. - public static SessionWorkingDirectoryContextHostType GitHub { get; } = new("github"); + /// Return main-agent events and typed subagent lifecycle events. + public static EventsAgentScope Primary { get; } = new("primary"); - /// The working directory repository is hosted on Azure DevOps. - public static SessionWorkingDirectoryContextHostType Ado { get; } = new("ado"); + /// Return events from all agents. + public static EventsAgentScope All { get; } = new("all"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(SessionWorkingDirectoryContextHostType left, SessionWorkingDirectoryContextHostType right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(EventsAgentScope left, EventsAgentScope right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(SessionWorkingDirectoryContextHostType left, SessionWorkingDirectoryContextHostType right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(EventsAgentScope left, EventsAgentScope right) => !(left == right); /// - public override bool Equals(object? obj) => obj is SessionWorkingDirectoryContextHostType other && Equals(other); + public override bool Equals(object? obj) => obj is EventsAgentScope other && Equals(other); /// - public bool Equals(SessionWorkingDirectoryContextHostType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(EventsAgentScope other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -18890,113 +22568,62 @@ public SessionWorkingDirectoryContextHostType(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override SessionWorkingDirectoryContextHostType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override EventsAgentScope Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, SessionWorkingDirectoryContextHostType value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, EventsAgentScope value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionWorkingDirectoryContextHostType)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(EventsAgentScope)); } } } -/// Rust-owned settings predicates exposed across the SDK boundary. Raw feature-flag names are intentionally not part of the contract. +/// Direction to page through the session's persisted event history. 'forward' pages from the cursor toward newer events; 'backward' returns the newest window first (tail-first) and pages toward older events. Events within a returned batch are always chronological (oldest-to-newest), even for a backward read. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct SessionSettingsPredicateName : IEquatable +public readonly struct EventsReadDirection : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public SessionSettingsPredicateName(string value) + public EventsReadDirection(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Whether the security-tools feature flag enables security tool wiring. - public static SessionSettingsPredicateName SecurityToolsEnabled { get; } = new("securityToolsEnabled"); - - /// Whether third-party security tools should receive the security prompt. - public static SessionSettingsPredicateName ThirdPartySecurityPromptEnabled { get; } = new("thirdPartySecurityPromptEnabled"); - - /// Whether validation may run in parallel. - public static SessionSettingsPredicateName ParallelValidationEnabled { get; } = new("parallelValidationEnabled"); - - /// Whether runtime timing telemetry is enabled. - public static SessionSettingsPredicateName RuntimeTimingTelemetryEnabled { get; } = new("runtimeTimingTelemetryEnabled"); - - /// Whether the co-author hook is enabled. - public static SessionSettingsPredicateName CoAuthorHookEnabled { get; } = new("coAuthorHookEnabled"); - - /// Whether Chronicle integration is enabled. - public static SessionSettingsPredicateName ChronicleEnabled { get; } = new("chronicleEnabled"); - - /// Whether content-exclusion policy may self-fetch data. - public static SessionSettingsPredicateName ContentExclusionSelfFetchEnabled { get; } = new("contentExclusionSelfFetchEnabled"); - - /// Whether Claude Opus token-limit caps should be applied. - public static SessionSettingsPredicateName CapClaudeOpusTokenLimitsEnabled { get; } = new("capClaudeOpusTokenLimitsEnabled"); - - /// Whether code-review behavior is enabled. - public static SessionSettingsPredicateName CodeReviewFeatureEnabled { get; } = new("codeReviewFeatureEnabled"); - - /// Whether CCA should use the TypeScript autofind behavior. - public static SessionSettingsPredicateName CcaUseTsAutofindEnabled { get; } = new("ccaUseTsAutofindEnabled"); - - /// Whether the dependency checker is enabled. - public static SessionSettingsPredicateName DependencyCheckerEnabled { get; } = new("dependencyCheckerEnabled"); - - /// Whether the Dependabot checker is enabled. - public static SessionSettingsPredicateName DependabotCheckerEnabled { get; } = new("dependabotCheckerEnabled"); - - /// Whether the CodeQL checker is enabled. - public static SessionSettingsPredicateName CodeqlCheckerEnabled { get; } = new("codeqlCheckerEnabled"); - - /// Whether trivial-change handling is enabled. - public static SessionSettingsPredicateName TrivialChangeEnabled { get; } = new("trivialChangeEnabled"); - - /// Whether trivial-change skip behavior is enabled. - public static SessionSettingsPredicateName TrivialChangeSkipEnabled { get; } = new("trivialChangeSkipEnabled"); + /// Page from the cursor toward newer events (default). + public static EventsReadDirection Forward { get; } = new("forward"); - /// Whether trivial-change handling is enabled for code review. - public static SessionSettingsPredicateName TrivialChangeEnabledForCodeReview { get; } = new("trivialChangeEnabledForCodeReview"); - - /// Whether trivial-change skip behavior is enabled for code review. - public static SessionSettingsPredicateName TrivialChangeSkipEnabledForCodeReview { get; } = new("trivialChangeSkipEnabledForCodeReview"); - - /// Whether trivial-change handling is enabled for a specific tool. - public static SessionSettingsPredicateName TrivialChangeEnabledForTool { get; } = new("trivialChangeEnabledForTool"); + /// Tail-first: return the newest events and page toward older events. + public static EventsReadDirection Backward { get; } = new("backward"); - /// Whether trivial-change skip behavior is enabled for a specific tool. - public static SessionSettingsPredicateName TrivialChangeSkipEnabledForTool { get; } = new("trivialChangeSkipEnabledForTool"); - - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(SessionSettingsPredicateName left, SessionSettingsPredicateName right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(EventsReadDirection left, EventsReadDirection right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(SessionSettingsPredicateName left, SessionSettingsPredicateName right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(EventsReadDirection left, EventsReadDirection right) => !(left == right); /// - public override bool Equals(object? obj) => obj is SessionSettingsPredicateName other && Equals(other); + public override bool Equals(object? obj) => obj is EventsReadDirection other && Equals(other); /// - public bool Equals(SessionSettingsPredicateName other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(EventsReadDirection other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -19004,65 +22631,62 @@ public SessionSettingsPredicateName(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override SessionSettingsPredicateName Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override EventsReadDirection Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, SessionSettingsPredicateName value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, EventsReadDirection value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionSettingsPredicateName)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(EventsReadDirection)); } } } -/// Signal to send (default: SIGTERM). +/// Client population used for the prediction baseline. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct ShellKillSignal : IEquatable +public readonly struct SessionLimitPredictionClientType : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public ShellKillSignal(string value) + public SessionLimitPredictionClientType(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Request graceful process termination. - public static ShellKillSignal SIGTERM { get; } = new("SIGTERM"); - - /// Forcefully terminate the process. - public static ShellKillSignal SIGKILL { get; } = new("SIGKILL"); + /// Interactive CLI sessions where a user can accept, edit, or top up the limit. + public static SessionLimitPredictionClientType CliInteractive { get; } = new("cli-interactive"); - /// Send an interrupt signal to the process. - public static ShellKillSignal SIGINT { get; } = new("SIGINT"); + /// Prompt/non-interactive CLI sessions where the initial limit must cover more of the run. + public static SessionLimitPredictionClientType CliPrompt { get; } = new("cli-prompt"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(ShellKillSignal left, ShellKillSignal right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SessionLimitPredictionClientType left, SessionLimitPredictionClientType right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(ShellKillSignal left, ShellKillSignal right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SessionLimitPredictionClientType left, SessionLimitPredictionClientType right) => !(left == right); /// - public override bool Equals(object? obj) => obj is ShellKillSignal other && Equals(other); + public override bool Equals(object? obj) => obj is SessionLimitPredictionClientType other && Equals(other); /// - public bool Equals(ShellKillSignal other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(SessionLimitPredictionClientType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -19070,62 +22694,68 @@ public ShellKillSignal(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override ShellKillSignal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override SessionLimitPredictionClientType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, ShellKillSignal value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, SessionLimitPredictionClientType value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ShellKillSignal)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionLimitPredictionClientType)); } } } -/// Whether this item is a queued user message or a queued slash command / model change. +/// Semantic usage tier used for a recommended cap or additional headroom. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct QueuePendingItemsKind : IEquatable +public readonly struct SessionLimitPredictionTier : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public QueuePendingItemsKind(string value) + public SessionLimitPredictionTier(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// A queued user message. - public static QueuePendingItemsKind Message { get; } = new("message"); + /// Recommended starting tier. + public static SessionLimitPredictionTier Recommended { get; } = new("recommended"); - /// A queued slash command or model-change command. - public static QueuePendingItemsKind Command { get; } = new("command"); + /// Additional headroom for longer-running sessions. + public static SessionLimitPredictionTier AdditionalHeadroom { get; } = new("additional_headroom"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(QueuePendingItemsKind left, QueuePendingItemsKind right) => left.Equals(right); + /// Generous headroom for unusually high usage. + public static SessionLimitPredictionTier GenerousHeadroom { get; } = new("generous_headroom"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(QueuePendingItemsKind left, QueuePendingItemsKind right) => !(left == right); + /// Maximum available headroom tier. + public static SessionLimitPredictionTier MaximumHeadroom { get; } = new("maximum_headroom"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SessionLimitPredictionTier left, SessionLimitPredictionTier right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SessionLimitPredictionTier left, SessionLimitPredictionTier right) => !(left == right); /// - public override bool Equals(object? obj) => obj is QueuePendingItemsKind other && Equals(other); + public override bool Equals(object? obj) => obj is SessionLimitPredictionTier other && Equals(other); /// - public bool Equals(QueuePendingItemsKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(SessionLimitPredictionTier other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -19133,62 +22763,65 @@ public QueuePendingItemsKind(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override QueuePendingItemsKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override SessionLimitPredictionTier Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, QueuePendingItemsKind value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, SessionLimitPredictionTier value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(QueuePendingItemsKind)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionLimitPredictionTier)); } } } -/// Cursor status: 'ok' means the cursor was applied successfully; 'expired' means the cursor referred to an event that no longer exists in history (e.g. truncated or compacted away) and the read started from the beginning of the remaining history. +/// Baseline fallback level used to create the prediction. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct EventsCursorStatus : IEquatable +public readonly struct SessionLimitPredictionSource : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public EventsCursorStatus(string value) + public SessionLimitPredictionSource(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// The cursor was applied successfully. - public static EventsCursorStatus Ok { get; } = new("ok"); + /// The prediction used the exact resolved model's baseline cell. + public static SessionLimitPredictionSource Model { get; } = new("model"); - /// The cursor referred to history that is no longer available. - public static EventsCursorStatus Expired { get; } = new("expired"); + /// The exact model was unavailable, so the prediction used the model family's baseline cell. + public static SessionLimitPredictionSource Family { get; } = new("family"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(EventsCursorStatus left, EventsCursorStatus right) => left.Equals(right); + /// No model or family cell was available, so the prediction used the global client-type baseline cell. + public static SessionLimitPredictionSource Global { get; } = new("global"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(EventsCursorStatus left, EventsCursorStatus right) => !(left == right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SessionLimitPredictionSource left, SessionLimitPredictionSource right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SessionLimitPredictionSource left, SessionLimitPredictionSource right) => !(left == right); /// - public override bool Equals(object? obj) => obj is EventsCursorStatus other && Equals(other); + public override bool Equals(object? obj) => obj is SessionLimitPredictionSource other && Equals(other); /// - public bool Equals(EventsCursorStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(SessionLimitPredictionSource other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -19196,62 +22829,62 @@ public EventsCursorStatus(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override EventsCursorStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override SessionLimitPredictionSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, EventsCursorStatus value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, SessionLimitPredictionSource value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(EventsCursorStatus)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionLimitPredictionSource)); } } } -/// Agent-scope filter: 'primary' returns only main-agent events plus events whose type starts with 'subagent.' (matching the typed-subscription default behavior); 'all' returns events from all agents (matching wildcard-subscription behavior). Default is 'all' to preserve wildcard semantics for catch-up callers. +/// Reason a prediction could not be computed. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct EventsAgentScope : IEquatable +public readonly struct SessionLimitPredictionUnavailableReason : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public EventsAgentScope(string value) + public SessionLimitPredictionUnavailableReason(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Return main-agent events and typed subagent lifecycle events. - public static EventsAgentScope Primary { get; } = new("primary"); + /// The current model is auto and has not resolved to a concrete model yet. + public static SessionLimitPredictionUnavailableReason AutoUnresolved { get; } = new("auto_unresolved"); - /// Return events from all agents. - public static EventsAgentScope All { get; } = new("all"); + /// No model was provided and the session does not currently have a selected model. + public static SessionLimitPredictionUnavailableReason NoModel { get; } = new("no_model"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(EventsAgentScope left, EventsAgentScope right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SessionLimitPredictionUnavailableReason left, SessionLimitPredictionUnavailableReason right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(EventsAgentScope left, EventsAgentScope right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SessionLimitPredictionUnavailableReason left, SessionLimitPredictionUnavailableReason right) => !(left == right); /// - public override bool Equals(object? obj) => obj is EventsAgentScope other && Equals(other); + public override bool Equals(object? obj) => obj is SessionLimitPredictionUnavailableReason other && Equals(other); /// - public bool Equals(EventsAgentScope other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(SessionLimitPredictionUnavailableReason other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -19259,20 +22892,20 @@ public EventsAgentScope(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override EventsAgentScope Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override SessionLimitPredictionUnavailableReason Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, EventsAgentScope value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, SessionLimitPredictionUnavailableReason value, JsonSerializerOptions options) { - GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(EventsAgentScope)); + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionLimitPredictionUnavailableReason)); } } } @@ -19599,6 +23232,72 @@ public override void Write(Utf8JsonWriter writer, SessionFsSqliteQueryType value } +/// SQLite transaction failure classification. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct SessionFsSqliteTransactionErrorClass : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public SessionFsSqliteTransactionErrorClass(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// SQLite reported BUSY or LOCKED before commit; the transaction was rolled back and may be retried. + public static SessionFsSqliteTransactionErrorClass BusyOrLocked { get; } = new("busyOrLocked"); + + /// The statement, database, or provider failed definitively and must not be retried automatically. + public static SessionFsSqliteTransactionErrorClass Fatal { get; } = new("fatal"); + + /// The transport failed after the provider may have committed; retrying could duplicate effects. + public static SessionFsSqliteTransactionErrorClass PostCommitAmbiguous { get; } = new("postCommitAmbiguous"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SessionFsSqliteTransactionErrorClass left, SessionFsSqliteTransactionErrorClass right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SessionFsSqliteTransactionErrorClass left, SessionFsSqliteTransactionErrorClass right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is SessionFsSqliteTransactionErrorClass other && Equals(other); + + /// + public bool Equals(SessionFsSqliteTransactionErrorClass other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override SessionFsSqliteTransactionErrorClass Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, SessionFsSqliteTransactionErrorClass value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionFsSqliteTransactionErrorClass)); + } + } +} + + /// Transport the runtime would otherwise use for this request. `http` (the default when absent) covers plain HTTP and SSE responses; `websocket` indicates a full-duplex message channel where each body chunk maps to one WebSocket message and the `binary` flag distinguishes text from binary frames. The SDK consumer uses this to decide whether to service the request with an HTTP client or a WebSocket client. It is the one piece of request metadata the consumer cannot reliably infer from the URL or headers alone. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] @@ -19685,7 +23384,7 @@ public async Task PingAsync(string? message = null, CancellationToke /// Performs the SDK server connection handshake and validates the optional connection token. Marked internal because this is JSON-RPC transport plumbing invoked automatically by an SDK client's own `connect()` wrapper, not a user-facing method. Stays internal as long as the SDK client owns the handshake; would only become public if the SDK ever exposed the raw schema surface to consumers without a connection wrapper. /// Connection token; required when the server was started with COPILOT_CONNECTION_TOKEN. - /// Opt this connection in to GitHub telemetry forwarding for its lifetime. When set, the runtime forwards every internal telemetry event it emits — across all sessions, plus sessionless events — to this connection over the `gitHubTelemetry.event` notification, in addition to the runtime's normal GitHub/CTS emission (dual-write). Intended for first-party hosts that re-emit the events into their own telemetry stores. Both unrestricted and restricted events are forwarded, each tagged with a `restricted` discriminator; a backstop drops restricted events when restricted telemetry is disabled. + /// Opt this connection in to GitHub telemetry forwarding for its lifetime. When set, the runtime forwards every internal telemetry event it emits — across all sessions, plus sessionless events — to this connection over the `gitHubTelemetry.event` notification. Regular events are also written to the runtime's normal GitHub/CTS path (dual-write); host-only compatibility events are forward-only and intentionally skip that path. Intended for first-party hosts that re-emit the events into their own telemetry stores. Both unrestricted and restricted events are forwarded, each tagged with a `restricted` discriminator; a backstop drops restricted events when restricted telemetry is disabled — using the process-global gate for ordinary events and an explicit session-scoped decision for host-only events. /// The to monitor for cancellation requests. The default is . /// Handshake result reporting the server's protocol version and package version on success. [Experimental(Diagnostics.Experimental)] @@ -19695,6 +23394,14 @@ internal async Task ConnectAsync(string? token = null, bool? enab return await CopilotClient.InvokeRpcAsync(_rpc, "connect", [request], cancellationToken); } + /// Registers the calling SDK client as the per-entrypoint extension launch provider. Call before creating any sessions. When omitted, the runtime temporarily falls back to its built-in Node launcher for backward compatibility. + /// The to monitor for cancellation requests. The default is . + [Experimental(Diagnostics.Experimental)] + public async Task RegisterExtensionLaunchProviderAsync(CancellationToken cancellationToken = default) + { + await CopilotClient.InvokeRpcAsync(_rpc, "registerExtensionLaunchProvider", [], cancellationToken); + } + /// Models APIs. public ServerModelsApi Models => field ?? @@ -19725,6 +23432,12 @@ internal async Task ConnectAsync(string? token = null, bool? enab Interlocked.CompareExchange(ref field, new(_rpc), null) ?? field; + /// Extensions APIs. + public ServerExtensionsApi Extensions => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + /// Plugins APIs. public ServerPluginsApi Plugins => field ?? @@ -19761,6 +23474,12 @@ internal async Task ConnectAsync(string? token = null, bool? enab Interlocked.CompareExchange(ref field, new(_rpc), null) ?? field; + /// ManagedSettings APIs. + public ServerManagedSettingsApi ManagedSettings => + field ?? + Interlocked.CompareExchange(ref field, new(_rpc), null) ?? + field; + /// Runtime APIs. public ServerRuntimeApi Runtime => field ?? @@ -19812,6 +23531,14 @@ public async Task ListAsync(string? gitHubToken = null, CancellationT var request = new ModelsListRequest { GitHubToken = gitHubToken }; return await CopilotClient.InvokeRpcAsync(_rpc, "models.list", [request], cancellationToken); } + + /// Returns the running runtime's complete catalog of well-known built-in model IDs without authentication or network access. + /// The to monitor for cancellation requests. The default is . + /// The running runtime's complete catalog of well-known built-in model IDs, including supported models and additional IDs with built-in metadata. + public async Task GetBuiltInCatalogAsync(CancellationToken cancellationToken = default) + { + return await CopilotClient.InvokeRpcAsync(_rpc, "models.getBuiltInCatalog", [], cancellationToken); + } } /// Provides server-scoped Tools APIs. @@ -20040,6 +23767,48 @@ public async Task ReloadAsync(CancellationToken cancellationToken = default) } } +/// Provides server-scoped Extensions APIs. +[Experimental(Diagnostics.Experimental)] +public sealed class ServerExtensionsApi +{ + private readonly JsonRpc _rpc; + + internal ServerExtensionsApi(JsonRpc rpc) + { + _rpc = rpc; + } + + /// Discovers user and enabled installed-plugin extensions from persisted Copilot home state, including enablement preferences. Launch-scoped additional plugins are not included. + /// The to monitor for cancellation requests. The default is . + /// Extensions discovered from persisted Copilot home state and their effective loading mode. Launch-scoped additional plugins are not included. + public async Task DiscoverAsync(CancellationToken cancellationToken = default) + { + return await CopilotClient.InvokeRpcAsync(_rpc, "extensions.discover", [], cancellationToken); + } + + /// Persistently enables extension IDs for future sessions. Active sessions are unchanged; use session.extensions.enable to update them. + /// Source-qualified user or plugin extension IDs to enable. + /// The to monitor for cancellation requests. The default is . + public async Task EnableAsync(IList ids, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(ids); + + var request = new DiscoveredExtensionsEnableRequest { Ids = ids }; + await CopilotClient.InvokeRpcAsync(_rpc, "extensions.enable", [request], cancellationToken); + } + + /// Persistently disables extension IDs for future sessions. Active sessions are unchanged; use session.extensions.disable to update them. + /// Source-qualified user or plugin extension IDs to disable. + /// The to monitor for cancellation requests. The default is . + public async Task DisableAsync(IList ids, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(ids); + + var request = new DiscoveredExtensionsDisableRequest { Ids = ids }; + await CopilotClient.InvokeRpcAsync(_rpc, "extensions.disable", [request], cancellationToken); + } +} + /// Provides server-scoped Plugins APIs. [Experimental(Diagnostics.Experimental)] public sealed class ServerPluginsApi @@ -20410,6 +24179,26 @@ public async Task SetAsync(object settings, CancellationT } } +/// Provides server-scoped ManagedSettings APIs. +[Experimental(Diagnostics.Experimental)] +public sealed class ServerManagedSettingsApi +{ + private readonly JsonRpc _rpc; + + internal ServerManagedSettingsApi(JsonRpc rpc) + { + _rpc = rpc; + } + + /// Discovers device-managed settings from production MDM and managed-file sources, validates them against the runtime-owned managed-settings schema, and returns the canonical JSON without requiring a session. + /// The to monitor for cancellation requests. The default is . + /// Validated device-managed settings discovered before a session exists. + public async Task ReadAsync(CancellationToken cancellationToken = default) + { + return await CopilotClient.InvokeRpcAsync(_rpc, "managedSettings.read", [], cancellationToken); + } +} + /// Provides server-scoped Runtime APIs. [Experimental(Diagnostics.Experimental)] public sealed class ServerRuntimeApi @@ -20569,6 +24358,28 @@ public async Task ListAsync(SessionSource? source = null, long? met return await CopilotClient.InvokeRpcAsync(_rpc, "sessions.list", [request], cancellationToken); } + /// Reads lightweight persisted metadata for one local session without opening it. + /// Session ID to inspect. + /// The to monitor for cancellation requests. The default is . + /// Persisted local session metadata when the session exists. + internal async Task GetMetadataAsync(string sessionId, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(sessionId); + + var request = new SessionsGetMetadataRequest { SessionId = sessionId }; + return await CopilotClient.InvokeRpcAsync(_rpc, "sessions.getMetadata", [request], cancellationToken); + } + + /// Lists recent local session IDs that contain user-visible history, omitting housekeeping-only sessions. + /// Maximum number of session IDs to return. + /// The to monitor for cancellation requests. The default is . + /// Recent local session IDs that contain user-visible history. + internal async Task ListNonEmptySessionIdsAsync(long? limit = null, CancellationToken cancellationToken = default) + { + var request = new SessionsListNonEmptySessionIdsRequest { Limit = limit }; + return await CopilotClient.InvokeRpcAsync(_rpc, "sessions.listNonEmptySessionIds", [request], cancellationToken); + } + /// Finds the local session bound to a GitHub task ID, if any. /// GitHub task ID to look up. /// The to monitor for cancellation requests. The default is . @@ -20671,6 +24482,18 @@ public async Task BulkDeleteAsync(IList session return await CopilotClient.InvokeRpcAsync(_rpc, "sessions.bulkDelete", [request], cancellationToken); } + /// Deletes one local session from disk after running the same lifecycle hooks as the session manager. + /// Session ID to delete. + /// Internal resolved session directory path to delete. + /// The to monitor for cancellation requests. The default is . + internal async Task DeleteAsync(string sessionId, string? sessionPath = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(sessionId); + + var request = new SessionsDeleteRequest { SessionId = sessionId, SessionPath = sessionPath }; + await CopilotClient.InvokeRpcAsync(_rpc, "sessions.delete", [request], cancellationToken); + } + /// Deletes sessions older than the given threshold, with optional dry-run and exclusion list. /// Delete sessions whose modifiedTime is at least this many days old. /// When true, only report what would be deleted without performing any deletion. @@ -20827,28 +24650,25 @@ public async Task GetRemoteControlStatusAsync(Cancell /// Registers extension-provided tools on the given session, gated by an optional `enabled` callback. Returns an opaque unsubscribe function the caller must invoke to deregister the tools when the extension is torn down. Marked internal because `loader`, `enabled`, and the returned `unsubscribe` are in-process handles that cannot cross the JSON-RPC boundary. Disappears once extension discovery / launch / tool registration are owned by the runtime: SDK consumers will pass pure config (search paths, disabled ids) via `SessionOptions` and the runtime will resolve, launch, register, and tear down extensions itself. /// Session to register extension tools on. - /// In-process ExtensionLoader handle (CLI-only optimization). Marked internal: this field is excluded from the public SDK surface. When the CLI migrates to a process-separated SDK, extension discovery/launch moves entirely into the runtime — the CLI passes pure config (search paths, disabled ids) via SessionOptions instead. /// Optional registration options. /// The to monitor for cancellation requests. The default is . /// Handle for releasing the extension tool registration. - internal async Task RegisterExtensionToolsOnSessionAsync(string sessionId, object loader, SessionsRegisterExtensionToolsOnSessionOptions? options = null, CancellationToken cancellationToken = default) + internal async Task RegisterExtensionToolsOnSessionAsync(string sessionId, SessionsRegisterExtensionToolsOnSessionOptions? options = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(sessionId); - ArgumentNullException.ThrowIfNull(loader); - var request = new RegisterExtensionToolsParams { SessionId = sessionId, Loader = CopilotClient.ToJsonElementForWire(loader)!.Value, Options = options }; + var request = new RegisterExtensionToolsParams { SessionId = sessionId, Options = options }; return await CopilotClient.InvokeRpcAsync(_rpc, "sessions.registerExtensionToolsOnSession", [request], cancellationToken); } /// Attaches (or detaches) an in-process ExtensionController delegate for the given session, used by shared-API surfaces that need to query or modify the session's extension state. Pass `controller: undefined` to detach. Marked internal because the controller is an in-process object that cannot cross the JSON-RPC boundary. Disappears alongside `registerExtensionToolsOnSession`: once the runtime owns extension management, the public surface exposes list/enable/disable/reload as dedicated RPCs served by the runtime. /// Session to attach the extension controller delegate to. - /// In-process ExtensionController delegate (CLI-only optimization). Marked internal: this field is excluded from the public SDK surface. The post-SDK extension surface exposes list/enable/disable/reload via dedicated RPCs served by the runtime. /// The to monitor for cancellation requests. The default is . - internal async Task ConfigureSessionExtensionsAsync(string sessionId, object? controller = null, CancellationToken cancellationToken = default) + internal async Task ConfigureSessionExtensionsAsync(string sessionId, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(sessionId); - var request = new ConfigureSessionExtensionsParams { SessionId = sessionId, Controller = CopilotClient.ToJsonElementForWire(controller) }; + var request = new ConfigureSessionExtensionsParams { SessionId = sessionId }; await CopilotClient.InvokeRpcAsync(_rpc, "sessions.configureSessionExtensions", [request], cancellationToken); } } @@ -21062,6 +24882,12 @@ internal SessionRpc(CopilotSession session) Interlocked.CompareExchange(ref field, new(_session), null) ?? field; + /// ContentExclusion APIs. + public ContentExclusionApi ContentExclusion => + field ?? + Interlocked.CompareExchange(ref field, new(_session), null) ?? + field; + /// Shell APIs. public ShellApi Shell => field ?? @@ -21092,6 +24918,12 @@ internal SessionRpc(CopilotSession session) Interlocked.CompareExchange(ref field, new(_session), null) ?? field; + /// LimitPrediction APIs. + public LimitPredictionApi LimitPrediction => + field ?? + Interlocked.CompareExchange(ref field, new(_session), null) ?? + field; + /// Remote APIs. public RemoteApi Remote => field ?? @@ -21129,12 +24961,12 @@ public async Task SuspendAsync(CancellationToken cancellationToken = default) /// If true, adds the message to the front of the queue instead of the end. /// If false, this message will not trigger a Premium Request Unit charge. User messages default to billable. /// If set, the request will fail if the named tool is not available when this message is among the user messages at the start of the current exchange. - /// Optional provenance tag copied to the resulting user.message event. Must match one of three forms: the literal `system`, `command-<command-id>` for messages originating from a command (e.g. slash command, Mission Control command), or `schedule-<numeric-id>` for messages originating from a scheduled job. + /// Optional provenance tag copied to the resulting user.message event. Must be `user`, `system`, `command-<command-id>` for command-originated messages, `schedule-<numeric-id>` for scheduled prompts, or `agent-<agent-id>` for prompts sent by another agent. /// The UI mode the agent was in when this message was sent. Defaults to the session's current mode. /// Custom HTTP headers to include in outbound model requests for this turn. Merged with session-level provider headers; per-turn headers augment and overwrite session-level headers with the same key. /// W3C Trace Context traceparent header for distributed tracing of this agent turn. /// W3C Trace Context tracestate header for distributed tracing. - /// If true, await completion of the agentic loop for this message before returning. Defaults to false (fire-and-forget). When true, the result still contains the same `messageId`; the caller can rely on the agent having processed the message before the call resolves. + /// If true, await completion of the agentic loop for this message before returning. Defaults to false (fire-and-forget). When true, the result still contains the same `messageId`; the caller can rely on the agent having processed the message before the call resolves. Transport-dependent tail semantics: on a LOCAL (in-process) session the wait additionally blocks until the completed turn's event tail has been dispatched to this session's in-process subscribers, so a subsequent read of subscriber state already reflects the turn; on a REMOTE session the wait resolves once the loop completes and mirrored delivery follows over the wire. Callers that need the stronger local guarantee on remote sessions should await the event stream explicitly. /// The to monitor for cancellation requests. The default is . /// Result of sending a user message. [Experimental(Diagnostics.Experimental)] @@ -21155,7 +24987,7 @@ public async Task SendAsync(string prompt, string? displayPrompt = n /// Custom HTTP headers to include in outbound model requests for this turn. Merged with session-level provider headers; per-turn headers augment and overwrite session-level headers with the same key. /// W3C Trace Context traceparent header for distributed tracing of this agent turn. /// W3C Trace Context tracestate header for distributed tracing. - /// If true, await completion of the agentic loop for this turn before returning. Defaults to false (fire-and-forget). When true, the result still contains the same `messageIds`; the caller can rely on the agent having processed the messages before the call resolves. + /// If true, await completion of the agentic loop for this turn before returning. Defaults to false (fire-and-forget). When true, the result still contains the same `messageIds`; the caller can rely on the agent having processed the messages before the call resolves. Transport-dependent tail semantics: on a LOCAL (in-process) session the wait additionally blocks until the completed turn's event tail has been dispatched to this session's in-process subscribers, so a subsequent read of subscriber state already reflects the turn; on a REMOTE session the wait resolves once the loop completes and mirrored delivery follows over the wire. Callers that need the stronger local guarantee on remote sessions should await the event stream explicitly. /// The to monitor for cancellation requests. The default is . /// Result of sending zero or more user messages. [Experimental(Diagnostics.Experimental)] @@ -21168,6 +25000,21 @@ public async Task SendMessagesAsync(IList m return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.sendMessages", [request], cancellationToken); } + /// Queues or sends an internal system notification to the session according to its passive policy. + /// Notification text to deliver to the model. + /// Optional structured notification kind. + /// Internal delivery options, including passive policy. + /// The to monitor for cancellation requests. The default is . + [Experimental(Diagnostics.Experimental)] + internal async Task SendSystemNotificationAsync(string message, object? kind = null, object? options = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(message); + _session.ThrowIfDisposed(); + + var request = new SendSystemNotificationRequest { SessionId = _session.SessionId, Message = message, Kind = CopilotClient.ToJsonElementForWire(kind), Options = CopilotClient.ToJsonElementForWire(options) }; + await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.sendSystemNotification", [request], cancellationToken); + } + /// Aborts the current agent turn. /// Finite reason code describing why the current turn was aborted. /// The to monitor for cancellation requests. The default is . @@ -21181,6 +25028,31 @@ public async Task AbortAsync(AbortReason? reason = null, Cancellati return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.abort", [request], cancellationToken); } + /// Interrupts the current main agent turn while leaving running background work (subagents, sidekicks, and promoted attached shells) alive. No-op when the main loop is not processing. + /// When true, the user's queued prompts are preserved and run as the next turn once the interrupted turn unwinds; when false (the default), the queue is cleared like a plain abort. + /// The to monitor for cancellation requests. The default is . + /// Result of interrupting the main agent turn. + [Experimental(Diagnostics.Experimental)] + public async Task InterruptMainTurnAsync(bool? flushQueued = null, CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new InterruptMainTurnRequest { SessionId = _session.SessionId, FlushQueued = flushQueued }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.interruptMainTurn", [request], cancellationToken); + } + + /// Cancels every running background agent (task-registry subagents plus sidekick agents) without interrupting the main agent loop. Promoted attached shells are left running. + /// The to monitor for cancellation requests. The default is . + /// The number of running background agents (task-registry agents) that were cancelled. + [Experimental(Diagnostics.Experimental)] + public async Task CancelAllBackgroundAgentsAsync(CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new SessionCancelAllBackgroundAgentsRequest { SessionId = _session.SessionId }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.cancelAllBackgroundAgents", [request], cancellationToken); + } + /// Shuts down the session and persists its final state. Awaits any deferred sessionEnd hooks before resolving so user-supplied hook scripts complete before the runtime tears down. /// Why the session is being shut down. Defaults to "routine" when omitted. /// Optional human-readable reason. Typically the message of the error that triggered shutdown when type is 'error'. @@ -21400,6 +25272,20 @@ public async Task RunAsync(string name, object args, RunOption return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.factory.run", [request], cancellationToken); } + /// Resumes a factory run using its persisted name, arguments, journal, and accounting. + /// Factory run identifier. + /// Optional per-invocation resource ceiling overrides. + /// The to monitor for cancellation requests. The default is . + /// Resolved persisted factory identity and resumed run envelope. + public async Task ResumeAsync(string runId, FactoryRunLimits? limits = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(runId); + _session.ThrowIfDisposed(); + + var request = new FactoryResumeRequest { SessionId = _session.SessionId, RunId = runId, Limits = limits }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.factory.resume", [request], cancellationToken); + } + /// Gets the current or settled envelope for a factory run. /// Factory run identifier. /// The to monitor for cancellation requests. The default is . @@ -21413,6 +25299,50 @@ public async Task GetRunAsync(string runId, CancellationToken return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.factory.getRun", [request], cancellationToken); } + /// Lists durable factory runs for this session in creation order. + /// Exclusive forward cursor. + /// Exclusive backward cursor. + /// Maximum terminal runs to return. Defaults to 200 and is capped at 500. + /// The to monitor for cancellation requests. The default is . + /// A page of factory runs in durable creation order. + public async Task ListRunsAsync(long? afterSeq = null, long? beforeSeq = null, int? limit = null, CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new FactoryListRunsRequest { SessionId = _session.SessionId, AfterSeq = afterSeq, BeforeSeq = beforeSeq, Limit = limit }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.factory.listRuns", [request], cancellationToken); + } + + /// Gets durable and live observability detail for one factory run. + /// Factory run identifier. + /// The to monitor for cancellation requests. The default is . + /// Full factory run observability detail. + public async Task GetRunDetailAsync(string runId, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(runId); + _session.ThrowIfDisposed(); + + var request = new FactoryGetRunRequest { SessionId = _session.SessionId, RunId = runId }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.factory.getRunDetail", [request], cancellationToken); + } + + /// Pages durable progress for one factory run. + /// Factory run identifier. + /// Optional phase identifier used to scope records and cursors. + /// Exclusive forward cursor. + /// Exclusive backward cursor. + /// Maximum records to return. Defaults to 200 and is capped at 500. + /// The to monitor for cancellation requests. The default is . + /// A bidirectional page of factory progress. + public async Task GetRunProgressAsync(string runId, string? phaseId = null, long? afterSeq = null, long? beforeSeq = null, int? limit = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(runId); + _session.ThrowIfDisposed(); + + var request = new FactoryGetRunProgressRequest { SessionId = _session.SessionId, RunId = runId, PhaseId = phaseId, AfterSeq = afterSeq, BeforeSeq = beforeSeq, Limit = limit }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.factory.getRunProgress", [request], cancellationToken); + } + /// Requests cancellation of a factory run and returns its run envelope. /// Factory run identifier. /// The to monitor for cancellation requests. The default is . @@ -21428,33 +25358,37 @@ public async Task CancelAsync(string runId, CancellationToken /// Records a batch of ordered factory progress lines. /// Factory run identifier. + /// Opaque token identifying the current factory execution attempt. /// Ordered progress lines to append. /// The to monitor for cancellation requests. The default is . /// Acknowledgement that a factory request was accepted. - public async Task LogAsync(string runId, IList lines, CancellationToken cancellationToken = default) + public async Task LogAsync(string runId, string executionToken, IList lines, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(runId); + ArgumentNullException.ThrowIfNull(executionToken); ArgumentNullException.ThrowIfNull(lines); _session.ThrowIfDisposed(); - var request = new FactoryLogRequest { SessionId = _session.SessionId, RunId = runId, Lines = lines }; + var request = new FactoryLogRequest { SessionId = _session.SessionId, RunId = runId, ExecutionToken = executionToken, Lines = lines }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.factory.log", [request], cancellationToken); } /// Runs one factory-scoped subagent and returns its result. /// Factory run identifier that owns the subagent. + /// Opaque token identifying the current factory execution attempt. /// Prompt to send to the subagent. /// Subagent execution options. /// The to monitor for cancellation requests. The default is . /// Result of one factory-scoped subagent call. - public async Task AgentAsync(string factoryRunId, string prompt, FactoryAgentOptions opts, CancellationToken cancellationToken = default) + public async Task AgentAsync(string factoryRunId, string executionToken, string prompt, FactoryAgentOptions opts, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(factoryRunId); + ArgumentNullException.ThrowIfNull(executionToken); ArgumentNullException.ThrowIfNull(prompt); ArgumentNullException.ThrowIfNull(opts); _session.ThrowIfDisposed(); - var request = new FactoryAgentRequest { SessionId = _session.SessionId, FactoryRunId = factoryRunId, Prompt = prompt, Opts = opts }; + var request = new FactoryAgentRequest { SessionId = _session.SessionId, FactoryRunId = factoryRunId, ExecutionToken = executionToken, Prompt = prompt, Opts = opts }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.factory.agent", [request], cancellationToken); } @@ -21478,33 +25412,37 @@ internal FactoryJournalApi(CopilotSession session) /// Reads a memoized factory journal entry. /// Factory run identifier. + /// Opaque token identifying the current factory execution attempt. /// Namespaced journal key. /// The to monitor for cancellation requests. The default is . /// Result of reading a factory journal entry. - public async Task GetAsync(string runId, string key, CancellationToken cancellationToken = default) + public async Task GetAsync(string runId, string executionToken, string key, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(runId); + ArgumentNullException.ThrowIfNull(executionToken); ArgumentNullException.ThrowIfNull(key); _session.ThrowIfDisposed(); - var request = new FactoryJournalGetRequest { SessionId = _session.SessionId, RunId = runId, Key = key }; + var request = new FactoryJournalGetRequest { SessionId = _session.SessionId, RunId = runId, ExecutionToken = executionToken, Key = key }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.factory.journal.get", [request], cancellationToken); } /// Stores a memoized factory journal entry. /// Factory run identifier. + /// Opaque token identifying the current factory execution attempt. /// Namespaced journal key. /// JSON result to memoize. /// The to monitor for cancellation requests. The default is . /// Acknowledgement that a factory request was accepted. - public async Task PutAsync(string runId, string key, object resultJson, CancellationToken cancellationToken = default) + public async Task PutAsync(string runId, string executionToken, string key, object resultJson, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(runId); + ArgumentNullException.ThrowIfNull(executionToken); ArgumentNullException.ThrowIfNull(key); ArgumentNullException.ThrowIfNull(resultJson); _session.ThrowIfDisposed(); - var request = new FactoryJournalPutRequest { SessionId = _session.SessionId, RunId = runId, Key = key, ResultJson = CopilotClient.ToJsonElementForWire(resultJson)!.Value }; + var request = new FactoryJournalPutRequest { SessionId = _session.SessionId, RunId = runId, ExecutionToken = executionToken, Key = key, ResultJson = CopilotClient.ToJsonElementForWire(resultJson)!.Value }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.factory.journal.put", [request], cancellationToken); } } @@ -21533,19 +25471,20 @@ public async Task GetCurrentAsync(CancellationToken cancellationTo /// Switches the session to a model and optional reasoning configuration. /// Model selection id to switch to, as returned by `list`. A bare id (e.g. `claude-sonnet-4.6`) names a Copilot (CAPI) model; a provider-qualified id (`provider/id`, e.g. `acme/claude-sonnet`) targets a registry BYOK model. - /// Reasoning effort level to use for the model. "none" disables reasoning. + /// Reasoning effort level to use for the model. CAPI values are model-defined and validated against the selected model; BYOK providers may define additional values. "none" disables reasoning. When omitted, no effort override is applied. /// Reasoning summary mode to request for supported model clients. /// Output verbosity level to request for supported models. /// Override individual model capabilities resolved by the runtime. /// Explicit context tier for the selected model. `"default"` / `"long_context"` apply the requested tier; omit this field to use normal model behavior with no explicit tier. + /// When true, defer this switch (enqueue it) if another model change is already queued, even when no turn is active — so it drains last (FIFO) and wins over the already-queued change. Intended for genuine user-initiated model selections; internal restore/reapply switches omit it and apply immediately when no turn is active. When no other model change is queued this has no effect (a switch still applies immediately unless a turn is active). /// The to monitor for cancellation requests. The default is . /// The model identifier active on the session after the switch. - public async Task SwitchToAsync(string modelId, string? reasoningEffort = null, ReasoningSummary? reasoningSummary = null, Verbosity? verbosity = null, ModelCapabilitiesOverride? modelCapabilities = null, ContextTier? contextTier = null, CancellationToken cancellationToken = default) + public async Task SwitchToAsync(string modelId, string? reasoningEffort = null, ReasoningSummary? reasoningSummary = null, Verbosity? verbosity = null, ModelCapabilitiesOverride? modelCapabilities = null, ContextTier? contextTier = null, bool? deferIfModelChangeQueued = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(modelId); _session.ThrowIfDisposed(); - var request = new ModelSwitchToRequest { SessionId = _session.SessionId, ModelId = modelId, ReasoningEffort = reasoningEffort, ReasoningSummary = reasoningSummary, Verbosity = verbosity, ModelCapabilities = modelCapabilities, ContextTier = contextTier }; + var request = new ModelSwitchToRequest { SessionId = _session.SessionId, ModelId = modelId, ReasoningEffort = reasoningEffort, ReasoningSummary = reasoningSummary, Verbosity = verbosity, ModelCapabilities = modelCapabilities, ContextTier = contextTier, DeferIfModelChangeQueued = deferIfModelChangeQueued }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.model.switchTo", [request], cancellationToken); } @@ -21566,11 +25505,11 @@ public async Task SetReasoningEffortAsync(string /// Optional listing options. /// The to monitor for cancellation requests. The default is . /// The list of models available to this session. - public async Task ListAsync(ModelListRequest? request = null, CancellationToken cancellationToken = default) + public async Task ListAsync(SessionModelListRequest? request = null, CancellationToken cancellationToken = default) { _session.ThrowIfDisposed(); - var rpcRequest = new ModelListRequestWithSession { SessionId = _session.SessionId, SkipCache = request?.SkipCache }; + var rpcRequest = new SessionModelListRequestWithSession { SessionId = _session.SessionId, SkipCache = request?.SkipCache }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.model.list", [rpcRequest], cancellationToken); } } @@ -21746,6 +25685,31 @@ public async Task GetWorkspaceAsync(CancellationTo return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.workspaces.getWorkspace", [request], cancellationToken); } + /// Updates workspace metadata for a local session and returns the refreshed workspace. + /// Opaque workspace context supplied by the session host. + /// Optional workspace display name override. + /// The to monitor for cancellation requests. The default is . + /// Current workspace metadata for the session, including its absolute filesystem path when available. + public async Task UpdateMetadataAsync(object? context = null, string? name = null, CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new WorkspacesUpdateMetadataRequest { SessionId = _session.SessionId, Context = CopilotClient.ToJsonElementForWire(context), Name = name }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.workspaces.updateMetadata", [request], cancellationToken); + } + + /// Ensures a local session workspace exists and returns it. + /// Opaque workspace context supplied by the session host. + /// The to monitor for cancellation requests. The default is . + /// Current workspace metadata for the session, including its absolute filesystem path when available. + public async Task EnsureAsync(object? context = null, CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new WorkspacesEnsureRequest { SessionId = _session.SessionId, Context = CopilotClient.ToJsonElementForWire(context) }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.workspaces.ensure", [request], cancellationToken); + } + /// Lists files stored in the session workspace files directory. /// The to monitor for cancellation requests. The default is . /// Relative paths of files stored in the session workspace files directory. @@ -21776,35 +25740,108 @@ public async Task ReadFileAsync(string path, Cancellat /// The to monitor for cancellation requests. The default is . public async Task CreateFileAsync(string path, string content, CancellationToken cancellationToken = default) { - ArgumentNullException.ThrowIfNull(path); + ArgumentNullException.ThrowIfNull(path); + ArgumentNullException.ThrowIfNull(content); + _session.ThrowIfDisposed(); + + var request = new WorkspacesCreateFileRequest { SessionId = _session.SessionId, Path = path, Content = content }; + await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.workspaces.createFile", [request], cancellationToken); + } + + /// Lists workspace checkpoints in chronological order. + /// The to monitor for cancellation requests. The default is . + /// Workspace checkpoints in chronological order; empty when the workspace is not enabled. + public async Task ListCheckpointsAsync(CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new SessionWorkspacesListCheckpointsRequest { SessionId = _session.SessionId }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.workspaces.listCheckpoints", [request], cancellationToken); + } + + /// Reads the content of a workspace checkpoint by number. + /// Checkpoint number to read. + /// The to monitor for cancellation requests. The default is . + /// Checkpoint content as a UTF-8 string, or null when the checkpoint or workspace is missing. + public async Task ReadCheckpointAsync(long number, CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new WorkspacesReadCheckpointRequest { SessionId = _session.SessionId, Number = number }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.workspaces.readCheckpoint", [request], cancellationToken); + } + + /// Adds a compaction summary checkpoint to the local session workspace. + /// Summary title shown in checkpoint listings. + /// Markdown summary content to persist. + /// The to monitor for cancellation requests. The default is . + /// Persisted summary metadata and refreshed workspace metadata. + public async Task AddSummaryAsync(string title, string content, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(title); + ArgumentNullException.ThrowIfNull(content); + _session.ThrowIfDisposed(); + + var request = new WorkspacesAddSummaryRequest { SessionId = _session.SessionId, Title = title, Content = content }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.workspaces.addSummary", [request], cancellationToken); + } + + /// Truncates local workspace compaction summaries after a rollback. + /// Number of newest summaries to keep. + /// The to monitor for cancellation requests. The default is . + /// Current workspace metadata for the session, including its absolute filesystem path when available. + public async Task TruncateSummariesAsync(long keepCount, CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new WorkspacesTruncateSummariesRequest { SessionId = _session.SessionId, KeepCount = keepCount }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.workspaces.truncateSummaries", [request], cancellationToken); + } + + /// Reads the autopilot objective state file from the local session workspace. + /// The to monitor for cancellation requests. The default is . + /// Autopilot objective file content, or null when missing. + public async Task ReadAutopilotObjectiveAsync(CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new SessionWorkspacesReadAutopilotObjectiveRequest { SessionId = _session.SessionId }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.workspaces.readAutopilotObjective", [request], cancellationToken); + } + + /// Writes the autopilot objective state file in the local session workspace. + /// Autopilot objective file content. + /// The to monitor for cancellation requests. The default is . + /// Result of writing the autopilot objective file. + public async Task WriteAutopilotObjectiveAsync(string content, CancellationToken cancellationToken = default) + { ArgumentNullException.ThrowIfNull(content); _session.ThrowIfDisposed(); - var request = new WorkspacesCreateFileRequest { SessionId = _session.SessionId, Path = path, Content = content }; - await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.workspaces.createFile", [request], cancellationToken); + var request = new WorkspacesWriteAutopilotObjectiveRequest { SessionId = _session.SessionId, Content = content }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.workspaces.writeAutopilotObjective", [request], cancellationToken); } - /// Lists workspace checkpoints in chronological order. + /// Deletes the autopilot objective state file from the local session workspace. /// The to monitor for cancellation requests. The default is . - /// Workspace checkpoints in chronological order; empty when the workspace is not enabled. - public async Task ListCheckpointsAsync(CancellationToken cancellationToken = default) + /// Result of deleting the autopilot objective file. + public async Task DeleteAutopilotObjectiveAsync(CancellationToken cancellationToken = default) { _session.ThrowIfDisposed(); - var request = new SessionWorkspacesListCheckpointsRequest { SessionId = _session.SessionId }; - return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.workspaces.listCheckpoints", [request], cancellationToken); + var request = new SessionWorkspacesDeleteAutopilotObjectiveRequest { SessionId = _session.SessionId }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.workspaces.deleteAutopilotObjective", [request], cancellationToken); } - /// Reads the content of a workspace checkpoint by number. - /// Checkpoint number to read. + /// Checks whether the local session workspace has an autopilot objective state file. /// The to monitor for cancellation requests. The default is . - /// Checkpoint content as a UTF-8 string, or null when the checkpoint or workspace is missing. - public async Task ReadCheckpointAsync(long number, CancellationToken cancellationToken = default) + /// Whether the autopilot objective file exists. + public async Task AutopilotObjectiveExistsAsync(CancellationToken cancellationToken = default) { _session.ThrowIfDisposed(); - var request = new WorkspacesReadCheckpointRequest { SessionId = _session.SessionId, Number = number }; - return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.workspaces.readCheckpoint", [request], cancellationToken); + var request = new SessionWorkspacesAutopilotObjectiveExistsRequest { SessionId = _session.SessionId }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.workspaces.autopilotObjectiveExists", [request], cancellationToken); } /// Saves pasted content as a UTF-8 file in the session workspace. @@ -21820,7 +25857,7 @@ public async Task SaveLargePasteAsync(string con return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.workspaces.saveLargePaste", [request], cancellationToken); } - /// Computes a diff for the session workspace. + /// Computes a diff for the session workspace. Never rejects for a busy session: a `session`-mode diff that cannot read the session's file-change captures falls back to an unstaged git diff with `isFallback: true` and reports why in `unavailableReason`. /// Diff mode requested by the client. /// When true, ignore whitespace-only changes (git `--ignore-all-space`). Defaults to false. /// The to monitor for cancellation requests. The default is . @@ -21929,15 +25966,30 @@ internal AgentApi(CopilotSession session) _session = session; } - /// Lists custom agents available to the session. + /// Lists agents available to the session. Defaults to custom agents only; pass includeBuiltInAgents to include the effective built-in agents. + /// Controls whether built-in agents and authored prompt text are included. + /// The to monitor for cancellation requests. The default is . + /// Agents available to the session. + public async Task ListAsync(SessionAgentListRequest? request = null, CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var rpcRequest = new SessionAgentListRequestWithSession { SessionId = _session.SessionId, IncludeBuiltInAgents = request?.IncludeBuiltInAgents, IncludePrompt = request?.IncludePrompt }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.agent.list", [rpcRequest], cancellationToken); + } + + /// Sets an in-memory authored prompt override for an available agent. For built-in agents, this replaces only the static base prompt while preserving runtime-owned dynamic prompt composition and behavior. The special `general-purpose` agent is not overrideable. Overrides are not persisted; resumed and forked sessions start without them, so the host must re-apply them. + /// Stable effective agent id. Plugin namespace separators are normalized. + /// Replacement authored prompt. Empty text is valid. /// The to monitor for cancellation requests. The default is . - /// Custom agents available to the session. - public async Task ListAsync(CancellationToken cancellationToken = default) + public async Task SetPromptAsync(string id, string prompt, CancellationToken cancellationToken = default) { + ArgumentNullException.ThrowIfNull(id); + ArgumentNullException.ThrowIfNull(prompt); _session.ThrowIfDisposed(); - var request = new SessionAgentListRequest { SessionId = _session.SessionId }; - return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.agent.list", [request], cancellationToken); + var request = new AgentSetPromptRequest { SessionId = _session.SessionId, Id = id, Prompt = prompt }; + await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.agent.setPrompt", [request], cancellationToken); } /// Gets the currently selected custom agent for the session. @@ -22289,15 +26341,13 @@ public async Task ReloadAsync(CancellationToken cancellationToken = default) } /// Reloads MCP server connections for the session with an explicit host-provided configuration. - /// Opaque runtime MCP reload configuration. Marked internal: an in-process runtime shape (reloadMcpServers throws over the wire). /// The to monitor for cancellation requests. The default is . /// MCP server startup filtering result. - internal async Task ReloadWithConfigAsync(object config, CancellationToken cancellationToken = default) + internal async Task ReloadWithConfigAsync(CancellationToken cancellationToken = default) { - ArgumentNullException.ThrowIfNull(config); _session.ThrowIfDisposed(); - var request = new McpReloadWithConfigRequest { SessionId = _session.SessionId, Config = CopilotClient.ToJsonElementForWire(config)!.Value }; + var request = new McpReloadWithConfigRequest { SessionId = _session.SessionId }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.mcp.reloadWithConfig", [request], cancellationToken); } @@ -22357,29 +26407,26 @@ public async Task RemoveGitHubAsync(CancellationToken can } /// Configures the built-in GitHub MCP server for the session's current auth context. - /// Opaque runtime auth info for GitHub MCP configuration. Marked internal: an in-process runtime shape (configureGitHubMcp is a no-op over the wire). /// The to monitor for cancellation requests. The default is . /// Result of configuring GitHub MCP. - internal async Task ConfigureGitHubAsync(object authInfo, CancellationToken cancellationToken = default) + internal async Task ConfigureGitHubAsync(CancellationToken cancellationToken = default) { - ArgumentNullException.ThrowIfNull(authInfo); _session.ThrowIfDisposed(); - var request = new McpConfigureGitHubRequest { SessionId = _session.SessionId, AuthInfo = CopilotClient.ToJsonElementForWire(authInfo)!.Value }; + var request = new McpConfigureGitHubRequest { SessionId = _session.SessionId }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.mcp.configureGitHub", [request], cancellationToken); } - /// Starts an individual MCP server on the live session from a caller-supplied config. Session-scoped and ephemeral: the server is added to this session's running set only and is reaped when the session ends. Does NOT modify persistent user configuration (`mcp.config.*`), so it does not affect future sessions. The server surfaces through `session.mcp.list` and the `session.mcp_servers_loaded` / `session.mcp_server_status_changed` events like any other server. + /// Starts an individual MCP server on the live session. Omit `config` for a config-free start-by-name of an already-configured server (reuses the server's already-registered configuration); supply `config` to start from a caller-supplied configuration. Session-scoped and ephemeral: the server is added to this session's running set only and is reaped when the session ends. Does NOT modify persistent user configuration (`mcp.config.*`), so it does not affect future sessions. The server surfaces through `session.mcp.list` and the `session.mcp_servers_loaded` / `session.mcp_server_status_changed` events like any other server. /// Name of the MCP server to start. - /// MCP server configuration (stdio process or remote HTTP/SSE). + /// MCP server configuration (stdio process or remote HTTP/SSE). Omit to start the server with its already-registered configuration (config-free start-by-name). /// The to monitor for cancellation requests. The default is . - public async Task StartServerAsync(string serverName, object config, CancellationToken cancellationToken = default) + public async Task StartServerAsync(string serverName, object? config = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(serverName); - ArgumentNullException.ThrowIfNull(config); _session.ThrowIfDisposed(); - var request = new McpStartServerRequest { SessionId = _session.SessionId, ServerName = serverName, Config = CopilotClient.ToJsonElementForWire(config)!.Value }; + var request = new McpStartServerRequest { SessionId = _session.SessionId, ServerName = serverName, Config = CopilotClient.ToJsonElementForWire(config) }; await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.mcp.startServer", [request], cancellationToken); } @@ -22410,19 +26457,13 @@ public async Task StopServerAsync(string serverName, CancellationToken cancellat /// Registers a pre-connected external MCP client (e.g. IDE) on the session's host. The caller retains lifecycle ownership of the client and transport. Marked internal because the `client` and `transport` arguments are in-process MCP SDK instances that cannot be serialized across the JSON-RPC boundary; once the CLI moves on top of the SDK, external clients will be expressed as transport configs the runtime can construct itself. /// Logical server name for the external client. - /// In-process MCP Client instance. Marked internal: cannot be serialized across the JSON-RPC boundary. - /// In-process MCP Transport instance. Marked internal: cannot be serialized across the JSON-RPC boundary. - /// In-process server config (MCPServerConfig) paired with the in-process client/transport. Marked internal alongside its companions. /// The to monitor for cancellation requests. The default is . - internal async Task RegisterExternalClientAsync(string serverName, object client, object transport, object config, CancellationToken cancellationToken = default) + internal async Task RegisterExternalClientAsync(string serverName, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(serverName); - ArgumentNullException.ThrowIfNull(client); - ArgumentNullException.ThrowIfNull(transport); - ArgumentNullException.ThrowIfNull(config); _session.ThrowIfDisposed(); - var request = new McpRegisterExternalClientRequest { SessionId = _session.SessionId, ServerName = serverName, Client = CopilotClient.ToJsonElementForWire(client)!.Value, Transport = CopilotClient.ToJsonElementForWire(transport)!.Value, Config = CopilotClient.ToJsonElementForWire(config)!.Value }; + var request = new McpRegisterExternalClientRequest { SessionId = _session.SessionId, ServerName = serverName }; await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.mcp.registerExternalClient", [request], cancellationToken); } @@ -22502,6 +26543,18 @@ public async Task HandlePendingRequestAsync(string return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.mcp.oauth.handlePendingRequest", [request], cancellationToken); } + /// Notifies the session that MCP OAuth authentication succeeded and updated credentials were persisted, so cached tool definitions can be refreshed. + /// Name of the MCP server whose OAuth credentials were updated. Omit only when the host cannot identify the server. + /// Whether the target session must mint a session-scoped access token instead of reusing a shared access token persisted by another session. + /// The to monitor for cancellation requests. The default is . + public async Task AuthenticationStateChangedAsync(string? serverName = null, bool? refreshSessionToken = null, CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new McpOauthAuthenticationStateChangedRequest { SessionId = _session.SessionId, ServerName = serverName, RefreshSessionToken = refreshSessionToken }; + await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.mcp.oauth.authenticationStateChanged", [request], cancellationToken); + } + /// Starts OAuth authentication for a remote MCP server. /// Name of the remote MCP server to authenticate. /// When true, clears any cached OAuth token for the server and runs a full new authorization. Use when the user explicitly wants to switch accounts or believes their session is stuck. @@ -22521,6 +26574,19 @@ public async Task LoginAsync(string serverName, bool? force var request = new McpOauthLoginRequest { SessionId = _session.SessionId, ServerName = serverName, ForceReauth = forceReauth, ClientName = clientName, CallbackSuccessMessage = callbackSuccessMessage, ClientId = clientId, ClientSecret = clientSecret, PublicClient = publicClient, GrantType = grantType }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.mcp.oauth.login", [request], cancellationToken); } + + /// Responds to a pending MCP OAuth authorization request by its request id. + /// OAuth request identifier from the mcp.oauth_required event. + /// The to monitor for cancellation requests. The default is . + /// Indicates whether the pending MCP OAuth response was accepted. + public async Task RespondAsync(string requestId, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(requestId); + _session.ThrowIfDisposed(); + + var request = new McpOauthRespondRequest { SessionId = _session.SessionId, RequestId = requestId }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.mcp.oauth.respond", [request], cancellationToken); + } } /// Provides session-scoped McpHeaders APIs. @@ -22726,11 +26792,11 @@ public async Task ListAsync(CancellationToken cancellationToken = de /// Reloads the session's plugin set, refreshing MCP servers, custom agents, hooks, and skills cache so SDK-driven changes via `server.plugins.*` take effect immediately. /// Optional flags controlling which side effects the reload performs. /// The to monitor for cancellation requests. The default is . - public async Task ReloadAsync(PluginsReloadRequest? request = null, CancellationToken cancellationToken = default) + public async Task ReloadAsync(SessionPluginsReloadRequest? request = null, CancellationToken cancellationToken = default) { _session.ThrowIfDisposed(); - var rpcRequest = new PluginsReloadRequestWithSession { SessionId = _session.SessionId, ReloadMcp = request?.ReloadMcp, ReloadCustomAgents = request?.ReloadCustomAgents, ReloadHooks = request?.ReloadHooks, ReloadExtensions = request?.ReloadExtensions, DeferRepoHooks = request?.DeferRepoHooks }; + var rpcRequest = new SessionPluginsReloadRequestWithSession { SessionId = _session.SessionId, ReloadMcp = request?.ReloadMcp, ReloadCustomAgents = request?.ReloadCustomAgents, ReloadHooks = request?.ReloadHooks, ReloadExtensions = request?.ReloadExtensions, DeferRepoHooks = request?.DeferRepoHooks }; await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.plugins.reload", [rpcRequest], cancellationToken); } } @@ -22750,11 +26816,11 @@ internal ProviderApi(CopilotSession session) /// Optional model identifier to scope the endpoint snapshot to. /// The to monitor for cancellation requests. The default is . /// A snapshot of the provider endpoint the session is currently configured to talk to. - public async Task GetEndpointAsync(ProviderGetEndpointRequest? request = null, CancellationToken cancellationToken = default) + public async Task GetEndpointAsync(SessionProviderGetEndpointRequest? request = null, CancellationToken cancellationToken = default) { _session.ThrowIfDisposed(); - var rpcRequest = new ProviderGetEndpointRequestWithSession { SessionId = _session.SessionId, ModelId = request?.ModelId }; + var rpcRequest = new SessionProviderGetEndpointRequestWithSession { SessionId = _session.SessionId, ModelId = request?.ModelId }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.provider.getEndpoint", [rpcRequest], cancellationToken); } @@ -22786,7 +26852,7 @@ internal OptionsApi(CopilotSession session) /// Patches the genuinely-mutable subset of session options. /// The model ID to use for assistant turns. /// Per-property model capability overrides for the selected model. - /// Reasoning effort for the selected model (model-defined enum). + /// Reasoning effort for the selected model. CAPI values are model-defined and validated against the selected model; BYOK providers may define additional values. When omitted, no effort override is applied. /// Reasoning summary mode for supported model clients. /// Output verbosity level for supported models. /// Identifier of the client driving the session. @@ -22803,15 +26869,16 @@ internal OptionsApi(CopilotSession session) /// Built-in subagent names to exclude from this session. Excluded built-ins are hidden from agent discovery and cannot be dispatched unless a custom agent with the same name is available. /// Controls how availableTools (allowlist) and excludedTools (denylist) combine when both are set. /// Whether shell-script safety heuristics are enabled. - /// Shell init profile (`None` or `NonInteractive`). - /// Per-shell process flags (e.g., `pwsh` arguments). + /// Per-session settings for built-in shell tools. + /// Use shell.initProfile instead. Shell init profile (`None` or `NonInteractive`). + /// PowerShell process flags applied to built-in and user-requested shell commands. /// Resolved sandbox configuration. /// Whether interactive shell sessions are logged. /// How env values are passed to MCP servers (`direct` inlines literal values; `indirect` resolves at launch). /// Whether to include instructions from every MCP server in the system prompt instead of only allowlisted servers. /// Additional directories to search for skills. /// Skill IDs that should be excluded from this session. - /// Whether to discover custom instructions on demand after successful file views (AGENTS.md / CLAUDE.md / .github/copilot-instructions.md surfacing). Combined with `skipCustomInstructions` and the runtime-side `ON_DEMAND_INSTRUCTIONS` feature flag. + /// Whether to discover custom instructions on demand after successful file views (AGENTS.md / CLAUDE.md / .github/copilot-instructions.md surfacing). Combined with `skipCustomInstructions`. /// Maximum decoded byte size of a single model-facing binary tool result (e.g. an image) persisted inline in session events and re-presented to the model on later turns / resume. Larger results are persisted as a metadata-only marker and shown to the model as a short text note. Defaults to 10 MB. /// Full set of installed plugins for the session. Replaces the existing list; the runtime invalidates the skills cache only when the list materially changes. /// Whether to default custom agents to local-only execution. @@ -22828,6 +26895,7 @@ internal OptionsApi(CopilotSession session) /// Whether to surface reasoning-summary events from the model. /// Runtime context discriminator (e.g., `cli`, `actions`). /// Override directory for the session-events log. When unset, the runtime's default events log directory is used. + /// Whether subagent callback events should be forwarded into the session event log sink. /// Additional content-exclusion policies to merge into the session's policy set. /// Whether to expose the `manage_schedule` tool to the agent. The runtime always owns the per-session schedule registry; this flag only controls tool exposure (typically gated to staff users). /// Replaces the session's capability set with the given list. Use to enable or disable capabilities mid-session (e.g., remove `memory` for reproducible scripted runs). Omit the field to leave the existing capability set unchanged. @@ -22841,11 +26909,11 @@ internal OptionsApi(CopilotSession session) /// Optional session limits. Pass null to clear the session limits. /// The to monitor for cancellation requests. The default is . /// Indicates whether the session options patch was applied successfully. - public async Task UpdateAsync(string? model = null, ModelCapabilitiesOverride? modelCapabilitiesOverrides = null, string? reasoningEffort = null, OptionsUpdateReasoningSummary? reasoningSummary = null, Verbosity? verbosity = null, string? clientName = null, string? lspClientName = null, string? integrationId = null, IDictionary? featureFlags = null, bool? isExperimentalMode = null, ProviderConfig? provider = null, CapiSessionOptions? capi = null, string? workingDirectory = null, IList? availableTools = null, IList? excludedTools = null, IList? includedBuiltinAgents = null, IList? excludedBuiltinAgents = null, OptionsUpdateToolFilterPrecedence? toolFilterPrecedence = null, bool? enableScriptSafety = null, string? shellInitProfile = null, IList? shellProcessFlags = null, SandboxConfig? sandboxConfig = null, bool? logInteractiveShells = null, OptionsUpdateEnvValueMode? envValueMode = null, bool? allowAllMcpServerInstructions = null, IList? skillDirectories = null, IList? disabledSkills = null, bool? enableOnDemandInstructionDiscovery = null, long? maxInlineBinaryBytes = null, IList? installedPlugins = null, bool? customAgentsLocalOnly = null, bool? suppressCustomAgentPrompt = null, bool? skipCustomInstructions = null, IList? disabledInstructionSources = null, bool? coauthorEnabled = null, string? trajectoryFile = null, bool? enableStreaming = null, string? copilotUrl = null, bool? askUserDisabled = null, bool? continueOnAutoMode = null, bool? runningInInteractiveMode = null, bool? enableReasoningSummaries = null, string? agentContext = null, string? eventsLogDirectory = null, IList? additionalContentExclusionPolicies = null, bool? manageScheduleEnabled = null, IList? sessionCapabilities = null, bool? skipEmbeddingRetrieval = null, string? organizationCustomInstructions = null, bool? enableFileHooks = null, bool? enableHostGitOperations = null, bool? enableSessionStore = null, bool? enableSkills = null, OptionsUpdateContextTier? contextTier = null, SessionLimitsConfig? sessionLimits = null, CancellationToken cancellationToken = default) + public async Task UpdateAsync(string? model = null, ModelCapabilitiesOverride? modelCapabilitiesOverrides = null, string? reasoningEffort = null, OptionsUpdateReasoningSummary? reasoningSummary = null, Verbosity? verbosity = null, string? clientName = null, string? lspClientName = null, string? integrationId = null, IDictionary? featureFlags = null, bool? isExperimentalMode = null, ProviderConfig? provider = null, CapiSessionOptions? capi = null, string? workingDirectory = null, IList? availableTools = null, IList? excludedTools = null, IList? includedBuiltinAgents = null, IList? excludedBuiltinAgents = null, OptionsUpdateToolFilterPrecedence? toolFilterPrecedence = null, bool? enableScriptSafety = null, ShellOptions? shell = null, string? shellInitProfile = null, IList? shellProcessFlags = null, SandboxConfig? sandboxConfig = null, bool? logInteractiveShells = null, OptionsUpdateEnvValueMode? envValueMode = null, bool? allowAllMcpServerInstructions = null, IList? skillDirectories = null, IList? disabledSkills = null, bool? enableOnDemandInstructionDiscovery = null, long? maxInlineBinaryBytes = null, IList? installedPlugins = null, bool? customAgentsLocalOnly = null, bool? suppressCustomAgentPrompt = null, bool? skipCustomInstructions = null, IList? disabledInstructionSources = null, bool? coauthorEnabled = null, string? trajectoryFile = null, bool? enableStreaming = null, string? copilotUrl = null, bool? askUserDisabled = null, bool? continueOnAutoMode = null, bool? runningInInteractiveMode = null, bool? enableReasoningSummaries = null, string? agentContext = null, string? eventsLogDirectory = null, bool? eventsLogIncludesSubagents = null, IList? additionalContentExclusionPolicies = null, bool? manageScheduleEnabled = null, IList? sessionCapabilities = null, bool? skipEmbeddingRetrieval = null, string? organizationCustomInstructions = null, bool? enableFileHooks = null, bool? enableHostGitOperations = null, bool? enableSessionStore = null, bool? enableSkills = null, OptionsUpdateContextTier? contextTier = null, SessionLimitsConfig? sessionLimits = null, CancellationToken cancellationToken = default) { _session.ThrowIfDisposed(); - var request = new SessionUpdateOptionsParams { SessionId = _session.SessionId, Model = model, ModelCapabilitiesOverrides = modelCapabilitiesOverrides, ReasoningEffort = reasoningEffort, ReasoningSummary = reasoningSummary, Verbosity = verbosity, ClientName = clientName, LspClientName = lspClientName, IntegrationId = integrationId, FeatureFlags = featureFlags, IsExperimentalMode = isExperimentalMode, Provider = provider, Capi = capi, WorkingDirectory = workingDirectory, AvailableTools = availableTools, ExcludedTools = excludedTools, IncludedBuiltinAgents = includedBuiltinAgents, ExcludedBuiltinAgents = excludedBuiltinAgents, ToolFilterPrecedence = toolFilterPrecedence, EnableScriptSafety = enableScriptSafety, ShellInitProfile = shellInitProfile, ShellProcessFlags = shellProcessFlags, SandboxConfig = sandboxConfig, LogInteractiveShells = logInteractiveShells, EnvValueMode = envValueMode, AllowAllMcpServerInstructions = allowAllMcpServerInstructions, SkillDirectories = skillDirectories, DisabledSkills = disabledSkills, EnableOnDemandInstructionDiscovery = enableOnDemandInstructionDiscovery, MaxInlineBinaryBytes = maxInlineBinaryBytes, InstalledPlugins = installedPlugins, CustomAgentsLocalOnly = customAgentsLocalOnly, SuppressCustomAgentPrompt = suppressCustomAgentPrompt, SkipCustomInstructions = skipCustomInstructions, DisabledInstructionSources = disabledInstructionSources, CoauthorEnabled = coauthorEnabled, TrajectoryFile = trajectoryFile, EnableStreaming = enableStreaming, CopilotUrl = copilotUrl, AskUserDisabled = askUserDisabled, ContinueOnAutoMode = continueOnAutoMode, RunningInInteractiveMode = runningInInteractiveMode, EnableReasoningSummaries = enableReasoningSummaries, AgentContext = agentContext, EventsLogDirectory = eventsLogDirectory, AdditionalContentExclusionPolicies = additionalContentExclusionPolicies, ManageScheduleEnabled = manageScheduleEnabled, SessionCapabilities = sessionCapabilities, SkipEmbeddingRetrieval = skipEmbeddingRetrieval, OrganizationCustomInstructions = organizationCustomInstructions, EnableFileHooks = enableFileHooks, EnableHostGitOperations = enableHostGitOperations, EnableSessionStore = enableSessionStore, EnableSkills = enableSkills, ContextTier = contextTier, SessionLimits = sessionLimits }; + var request = new SessionUpdateOptionsParams { SessionId = _session.SessionId, Model = model, ModelCapabilitiesOverrides = modelCapabilitiesOverrides, ReasoningEffort = reasoningEffort, ReasoningSummary = reasoningSummary, Verbosity = verbosity, ClientName = clientName, LspClientName = lspClientName, IntegrationId = integrationId, FeatureFlags = featureFlags, IsExperimentalMode = isExperimentalMode, Provider = provider, Capi = capi, WorkingDirectory = workingDirectory, AvailableTools = availableTools, ExcludedTools = excludedTools, IncludedBuiltinAgents = includedBuiltinAgents, ExcludedBuiltinAgents = excludedBuiltinAgents, ToolFilterPrecedence = toolFilterPrecedence, EnableScriptSafety = enableScriptSafety, Shell = shell, ShellInitProfile = shellInitProfile, ShellProcessFlags = shellProcessFlags, SandboxConfig = sandboxConfig, LogInteractiveShells = logInteractiveShells, EnvValueMode = envValueMode, AllowAllMcpServerInstructions = allowAllMcpServerInstructions, SkillDirectories = skillDirectories, DisabledSkills = disabledSkills, EnableOnDemandInstructionDiscovery = enableOnDemandInstructionDiscovery, MaxInlineBinaryBytes = maxInlineBinaryBytes, InstalledPlugins = installedPlugins, CustomAgentsLocalOnly = customAgentsLocalOnly, SuppressCustomAgentPrompt = suppressCustomAgentPrompt, SkipCustomInstructions = skipCustomInstructions, DisabledInstructionSources = disabledInstructionSources, CoauthorEnabled = coauthorEnabled, TrajectoryFile = trajectoryFile, EnableStreaming = enableStreaming, CopilotUrl = copilotUrl, AskUserDisabled = askUserDisabled, ContinueOnAutoMode = continueOnAutoMode, RunningInInteractiveMode = runningInInteractiveMode, EnableReasoningSummaries = enableReasoningSummaries, AgentContext = agentContext, EventsLogDirectory = eventsLogDirectory, EventsLogIncludesSubagents = eventsLogIncludesSubagents, AdditionalContentExclusionPolicies = additionalContentExclusionPolicies, ManageScheduleEnabled = manageScheduleEnabled, SessionCapabilities = sessionCapabilities, SkipEmbeddingRetrieval = skipEmbeddingRetrieval, OrganizationCustomInstructions = organizationCustomInstructions, EnableFileHooks = enableFileHooks, EnableHostGitOperations = enableHostGitOperations, EnableSessionStore = enableSessionStore, EnableSkills = enableSkills, ContextTier = contextTier, SessionLimits = sessionLimits }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.options.update", [request], cancellationToken); } } @@ -23021,11 +27089,11 @@ internal CommandsApi(CopilotSession session) /// Optional filters controlling which command sources to include in the listing. /// The to monitor for cancellation requests. The default is . /// Slash commands available in the session, after applying any include/exclude filters. - public async Task ListAsync(CommandsListRequest? request = null, CancellationToken cancellationToken = default) + public async Task ListAsync(SessionCommandsListRequest? request = null, CancellationToken cancellationToken = default) { _session.ThrowIfDisposed(); - var rpcRequest = new CommandsListRequestWithSession { SessionId = _session.SessionId, IncludeBuiltins = request?.IncludeBuiltins, IncludeSkills = request?.IncludeSkills, IncludeClientCommands = request?.IncludeClientCommands }; + var rpcRequest = new SessionCommandsListRequestWithSession { SessionId = _session.SessionId, IncludeBuiltins = request?.IncludeBuiltins, IncludeSkills = request?.IncludeSkills, IncludeClientCommands = request?.IncludeClientCommands }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.commands.list", [rpcRequest], cancellationToken); } @@ -23149,16 +27217,14 @@ internal UiApi(CopilotSession session) /// Runs a transient no-tools model query against the current conversation context. /// Question to answer from the current conversation context. - /// In-process streaming callback `(text) => void` invoked with each token as the model emits it. Marked internal: excluded from the public SDK surface. In a process-separated SDK this is replaced by a streaming RPC that yields chunks and a final answer. - /// In-process `AbortSignal` forwarded to the model client to cancel an in-flight request. Marked internal: excluded from the public SDK surface. Replaced by an explicit cancellation token + cancel RPC in the SDK migration. /// The to monitor for cancellation requests. The default is . /// Transient answer generated from current conversation context. - public async Task EphemeralQueryAsync(string question, object? onChunk = null, object? abortSignal = null, CancellationToken cancellationToken = default) + public async Task EphemeralQueryAsync(string question, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(question); _session.ThrowIfDisposed(); - var request = new UIEphemeralQueryRequest { SessionId = _session.SessionId, Question = question, OnChunk = CopilotClient.ToJsonElementForWire(onChunk), AbortSignal = CopilotClient.ToJsonElementForWire(abortSignal) }; + var request = new UIEphemeralQueryRequest { SessionId = _session.SessionId, Question = question }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.ui.ephemeralQuery", [request], cancellationToken); } @@ -23321,15 +27387,16 @@ public async Task ConfigureAsync(bool? approveAllToo /// Provides a decision for a pending tool permission request. /// Request ID of the pending permission request. /// The client's response to the pending permission prompt. + /// Optional informational context describing how and where this response was made. Omit it to preserve legacy behavior without attributing an origin. /// The to monitor for cancellation requests. The default is . /// Indicates whether the permission decision was applied; false when the request was already resolved. - public async Task HandlePendingPermissionRequestAsync(string requestId, PermissionDecision result, CancellationToken cancellationToken = default) + public async Task HandlePendingPermissionRequestAsync(string requestId, PermissionDecision result, PermissionDecisionContext? decisionContext = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(requestId); ArgumentNullException.ThrowIfNull(result); _session.ThrowIfDisposed(); - var request = new PermissionDecisionRequest { SessionId = _session.SessionId, RequestId = requestId, Result = result }; + var request = new PermissionDecisionRequest { SessionId = _session.SessionId, RequestId = requestId, Result = result, DecisionContext = decisionContext }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.permissions.handlePendingPermissionRequest", [request], cancellationToken); } @@ -23360,7 +27427,7 @@ public async Task SetApproveAllAsync(bool enable /// Sets the allow-all permission mode for the session. Used by attach-mode clients (e.g. LocalRpcSession's `/allow-all` forwarder) to flip the target session's permission state. The `on` mode swaps in unrestricted path and URL managers and emits `session.permissions_changed` on transition; the `auto` mode keeps normal prompt paths active while attaching LLM safety recommendations. The result returns the authoritative post-mutation state so callers can update their local mirrors without racing the `session.permissions_changed` notification on the same wire. /// Allow-all mode to apply. `on` enables full allow-all; `auto` enables advisory LLM auto-approval; `off` disables both. /// Legacy full allow-all toggle. Prefer `mode`; when `mode` is omitted, `enabled: true` is treated as `mode: "on"` and any other value is treated as `mode: "off"`. - /// Optional model id for the `auto` mode auto-approval LLM judging. Only meaningful when `mode` is `auto`; ignored otherwise. When omitted, the session's active model is used. + /// Optional model id for the `auto` mode auto-approval LLM judging. Only meaningful when `mode` is `auto`; ignored otherwise. When omitted, the session resolves a default judge model: `gpt-5.5` for CAPI sessions and the session's active model for BYOK sessions. /// Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers. /// The to monitor for cancellation requests. The default is . /// Indicates whether the operation succeeded and reports the post-mutation state. @@ -23411,13 +27478,14 @@ public async Task SetRequiredAsync(bool required, } /// Clears session-scoped tool permission approvals. + /// Whether location-scoped approvals are cleared too. Defaults to `true`. /// The to monitor for cancellation requests. The default is . /// Indicates whether the operation succeeded. - public async Task ResetSessionApprovalsAsync(CancellationToken cancellationToken = default) + public async Task ResetSessionApprovalsAsync(bool? includeLocation = null, CancellationToken cancellationToken = default) { _session.ThrowIfDisposed(); - var request = new PermissionsResetSessionApprovalsRequest { SessionId = _session.SessionId }; + var request = new PermissionsResetSessionApprovalsRequest { SessionId = _session.SessionId, IncludeLocation = includeLocation }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.permissions.resetSessionApprovals", [request], cancellationToken); } @@ -23806,6 +27874,31 @@ internal async Task EvaluatePredicateAsy } } +/// Provides session-scoped ContentExclusion APIs. +[Experimental(Diagnostics.Experimental)] +public sealed class ContentExclusionApi +{ + private readonly CopilotSession _session; + + internal ContentExclusionApi(CopilotSession session) + { + _session = session; + } + + /// Checks local file system absolute paths within the session working directory against its content-exclusion policy. Results preserve input order. Unsupported paths/filesystems and unavailable policy evaluation return available false, and callers must treat every requested path as excluded. + /// Local file system absolute paths within the session working directory to check. Results are returned in the same order, including duplicates. + /// The to monitor for cancellation requests. The default is . + /// Batch content-exclusion result. Callers must fail closed when policy evaluation is unavailable. + public async Task CheckPathsAsync(IList paths, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(paths); + _session.ThrowIfDisposed(); + + var request = new ContentExclusionCheckPathsRequest { SessionId = _session.SessionId, Paths = paths }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.contentExclusion.checkPaths", [request], cancellationToken); + } +} + /// Provides session-scoped Shell APIs. [Experimental(Diagnostics.Experimental)] public sealed class ShellApi @@ -23817,7 +27910,7 @@ internal ShellApi(CopilotSession session) _session = session; } - /// Starts a shell command and streams output through session notifications. + /// Starts a shell command and streams output through session notifications. The command runs as the leader of its own process group (POSIX) or in a dedicated job object (Windows), so a forced termination — via "shell.kill", the request timeout, or session disposal — signals that whole group/job rather than only the direct child. Two gaps are worth planning for: a command that exits on its own does not trigger that teardown, and on POSIX a descendant that moves itself into a new session or process group (for example via "setsid") leaves the signalled group, so either can leave a background process running. /// Shell command to execute. /// Working directory (defaults to session working directory). /// Timeout in milliseconds (default: 30000). @@ -23832,7 +27925,7 @@ public async Task ExecAsync(string command, string? cwd = null, return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.shell.exec", [request], cancellationToken); } - /// Sends a signal to a shell process previously started via "shell.exec". + /// Sends a signal to a shell process previously started via "shell.exec". The signal targets the command's whole process group (POSIX) or job object (Windows), so descendants still in that group are signalled too, not just the direct child. On POSIX a descendant that moved itself into a new session or process group (for example via "setsid") is no longer in the signalled group and survives. /// Process identifier returned by shell.exec. /// Signal to send (default: SIGTERM). /// The to monitor for cancellation requests. The default is . @@ -23890,11 +27983,11 @@ internal HistoryApi(CopilotSession session) /// Optional compaction parameters. /// The to monitor for cancellation requests. The default is . /// Compaction outcome with the number of tokens and messages removed, summary text, and the resulting context window breakdown. - public async Task CompactAsync(HistoryCompactRequest? request = null, CancellationToken cancellationToken = default) + public async Task CompactAsync(SessionHistoryCompactRequest? request = null, CancellationToken cancellationToken = default) { _session.ThrowIfDisposed(); - var rpcRequest = new HistoryCompactRequestWithSession { SessionId = _session.SessionId, CustomInstructions = request?.CustomInstructions }; + var rpcRequest = new SessionHistoryCompactRequestWithSession { SessionId = _session.SessionId, CustomInstructions = request?.CustomInstructions, Trigger = request?.Trigger, TokenLimit = request?.TokenLimit }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.history.compact", [rpcRequest], cancellationToken); } @@ -23911,6 +28004,44 @@ public async Task TruncateAsync(string eventId, Cancellat return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.history.truncate", [request], cancellationToken); } + /// Lists the user turns that the session can rewind to. Never rejects for a busy session: rewind reads need the session's file-change captures to be settled, so a session that still holds active work answers with `unavailableReason: "session-busy"` and no points, which the caller can retry. + /// The to monitor for cancellation requests. The default is . + /// Rewind points and file-change-tracking availability for the session. + public async Task ListRewindPointsAsync(CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new SessionHistoryListRewindPointsRequest { SessionId = _session.SessionId }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.history.listRewindPoints", [request], cancellationToken); + } + + /// Previews the files that a conversation-and-files rewind would restore. + /// ID of the user.message event that begins the discarded suffix. + /// The to monitor for cancellation requests. The default is . + /// Files and aggregate changes for a prospective rewind. + public async Task PreviewRewindAsync(string eventId, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(eventId); + _session.ThrowIfDisposed(); + + var request = new HistoryPreviewRewindRequest { SessionId = _session.SessionId, EventId = eventId }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.history.previewRewind", [request], cancellationToken); + } + + /// Rewinds the session conversation, optionally restoring files changed by the discarded turns. Not crash-atomic: file restore and conversation truncation are separate stores, applied in that order, so a process crash between them can leave the workspace rewound while the conversation still contains the discarded turns. There is no recovery journal; re-running the same rewind is the recovery path for a crash before truncation lands, since file restore is idempotent (already-restored files are reported as skipped) and truncation is re-derived from the still-retained boundary event. After truncation lands that boundary no longer exists, so the same request is rejected; the only stage that can still be outstanding is snapshot pruning, whose failure leaves orphan snapshots the capture store tolerates. The reverse inconsistency cannot occur, because truncation is never applied before file restore succeeds. + /// ID of the user.message event that begins the discarded suffix. + /// Whether to rewind only conversation history or also restore captured files. + /// The to monitor for cancellation requests. The default is . + /// Structured outcome of a rewind request. + public async Task RewindAsync(string eventId, HistoryRewindMode mode, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(eventId); + _session.ThrowIfDisposed(); + + var request = new HistoryRewindRequest { SessionId = _session.SessionId, EventId = eventId, Mode = mode }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.history.rewind", [request], cancellationToken); + } + /// Cancels any in-progress background compaction on a local session. /// The to monitor for cancellation requests. The default is . /// Indicates whether an in-progress background compaction was cancelled. @@ -23943,6 +28074,19 @@ public async Task SummarizeForHandoffAsync(Can var request = new SessionHistorySummarizeForHandoffRequest { SessionId = _session.SessionId }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.history.summarizeForHandoff", [request], cancellationToken); } + + /// Clears the session's conversation history, keeping only system and developer messages, and seeds the fresh context window with a first user message. Must be called from inside a tool handler: the clear has to drop the results of the tool calls its wipe orphans, and it rejects when no tool call is in flight. + /// First user message of the fresh context window. Required: a cleared window holding only system and developer messages is not a conversation a model can answer, so every clear seeds the window it creates. Delivered by the enclosing turn driver once the agentic loop exits, which is why the call must be made from inside a tool handler. + /// The to monitor for cancellation requests. The default is . + /// What a successful clear removed. A clear that could not be applied rejects instead of reporting a count. + public async Task ClearContextAsync(string prompt, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(prompt); + _session.ThrowIfDisposed(); + + var request = new HistoryClearContextRequest { SessionId = _session.SessionId, Prompt = prompt }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.history.clearContext", [request], cancellationToken); + } } /// Provides session-scoped Queue APIs. @@ -23967,6 +28111,158 @@ public async Task PendingItemsAsync(CancellationToken c return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.queue.pendingItems", [request], cancellationToken); } + /// Returns the internal native queue snapshot for in-process session orchestration. + /// The to monitor for cancellation requests. The default is . + /// Internal snapshot of native queue state for local session orchestration. + internal async Task SnapshotAsync(CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new SessionQueueSnapshotRequest { SessionId = _session.SessionId }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.queue.snapshot", [request], cancellationToken); + } + + /// Moves an addressable queued item to a public visible position. + /// Stable opaque queued-item id. + /// Zero-based target position in the public visible queue. Values outside the queue clamp to an end. + /// The to monitor for cancellation requests. The default is . + /// Result of moving a queued item. + public async Task MoveItemAsync(string id, long toPosition, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(id); + _session.ThrowIfDisposed(); + + var request = new QueueMoveItemRequest { SessionId = _session.SessionId, Id = id, ToPosition = toPosition }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.queue.moveItem", [request], cancellationToken); + } + + /// Inserts a new queued message at a public visible position. + /// Zero-based position in the public visible queue. Values outside the queue clamp to an end. + /// The message parameter. + /// The to monitor for cancellation requests. The default is . + /// Result of inserting a queued message. + public async Task InsertAtAsync(long position, QueueInsertMessage message, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(message); + _session.ThrowIfDisposed(); + + var request = new QueueInsertAtRequest { SessionId = _session.SessionId, Position = position, Message = message }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.queue.insertAt", [request], cancellationToken); + } + + /// Removes an addressable queued item by its stable id. + /// The id parameter. + /// The to monitor for cancellation requests. The default is . + /// Result of removing a queued item. + public async Task RemoveAtAsync(string id, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(id); + _session.ThrowIfDisposed(); + + var request = new QueueRemoveAtRequest { SessionId = _session.SessionId, Id = id }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.queue.removeAt", [request], cancellationToken); + } + + /// Updates the text of an addressable single-message queue item. + /// The id parameter. + /// The prompt parameter. + /// The displayPrompt parameter. + /// The to monitor for cancellation requests. The default is . + /// Result of editing a queued message. + public async Task UpdateTextAsync(string id, string prompt, string? displayPrompt = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(id); + ArgumentNullException.ThrowIfNull(prompt); + _session.ThrowIfDisposed(); + + var request = new QueueUpdateTextRequest { SessionId = _session.SessionId, Id = id, Prompt = prompt, DisplayPrompt = displayPrompt }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.queue.updateText", [request], cancellationToken); + } + + /// Duplicates an addressable queued item immediately after its source. + /// The id parameter. + /// The to monitor for cancellation requests. The default is . + /// Result of duplicating a queued item. + public async Task DuplicateAtAsync(string id, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(id); + _session.ThrowIfDisposed(); + + var request = new QueueDuplicateAtRequest { SessionId = _session.SessionId, Id = id }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.queue.duplicateAt", [request], cancellationToken); + } + + /// Acquires or releases the queued-lane drain pause. + /// The paused parameter. + /// The to monitor for cancellation requests. The default is . + public async Task SetDrainPausedAsync(bool paused, CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new QueueSetDrainPausedRequest { SessionId = _session.SessionId, Paused = paused }; + await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.queue.setDrainPaused", [request], cancellationToken); + } + + /// Moves an addressable queued message into the live turn's steering lane. + /// The id parameter. + /// The to monitor for cancellation requests. The default is . + /// Result of trying to steer a queued message into a live turn. + public async Task SendNowAsync(string id, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(id); + _session.ThrowIfDisposed(); + + var request = new QueueSendNowRequest { SessionId = _session.SessionId, Id = id }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.queue.sendNow", [request], cancellationToken); + } + + /// Reports whether the local session has native queued work pending. + /// The to monitor for cancellation requests. The default is . + /// Whether the native queue has pending work. + internal async Task HasPendingAsync(CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new SessionQueueHasPendingRequest { SessionId = _session.SessionId }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.queue.hasPending", [request], cancellationToken); + } + + /// Begins a native deferred-idle drain when background work has quiesced. + /// Whether the host still has active background work. + /// The to monitor for cancellation requests. The default is . + /// Whether a deferred-idle drain should run. + internal async Task BeginDeferredIdleDrainAsync(bool activeBackgroundWork, CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new QueueBeginDeferredIdleDrainRequest { SessionId = _session.SessionId, ActiveBackgroundWork = activeBackgroundWork }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.queue.beginDeferredIdleDrain", [request], cancellationToken); + } + + /// Finishes a native deferred-idle drain and reports whether to drain queue work or emit idle. + /// Whether the host still has active background work. + /// Whether native queued work remains. + /// The to monitor for cancellation requests. The default is . + /// Action selected by the native deferred-idle drain. + internal async Task FinishDeferredIdleDrainAsync(bool activeBackgroundWork, bool hasPending, CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new QueueFinishDeferredIdleDrainRequest { SessionId = _session.SessionId, ActiveBackgroundWork = activeBackgroundWork, HasPending = hasPending }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.queue.finishDeferredIdleDrain", [request], cancellationToken); + } + + /// Marks session.idle as deferred by native background work state. + /// Whether the deferred idle was caused by an aborted foreground turn. + /// The to monitor for cancellation requests. The default is . + internal async Task DeferSessionIdleAsync(bool aborted, CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new QueueDeferSessionIdleRequest { SessionId = _session.SessionId, Aborted = aborted }; + await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.queue.deferSessionIdle", [request], cancellationToken); + } + /// Removes the most recently queued user-facing item (LIFO). /// The to monitor for cancellation requests. The default is . /// Indicates whether a user-facing pending item was removed. @@ -23987,6 +28283,40 @@ public async Task ClearAsync(CancellationToken cancellationToken = default) var request = new SessionQueueClearRequest { SessionId = _session.SessionId }; await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.queue.clear", [request], cancellationToken); } + + /// Consumes queued native system notifications matching an internal filter. + /// Opaque runtime-owned filter object. + /// The to monitor for cancellation requests. The default is . + /// Indicates whether a user-facing pending item was removed. + internal async Task ConsumeSystemNotificationsAsync(object filter, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(filter); + _session.ThrowIfDisposed(); + + var request = new QueueConsumeSystemNotificationsRequest { SessionId = _session.SessionId, Filter = CopilotClient.ToJsonElementForWire(filter)!.Value }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.queue.consumeSystemNotifications", [request], cancellationToken); + } + + /// Enqueues the internal resume-pending wake item when orphan handling needs a follow-up turn. + /// The to monitor for cancellation requests. The default is . + /// Result of enqueueing the resume-pending wake item. + internal async Task EnqueueResumePendingAsync(CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new SessionQueueEnqueueResumePendingRequest { SessionId = _session.SessionId }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.queue.enqueueResumePending", [request], cancellationToken); + } + + /// Drains the native local-session work queue for in-process session orchestration. + /// The to monitor for cancellation requests. The default is . + internal async Task ProcessAsync(CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new SessionQueueProcessRequest { SessionId = _session.SessionId }; + await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.queue.process", [request], cancellationToken); + } } /// Provides session-scoped EventLog APIs. @@ -24000,19 +28330,22 @@ internal EventLogApi(CopilotSession session) _session = session; } - /// Reads a batch of session events from a cursor, optionally waiting for new events. + /// Reads a batch of session events from a cursor, optionally waiting for new events. Supports tail-first reads via `direction: backward`. /// Opaque cursor returned by a previous read. Omit on the first call to start from the beginning of the session's persisted history. /// Maximum number of events to return in this batch (1–1000, default 200). - /// Milliseconds to wait for new events when the cursor is at the tail of history. 0 (default) returns immediately even if no events are available. Capped at 30000ms. Ephemeral events that arrive during the wait are delivered in this batch but are NOT replayable on a subsequent read (use a non-zero waitMs in your next call to capture future ephemerals as they happen). + /// Milliseconds to wait for new events when the cursor is at the tail of history. 0 (default) returns immediately even if no events are available. Capped at 30000ms. Ephemeral events that arrive during the wait are delivered in this batch but are NOT replayable on a subsequent read (use a non-zero waitMs in your next call to capture future ephemerals as they happen). This applies to forward reads only: a backward read always returns immediately and ignores `waitMs`, because backward paging covers persisted history only while new events append at the tail (the opposite end from a backward page), so no blocking or ephemeral delivery can occur. /// Either '*' to receive all event types, or a non-empty list of event types to receive. /// Agent-scope filter: 'primary' returns only main-agent events plus events whose type starts with 'subagent.' (matching the typed-subscription default behavior); 'all' returns events from all agents (matching wildcard-subscription behavior). Default is 'all' to preserve wildcard semantics for catch-up callers. + /// Optional non-empty list of subagent identifiers. When provided, only events owned by one of these agents are returned; ownership recognizes the event envelope's agentId plus legacy data.agentId and data.parentToolCallId markers. This filter takes precedence over agentScope. + /// Direction to page through the session's persisted event history. 'forward' (default) pages from the cursor toward newer events (or from the start of history when no cursor is given). 'backward' enables tail-first reads: with no cursor it returns the NEWEST `max` events, and the returned cursor pages toward OLDER events on subsequent backward reads. Events within a returned batch are always in chronological (oldest-to-newest) order, even for a backward read. Backward reads cover PERSISTED history only; ephemeral events are never returned by a backward read. `direction` selects the INITIAL read only: the returned cursor is self-describing, so a continuation read pages in the cursor's own direction regardless of the `direction` passed alongside it — a forward cursor always pages forward and a backward cursor always pages backward. Pass the direction that matches the cursor to avoid confusion. + /// When false, skip ephemeral events entirely and return only durable (persisted) events. History-backfill callers that discard ephemerals anyway should set this so the read is bounded by the durable log length instead of racing the ephemeral ring on a busy session. Defaults to true (ephemerals are interleaved with durable events in creation order). Ignored by backward reads, which always cover persisted history only. /// The to monitor for cancellation requests. The default is . /// Batch of session events returned by a read, with cursor and continuation metadata. - public async Task ReadAsync(string? cursor = null, long? max = null, TimeSpan? waitMs = null, object? types = null, EventsAgentScope? agentScope = null, CancellationToken cancellationToken = default) + public async Task ReadAsync(string? cursor = null, long? max = null, TimeSpan? waitMs = null, object? types = null, EventsAgentScope? agentScope = null, IList? agentIds = null, EventsReadDirection? direction = null, bool? includeEphemeral = null, CancellationToken cancellationToken = default) { _session.ThrowIfDisposed(); - var request = new EventLogReadRequest { SessionId = _session.SessionId, Cursor = cursor, Max = max, Wait = waitMs, Types = CopilotClient.ToJsonElementForWire(types), AgentScope = agentScope }; + var request = new EventLogReadRequest { SessionId = _session.SessionId, Cursor = cursor, Max = max, Wait = waitMs, Types = CopilotClient.ToJsonElementForWire(types), AgentScope = agentScope, AgentIds = agentIds, Direction = direction, IncludeEphemeral = includeEphemeral }; return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.eventLog.read", [request], cancellationToken); } @@ -24077,6 +28410,30 @@ public async Task GetMetricsAsync(CancellationToken cance } } +/// Provides session-scoped LimitPrediction APIs. +[Experimental(Diagnostics.Experimental)] +public sealed class LimitPredictionApi +{ + private readonly CopilotSession _session; + + internal LimitPredictionApi(CopilotSession session) + { + _session = session; + } + + /// Predicts an AI-credit session limit for the session's resolved model. Returns an unavailable result instead of falling back when the current model is unresolved auto. + /// Parameters for predicting an AI-credit session limit. Omitting `modelId` uses the session's currently selected model. + /// The to monitor for cancellation requests. The default is . + /// Prediction result. Available results include prediction details; unavailable results include an explicit reason. + public async Task PredictAsync(SessionLimitPredictionPredictRequest? request = null, CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var rpcRequest = new SessionLimitPredictionPredictRequestWithSession { SessionId = _session.SessionId, ModelId = request?.ModelId, ClientType = request?.ClientType }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.limitPrediction.predict", [rpcRequest], cancellationToken); + } +} + /// Provides session-scoped Remote APIs. [Experimental(Diagnostics.Experimental)] public sealed class RemoteApi @@ -24180,6 +28537,105 @@ public async Task ListAsync(CancellationToken cancellationToken = return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.schedule.list", [request], cancellationToken); } + /// Hydrates the native schedule registry from persisted session events. + /// The to monitor for cancellation requests. The default is . + internal async Task HydrateAsync(CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new SessionScheduleHydrateRequest { SessionId = _session.SessionId }; + await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.schedule.hydrate", [request], cancellationToken); + } + + /// Reports whether the session has an active self-paced scheduled prompt. + /// The to monitor for cancellation requests. The default is . + /// Whether the session currently has an active self-paced schedule. + internal async Task HasSelfPacedAsync(CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new SessionScheduleHasSelfPacedRequest { SessionId = _session.SessionId }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.schedule.hasSelfPaced", [request], cancellationToken); + } + + /// Registers a relative-interval scheduled prompt. + /// Human-readable interval such as `30s`, `5m`, or `2h`. + /// Prompt text to enqueue when the schedule fires. + /// Whether the schedule should re-arm after each tick. Defaults to true. + /// Optional display-only prompt label. + /// The to monitor for cancellation requests. The default is . + /// Result of registering or re-arming a scheduled prompt. + internal async Task AddAsync(string interval, string prompt, bool? recurring = null, string? displayPrompt = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(interval); + ArgumentNullException.ThrowIfNull(prompt); + _session.ThrowIfDisposed(); + + var request = new ScheduleAddRequest { SessionId = _session.SessionId, Interval = interval, Prompt = prompt, Recurring = recurring, DisplayPrompt = displayPrompt }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.schedule.add", [request], cancellationToken); + } + + /// Registers a recurring cron scheduled prompt. + /// 5-field cron expression. + /// Prompt text to enqueue when the schedule fires. + /// Whether the schedule should re-arm after each tick. Defaults to true. + /// Optional display-only prompt label. + /// IANA timezone for evaluating the cron expression. + /// The to monitor for cancellation requests. The default is . + /// Result of registering or re-arming a scheduled prompt. + internal async Task AddCronAsync(string cron, string prompt, bool? recurring = null, string? displayPrompt = null, string? tz = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(cron); + ArgumentNullException.ThrowIfNull(prompt); + _session.ThrowIfDisposed(); + + var request = new ScheduleAddCronRequest { SessionId = _session.SessionId, Cron = cron, Prompt = prompt, Recurring = recurring, DisplayPrompt = displayPrompt, Tz = tz }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.schedule.addCron", [request], cancellationToken); + } + + /// Registers an absolute-time scheduled prompt. + /// Epoch milliseconds when the prompt should fire. + /// Prompt text to enqueue when the schedule fires. + /// Whether the schedule should re-arm after each tick. Defaults to false. + /// Optional display-only prompt label. + /// The to monitor for cancellation requests. The default is . + /// Result of registering or re-arming a scheduled prompt. + internal async Task AddAtAsync(long at, string prompt, bool? recurring = null, string? displayPrompt = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(prompt); + _session.ThrowIfDisposed(); + + var request = new ScheduleAddAtRequest { SessionId = _session.SessionId, At = at, Prompt = prompt, Recurring = recurring, DisplayPrompt = displayPrompt }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.schedule.addAt", [request], cancellationToken); + } + + /// Registers a self-paced scheduled prompt. + /// Prompt text to enqueue when the schedule fires. + /// Optional display-only prompt label. + /// The to monitor for cancellation requests. The default is . + /// Result of registering or re-arming a scheduled prompt. + internal async Task AddSelfPacedAsync(string prompt, string? displayPrompt = null, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(prompt); + _session.ThrowIfDisposed(); + + var request = new ScheduleAddSelfPacedRequest { SessionId = _session.SessionId, Prompt = prompt, DisplayPrompt = displayPrompt }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.schedule.addSelfPaced", [request], cancellationToken); + } + + /// Re-arms an active self-paced scheduled prompt. + /// Id of the self-paced scheduled prompt. + /// Epoch milliseconds when the prompt should next fire. + /// The to monitor for cancellation requests. The default is . + /// Result of registering or re-arming a scheduled prompt. + internal async Task RearmSelfPacedAsync(long id, long at, CancellationToken cancellationToken = default) + { + _session.ThrowIfDisposed(); + + var request = new ScheduleRearmSelfPacedRequest { SessionId = _session.SessionId, Id = id, At = at }; + return await CopilotClient.InvokeRpcAsync(_session.Rpc, "session.schedule.rearmSelfPaced", [request], cancellationToken); + } + /// Removes a scheduled prompt by id. /// Id of the scheduled prompt to remove. /// The to monitor for cancellation requests. The default is . @@ -24274,11 +28730,16 @@ public interface ISessionFsHandler /// The to monitor for cancellation requests. The default is . /// Describes a filesystem error. Task RenameAsync(SessionFsRenameRequest request, CancellationToken cancellationToken = default); - /// Executes a SQLite query against the per-session database. - /// SQL query, query type, and optional bind parameters for executing a SQLite query against the per-session database. + /// Executes a SQLite query against the per-session database. Providers apply busy handling for every call. + /// SQL query, query type, and optional bind parameters for executing a SQLite query against the per-session database. The provider applies its SQLite busy timeout for every call. /// The to monitor for cancellation requests. The default is . /// Query results including rows, columns, and rows affected, or a filesystem error if execution failed. Task SqliteQueryAsync(SessionFsSqliteQueryRequest request, CancellationToken cancellationToken = default); + /// Executes SQLite statements atomically on the provider-owned connection. + /// Statements to execute atomically. Providers apply busy handling for every call. + /// The to monitor for cancellation requests. The default is . + /// Per-statement results, or a classified transaction error. + Task SqliteTransactionAsync(SessionFsSqliteTransactionRequest request, CancellationToken cancellationToken = default); /// Checks whether the per-session SQLite database already exists, without creating it. /// Identifies the target session. /// The to monitor for cancellation requests. The default is . @@ -24416,6 +28877,12 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, Func>)(async (request, cancellationToken) => + { + var handler = getHandlers(request.SessionId).SessionFs; + if (handler is null) throw new InvalidOperationException($"No sessionFs handler registered for session: {request.SessionId}"); + return await handler.SqliteTransactionAsync(request, cancellationToken); + }), singleObjectParam: true); rpc.SetLocalRpcMethod("sessionFs.sqliteExists", (Func>)(async (request, cancellationToken) => { var handler = getHandlers(request.SessionId).SessionFs; @@ -24443,6 +28910,17 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, FuncHandles `extensionLaunchProvider` client global API methods. +[Experimental(Diagnostics.Experimental)] +public interface IExtensionLaunchProviderHandler +{ + /// Asks the registered SDK client to resolve an opaque process launch profile for one discovered extension entrypoint immediately before launch or reload. The provider must respond within 15 seconds. + /// A discovered extension entrypoint that the registered integrator may classify and resolve to an opaque launch profile. + /// The to monitor for cancellation requests. The default is . + /// The launch profile for a supported entrypoint. Omit launch when the provider does not support the entrypoint. + Task ResolveAsync(ExtensionLaunchProviderResolveRequest request, CancellationToken cancellationToken = default); +} + /// Handles `llmInference` client global API methods. [Experimental(Diagnostics.Experimental)] public interface ILlmInferenceHandler @@ -24472,6 +28950,9 @@ public interface IGitHubTelemetryHandler /// Provides all client global API handler groups for a connection. public sealed class ClientGlobalApiHandlers { + /// Optional handler for ExtensionLaunchProvider client global API methods. + public IExtensionLaunchProviderHandler? ExtensionLaunchProvider { get; set; } + /// Optional handler for LlmInference client global API methods. public ILlmInferenceHandler? LlmInference { get; set; } @@ -24490,6 +28971,11 @@ internal static class ClientGlobalApiRegistration /// public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiHandlers handlers) { + rpc.SetLocalRpcMethod("extensionLaunchProvider.resolve", (Func>)(async (request, cancellationToken) => + { + var handler = handlers.ExtensionLaunchProvider ?? throw new InvalidOperationException("No extensionLaunchProvider client-global handler registered"); + return await handler.ResolveAsync(request, cancellationToken); + }), singleObjectParam: true); rpc.SetLocalRpcMethod("llmInference.httpRequestStart", (Func>)(async (request, cancellationToken) => { var handler = handlers.LlmInference ?? throw new InvalidOperationException("No llmInference client-global handler registered"); @@ -24577,6 +29063,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.AttachmentSelectionDetails), TypeInfoPropertyName = "SessionEventsAttachmentSelectionDetails")] [JsonSerializable(typeof(GitHub.Copilot.AttachmentSelectionDetailsEnd), TypeInfoPropertyName = "SessionEventsAttachmentSelectionDetailsEnd")] [JsonSerializable(typeof(GitHub.Copilot.AttachmentSelectionDetailsStart), TypeInfoPropertyName = "SessionEventsAttachmentSelectionDetailsStart")] +[JsonSerializable(typeof(GitHub.Copilot.AutoApprovalJudgeFailureReason), TypeInfoPropertyName = "SessionEventsAutoApprovalJudgeFailureReason")] [JsonSerializable(typeof(GitHub.Copilot.AutoApprovalRecommendation), TypeInfoPropertyName = "SessionEventsAutoApprovalRecommendation")] [JsonSerializable(typeof(GitHub.Copilot.AutoModeResolvedReasoningBucket), TypeInfoPropertyName = "SessionEventsAutoModeResolvedReasoningBucket")] [JsonSerializable(typeof(GitHub.Copilot.AutoModeSwitchCompletedData), TypeInfoPropertyName = "SessionEventsAutoModeSwitchCompletedData")] @@ -24615,6 +29102,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.CommandsChangedEvent), TypeInfoPropertyName = "SessionEventsCommandsChangedEvent")] [JsonSerializable(typeof(GitHub.Copilot.CompactionCompleteCompactionTokensUsed), TypeInfoPropertyName = "SessionEventsCompactionCompleteCompactionTokensUsed")] [JsonSerializable(typeof(GitHub.Copilot.CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail), TypeInfoPropertyName = "SessionEventsCompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail")] +[JsonSerializable(typeof(GitHub.Copilot.CompactionTrigger), TypeInfoPropertyName = "SessionEventsCompactionTrigger")] [JsonSerializable(typeof(GitHub.Copilot.ContextTier), TypeInfoPropertyName = "SessionEventsContextTier")] [JsonSerializable(typeof(GitHub.Copilot.CustomAgentsUpdatedAgent), TypeInfoPropertyName = "SessionEventsCustomAgentsUpdatedAgent")] [JsonSerializable(typeof(GitHub.Copilot.ElicitationCompletedAction), TypeInfoPropertyName = "SessionEventsElicitationCompletedAction")] @@ -24638,6 +29126,11 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.ExternalToolCompletedEvent), TypeInfoPropertyName = "SessionEventsExternalToolCompletedEvent")] [JsonSerializable(typeof(GitHub.Copilot.ExternalToolRequestedData), TypeInfoPropertyName = "SessionEventsExternalToolRequestedData")] [JsonSerializable(typeof(GitHub.Copilot.ExternalToolRequestedEvent), TypeInfoPropertyName = "SessionEventsExternalToolRequestedEvent")] +[JsonSerializable(typeof(GitHub.Copilot.FactoryPermissionOperation), TypeInfoPropertyName = "SessionEventsFactoryPermissionOperation")] +[JsonSerializable(typeof(GitHub.Copilot.FactoryPermissionPhase), TypeInfoPropertyName = "SessionEventsFactoryPermissionPhase")] +[JsonSerializable(typeof(GitHub.Copilot.FactoryRunUpdatedData), TypeInfoPropertyName = "SessionEventsFactoryRunUpdatedData")] +[JsonSerializable(typeof(GitHub.Copilot.FactoryRunUpdatedEvent), TypeInfoPropertyName = "SessionEventsFactoryRunUpdatedEvent")] +[JsonSerializable(typeof(GitHub.Copilot.GitHubMcpToolConfig), TypeInfoPropertyName = "SessionEventsGitHubMcpToolConfig")] [JsonSerializable(typeof(GitHub.Copilot.GitHubRepoRef), TypeInfoPropertyName = "SessionEventsGitHubRepoRef")] [JsonSerializable(typeof(GitHub.Copilot.HandoffRepository), TypeInfoPropertyName = "SessionEventsHandoffRepository")] [JsonSerializable(typeof(GitHub.Copilot.HandoffSourceType), TypeInfoPropertyName = "SessionEventsHandoffSourceType")] @@ -24702,6 +29195,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.PermissionPromptRequestCustomTool), TypeInfoPropertyName = "SessionEventsPermissionPromptRequestCustomTool")] [JsonSerializable(typeof(GitHub.Copilot.PermissionPromptRequestExtensionManagement), TypeInfoPropertyName = "SessionEventsPermissionPromptRequestExtensionManagement")] [JsonSerializable(typeof(GitHub.Copilot.PermissionPromptRequestExtensionPermissionAccess), TypeInfoPropertyName = "SessionEventsPermissionPromptRequestExtensionPermissionAccess")] +[JsonSerializable(typeof(GitHub.Copilot.PermissionPromptRequestFactory), TypeInfoPropertyName = "SessionEventsPermissionPromptRequestFactory")] [JsonSerializable(typeof(GitHub.Copilot.PermissionPromptRequestHook), TypeInfoPropertyName = "SessionEventsPermissionPromptRequestHook")] [JsonSerializable(typeof(GitHub.Copilot.PermissionPromptRequestMcp), TypeInfoPropertyName = "SessionEventsPermissionPromptRequestMcp")] [JsonSerializable(typeof(GitHub.Copilot.PermissionPromptRequestMemory), TypeInfoPropertyName = "SessionEventsPermissionPromptRequestMemory")] @@ -24714,6 +29208,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.PermissionRequestCustomTool), TypeInfoPropertyName = "SessionEventsPermissionRequestCustomTool")] [JsonSerializable(typeof(GitHub.Copilot.PermissionRequestExtensionManagement), TypeInfoPropertyName = "SessionEventsPermissionRequestExtensionManagement")] [JsonSerializable(typeof(GitHub.Copilot.PermissionRequestExtensionPermissionAccess), TypeInfoPropertyName = "SessionEventsPermissionRequestExtensionPermissionAccess")] +[JsonSerializable(typeof(GitHub.Copilot.PermissionRequestFactory), TypeInfoPropertyName = "SessionEventsPermissionRequestFactory")] [JsonSerializable(typeof(GitHub.Copilot.PermissionRequestHook), TypeInfoPropertyName = "SessionEventsPermissionRequestHook")] [JsonSerializable(typeof(GitHub.Copilot.PermissionRequestMcp), TypeInfoPropertyName = "SessionEventsPermissionRequestMcp")] [JsonSerializable(typeof(GitHub.Copilot.PermissionRequestMemory), TypeInfoPropertyName = "SessionEventsPermissionRequestMemory")] @@ -24722,6 +29217,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.PermissionRequestRead), TypeInfoPropertyName = "SessionEventsPermissionRequestRead")] [JsonSerializable(typeof(GitHub.Copilot.PermissionRequestShell), TypeInfoPropertyName = "SessionEventsPermissionRequestShell")] [JsonSerializable(typeof(GitHub.Copilot.PermissionRequestShellCommand), TypeInfoPropertyName = "SessionEventsPermissionRequestShellCommand")] +[JsonSerializable(typeof(GitHub.Copilot.PermissionRequestShellCommandSegment), TypeInfoPropertyName = "SessionEventsPermissionRequestShellCommandSegment")] [JsonSerializable(typeof(GitHub.Copilot.PermissionRequestShellPossibleUrl), TypeInfoPropertyName = "SessionEventsPermissionRequestShellPossibleUrl")] [JsonSerializable(typeof(GitHub.Copilot.PermissionRequestUrl), TypeInfoPropertyName = "SessionEventsPermissionRequestUrl")] [JsonSerializable(typeof(GitHub.Copilot.PermissionRequestWrite), TypeInfoPropertyName = "SessionEventsPermissionRequestWrite")] @@ -24738,6 +29234,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.SamplingCompletedEvent), TypeInfoPropertyName = "SessionEventsSamplingCompletedEvent")] [JsonSerializable(typeof(GitHub.Copilot.SamplingRequestedData), TypeInfoPropertyName = "SessionEventsSamplingRequestedData")] [JsonSerializable(typeof(GitHub.Copilot.SamplingRequestedEvent), TypeInfoPropertyName = "SessionEventsSamplingRequestedEvent")] +[JsonSerializable(typeof(GitHub.Copilot.ScheduleOrigin), TypeInfoPropertyName = "SessionEventsScheduleOrigin")] [JsonSerializable(typeof(GitHub.Copilot.SessionEvent), TypeInfoPropertyName = "SessionEventsSessionEvent")] [JsonSerializable(typeof(GitHub.Copilot.SessionLimitsConfig), TypeInfoPropertyName = "SessionEventsSessionLimitsConfig")] [JsonSerializable(typeof(GitHub.Copilot.SessionLimitsExhaustedCompletedData), TypeInfoPropertyName = "SessionEventsSessionLimitsExhaustedCompletedData")] @@ -24779,10 +29276,14 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.SystemNotificationAgentIdle), TypeInfoPropertyName = "SessionEventsSystemNotificationAgentIdle")] [JsonSerializable(typeof(GitHub.Copilot.SystemNotificationData), TypeInfoPropertyName = "SessionEventsSystemNotificationData")] [JsonSerializable(typeof(GitHub.Copilot.SystemNotificationEvent), TypeInfoPropertyName = "SessionEventsSystemNotificationEvent")] +[JsonSerializable(typeof(GitHub.Copilot.SystemNotificationFactoryCompleted), TypeInfoPropertyName = "SessionEventsSystemNotificationFactoryCompleted")] +[JsonSerializable(typeof(GitHub.Copilot.SystemNotificationFactoryCompletedStatus), TypeInfoPropertyName = "SessionEventsSystemNotificationFactoryCompletedStatus")] [JsonSerializable(typeof(GitHub.Copilot.SystemNotificationInstructionDiscovered), TypeInfoPropertyName = "SessionEventsSystemNotificationInstructionDiscovered")] [JsonSerializable(typeof(GitHub.Copilot.SystemNotificationNewInboxMessage), TypeInfoPropertyName = "SessionEventsSystemNotificationNewInboxMessage")] [JsonSerializable(typeof(GitHub.Copilot.SystemNotificationShellCompleted), TypeInfoPropertyName = "SessionEventsSystemNotificationShellCompleted")] [JsonSerializable(typeof(GitHub.Copilot.SystemNotificationShellDetachedCompleted), TypeInfoPropertyName = "SessionEventsSystemNotificationShellDetachedCompleted")] +[JsonSerializable(typeof(GitHub.Copilot.SystemNotificationUnclassified), TypeInfoPropertyName = "SessionEventsSystemNotificationUnclassified")] +[JsonSerializable(typeof(GitHub.Copilot.TaskCompletionOutcome), TypeInfoPropertyName = "SessionEventsTaskCompletionOutcome")] [JsonSerializable(typeof(GitHub.Copilot.ToolExecutionCompleteContent), TypeInfoPropertyName = "SessionEventsToolExecutionCompleteContent")] [JsonSerializable(typeof(GitHub.Copilot.ToolExecutionCompleteContentAudio), TypeInfoPropertyName = "SessionEventsToolExecutionCompleteContentAudio")] [JsonSerializable(typeof(GitHub.Copilot.ToolExecutionCompleteContentImage), TypeInfoPropertyName = "SessionEventsToolExecutionCompleteContentImage")] @@ -24838,6 +29339,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(GitHub.Copilot.UserToolSessionApprovalCustomTool), TypeInfoPropertyName = "SessionEventsUserToolSessionApprovalCustomTool")] [JsonSerializable(typeof(GitHub.Copilot.UserToolSessionApprovalExtensionManagement), TypeInfoPropertyName = "SessionEventsUserToolSessionApprovalExtensionManagement")] [JsonSerializable(typeof(GitHub.Copilot.UserToolSessionApprovalExtensionPermissionAccess), TypeInfoPropertyName = "SessionEventsUserToolSessionApprovalExtensionPermissionAccess")] +[JsonSerializable(typeof(GitHub.Copilot.UserToolSessionApprovalFactory), TypeInfoPropertyName = "SessionEventsUserToolSessionApprovalFactory")] [JsonSerializable(typeof(GitHub.Copilot.UserToolSessionApprovalMcp), TypeInfoPropertyName = "SessionEventsUserToolSessionApprovalMcp")] [JsonSerializable(typeof(GitHub.Copilot.UserToolSessionApprovalMemory), TypeInfoPropertyName = "SessionEventsUserToolSessionApprovalMemory")] [JsonSerializable(typeof(GitHub.Copilot.UserToolSessionApprovalRead), TypeInfoPropertyName = "SessionEventsUserToolSessionApprovalRead")] @@ -24869,11 +29371,14 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(AgentReloadResult))] [JsonSerializable(typeof(AgentSelectRequest))] [JsonSerializable(typeof(AgentSelectResult))] +[JsonSerializable(typeof(AgentSetPromptRequest))] [JsonSerializable(typeof(AgentsDiscoverRequest))] [JsonSerializable(typeof(AgentsGetDiscoveryPathsRequest))] [JsonSerializable(typeof(AllowAllPermissionSetResult))] [JsonSerializable(typeof(AllowAllPermissionState))] [JsonSerializable(typeof(AuthInfo))] +[JsonSerializable(typeof(BuiltInModelCatalog))] +[JsonSerializable(typeof(BuiltInModelCatalogEntry))] [JsonSerializable(typeof(CancelUserRequestedShellCommandResult))] [JsonSerializable(typeof(CanvasAction))] [JsonSerializable(typeof(CanvasActionInvokeRequest))] @@ -24894,8 +29399,6 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(CommandsHandlePendingCommandRequest))] [JsonSerializable(typeof(CommandsHandlePendingCommandResult))] [JsonSerializable(typeof(CommandsInvokeRequest))] -[JsonSerializable(typeof(CommandsListRequest))] -[JsonSerializable(typeof(CommandsListRequestWithSession))] [JsonSerializable(typeof(CommandsRespondToQueuedCommandRequest))] [JsonSerializable(typeof(CommandsRespondToQueuedCommandResult))] [JsonSerializable(typeof(CompletionsGetTriggerCharactersResult))] @@ -24907,6 +29410,9 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(ConnectResult))] [JsonSerializable(typeof(ConnectedRemoteSessionMetadata))] [JsonSerializable(typeof(ConnectedRemoteSessionMetadataRepository))] +[JsonSerializable(typeof(ContentExclusionCheckPathsRequest))] +[JsonSerializable(typeof(ContentExclusionCheckPathsResult))] +[JsonSerializable(typeof(ContentExclusionPathCheck))] [JsonSerializable(typeof(ContextHeaviestMessage))] [JsonSerializable(typeof(CopilotUserResponse))] [JsonSerializable(typeof(CopilotUserResponseEndpoints))] @@ -24925,6 +29431,11 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(DebugCollectLogsResult))] [JsonSerializable(typeof(DebugCollectLogsSkippedEntry))] [JsonSerializable(typeof(DiscoveredCanvas))] +[JsonSerializable(typeof(DiscoveredExtension))] +[JsonSerializable(typeof(DiscoveredExtensionPlugin))] +[JsonSerializable(typeof(DiscoveredExtensions))] +[JsonSerializable(typeof(DiscoveredExtensionsDisableRequest))] +[JsonSerializable(typeof(DiscoveredExtensionsEnableRequest))] [JsonSerializable(typeof(DiscoveredMcpServer))] [JsonSerializable(typeof(EnqueueCommandParams))] [JsonSerializable(typeof(EnqueueCommandResult))] @@ -24935,6 +29446,9 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(ExecuteCommandParams))] [JsonSerializable(typeof(ExecuteCommandResult))] [JsonSerializable(typeof(Extension))] +[JsonSerializable(typeof(ExtensionLaunchProfile))] +[JsonSerializable(typeof(ExtensionLaunchProviderResolveRequest))] +[JsonSerializable(typeof(ExtensionLaunchProviderResolveResult))] [JsonSerializable(typeof(ExtensionList))] [JsonSerializable(typeof(ExtensionsDisableRequest))] [JsonSerializable(typeof(ExtensionsEnableRequest))] @@ -24943,19 +29457,34 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(FactoryAgentOptions))] [JsonSerializable(typeof(FactoryAgentRequest))] [JsonSerializable(typeof(FactoryAgentResult))] +[JsonSerializable(typeof(FactoryAgentSummary))] [JsonSerializable(typeof(FactoryCancelRequest))] +[JsonSerializable(typeof(FactoryCurrentPhase))] +[JsonSerializable(typeof(FactoryDeclaredLimits))] [JsonSerializable(typeof(FactoryExecuteRequest))] [JsonSerializable(typeof(FactoryExecuteResult))] +[JsonSerializable(typeof(FactoryGetRunProgressRequest))] [JsonSerializable(typeof(FactoryGetRunRequest))] [JsonSerializable(typeof(FactoryJournalGetRequest))] [JsonSerializable(typeof(FactoryJournalGetResult))] [JsonSerializable(typeof(FactoryJournalPutRequest))] +[JsonSerializable(typeof(FactoryListRunsRequest))] +[JsonSerializable(typeof(FactoryListRunsResult))] [JsonSerializable(typeof(FactoryLogLine))] [JsonSerializable(typeof(FactoryLogRequest))] +[JsonSerializable(typeof(FactoryPhaseObservation))] +[JsonSerializable(typeof(FactoryProgressLine))] +[JsonSerializable(typeof(FactoryProgressPage))] +[JsonSerializable(typeof(FactoryResumeRequest))] +[JsonSerializable(typeof(FactoryResumeResult))] +[JsonSerializable(typeof(FactoryRunConsumed))] +[JsonSerializable(typeof(FactoryRunDetail))] [JsonSerializable(typeof(FactoryRunFailure))] [JsonSerializable(typeof(FactoryRunLimits))] [JsonSerializable(typeof(FactoryRunRequest))] [JsonSerializable(typeof(FactoryRunResult))] +[JsonSerializable(typeof(FactoryRunSummary))] +[JsonSerializable(typeof(FactoryRunTerminal))] [JsonSerializable(typeof(FleetStartRequest))] [JsonSerializable(typeof(FleetStartResult))] [JsonSerializable(typeof(FolderTrustAddParams))] @@ -24968,10 +29497,18 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(HandlePendingToolCallResult))] [JsonSerializable(typeof(HistoryAbortManualCompactionResult))] [JsonSerializable(typeof(HistoryCancelBackgroundCompactionResult))] +[JsonSerializable(typeof(HistoryClearContextRequest))] +[JsonSerializable(typeof(HistoryClearContextResult))] [JsonSerializable(typeof(HistoryCompactContextWindow))] -[JsonSerializable(typeof(HistoryCompactRequest))] -[JsonSerializable(typeof(HistoryCompactRequestWithSession))] [JsonSerializable(typeof(HistoryCompactResult))] +[JsonSerializable(typeof(HistoryListRewindPointsResult))] +[JsonSerializable(typeof(HistoryPreviewRewindRequest))] +[JsonSerializable(typeof(HistoryPreviewRewindResult))] +[JsonSerializable(typeof(HistoryRewindFilePreview))] +[JsonSerializable(typeof(HistoryRewindPoint))] +[JsonSerializable(typeof(HistoryRewindRequest))] +[JsonSerializable(typeof(HistoryRewindResult))] +[JsonSerializable(typeof(HistorySkippedFileRestore))] [JsonSerializable(typeof(HistorySummarizeForHandoffResult))] [JsonSerializable(typeof(HistoryTruncateRequest))] [JsonSerializable(typeof(HistoryTruncateResult))] @@ -24985,6 +29522,8 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(InstructionsDiscoverRequest))] [JsonSerializable(typeof(InstructionsGetDiscoveryPathsRequest))] [JsonSerializable(typeof(InstructionsGetSourcesResult))] +[JsonSerializable(typeof(InterruptMainTurnRequest))] +[JsonSerializable(typeof(InterruptMainTurnResult))] [JsonSerializable(typeof(LlmInferenceHttpRequestChunkRequest))] [JsonSerializable(typeof(LlmInferenceHttpRequestChunkResult))] [JsonSerializable(typeof(LlmInferenceHttpRequestStartRequest))] @@ -24999,6 +29538,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(LogRequest))] [JsonSerializable(typeof(LogResult))] [JsonSerializable(typeof(LspInitializeRequest))] +[JsonSerializable(typeof(ManagedSettingsReadResult))] [JsonSerializable(typeof(MarketplaceAddResult))] [JsonSerializable(typeof(MarketplaceBrowseResult))] [JsonSerializable(typeof(MarketplaceInfo))] @@ -25048,11 +29588,14 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(McpIsServerRunningResult))] [JsonSerializable(typeof(McpListToolsRequest))] [JsonSerializable(typeof(McpListToolsResult))] +[JsonSerializable(typeof(McpOauthAuthenticationStateChangedRequest))] [JsonSerializable(typeof(McpOauthHandlePendingRequest))] [JsonSerializable(typeof(McpOauthHandlePendingResult))] [JsonSerializable(typeof(McpOauthLoginRequest))] [JsonSerializable(typeof(McpOauthLoginResult))] [JsonSerializable(typeof(McpOauthPendingRequestResponse))] +[JsonSerializable(typeof(McpOauthRespondRequest))] +[JsonSerializable(typeof(McpOauthRespondResult))] [JsonSerializable(typeof(McpRegisterExternalClientRequest))] [JsonSerializable(typeof(McpReloadWithConfigRequest))] [JsonSerializable(typeof(McpRemoveGitHubResult))] @@ -25083,6 +29626,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(McpUnregisterExternalClientRequest))] [JsonSerializable(typeof(MetadataContextAttributionResult))] [JsonSerializable(typeof(MetadataContextAttributionResultContextAttribution))] +[JsonSerializable(typeof(MetadataContextAttributionResultContextAttributionCategories))] [JsonSerializable(typeof(MetadataContextAttributionResultContextAttributionCompactions))] [JsonSerializable(typeof(MetadataContextAttributionResultContextAttributionEntry))] [JsonSerializable(typeof(MetadataContextHeaviestMessagesRequest))] @@ -25114,8 +29658,6 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(ModelCapabilitiesOverrideSupports))] [JsonSerializable(typeof(ModelCapabilitiesSupports))] [JsonSerializable(typeof(ModelList))] -[JsonSerializable(typeof(ModelListRequest))] -[JsonSerializable(typeof(ModelListRequestWithSession))] [JsonSerializable(typeof(ModelPolicy))] [JsonSerializable(typeof(ModelSetReasoningEffortRequest))] [JsonSerializable(typeof(ModelSetReasoningEffortResult))] @@ -25136,6 +29678,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(PermissionDecision))] [JsonSerializable(typeof(PermissionDecisionApproveForLocationApproval))] [JsonSerializable(typeof(PermissionDecisionApproveForSessionApproval))] +[JsonSerializable(typeof(PermissionDecisionContext))] [JsonSerializable(typeof(PermissionDecisionRequest))] [JsonSerializable(typeof(PermissionLocationAddToolApprovalParams))] [JsonSerializable(typeof(PermissionLocationApplyParams))] @@ -25201,8 +29744,6 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(PluginsMarketplacesBrowseRequest))] [JsonSerializable(typeof(PluginsMarketplacesRefreshRequest))] [JsonSerializable(typeof(PluginsMarketplacesRemoveRequest))] -[JsonSerializable(typeof(PluginsReloadRequest))] -[JsonSerializable(typeof(PluginsReloadRequestWithSession))] [JsonSerializable(typeof(PluginsUninstallRequest))] [JsonSerializable(typeof(PluginsUpdateRequest))] [JsonSerializable(typeof(ProviderAddRequest))] @@ -25210,8 +29751,6 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(ProviderConfig))] [JsonSerializable(typeof(ProviderConfigAzure))] [JsonSerializable(typeof(ProviderEndpoint))] -[JsonSerializable(typeof(ProviderGetEndpointRequest))] -[JsonSerializable(typeof(ProviderGetEndpointRequestWithSession))] [JsonSerializable(typeof(ProviderModelConfig))] [JsonSerializable(typeof(ProviderSessionToken))] [JsonSerializable(typeof(ProviderTokenAcquireRequest))] @@ -25224,9 +29763,32 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(PushAttachmentSelectionDetailsEnd))] [JsonSerializable(typeof(PushAttachmentSelectionDetailsStart))] [JsonSerializable(typeof(PushGitHubRepoRef))] +[JsonSerializable(typeof(QueueBeginDeferredIdleDrainRequest))] +[JsonSerializable(typeof(QueueBeginDeferredIdleDrainResult))] +[JsonSerializable(typeof(QueueConsumeSystemNotificationsRequest))] +[JsonSerializable(typeof(QueueDeferSessionIdleRequest))] +[JsonSerializable(typeof(QueueDuplicateAtRequest))] +[JsonSerializable(typeof(QueueDuplicateAtResult))] +[JsonSerializable(typeof(QueueEnqueueResumePendingResult))] +[JsonSerializable(typeof(QueueFinishDeferredIdleDrainRequest))] +[JsonSerializable(typeof(QueueFinishDeferredIdleDrainResult))] +[JsonSerializable(typeof(QueueHasPendingResult))] +[JsonSerializable(typeof(QueueInsertAtRequest))] +[JsonSerializable(typeof(QueueInsertAtResult))] +[JsonSerializable(typeof(QueueInsertMessage))] +[JsonSerializable(typeof(QueueMoveItemRequest))] +[JsonSerializable(typeof(QueueMoveItemResult))] [JsonSerializable(typeof(QueuePendingItems))] [JsonSerializable(typeof(QueuePendingItemsResult))] +[JsonSerializable(typeof(QueueRemoveAtRequest))] +[JsonSerializable(typeof(QueueRemoveAtResult))] [JsonSerializable(typeof(QueueRemoveMostRecentResult))] +[JsonSerializable(typeof(QueueSendNowRequest))] +[JsonSerializable(typeof(QueueSendNowResult))] +[JsonSerializable(typeof(QueueSetDrainPausedRequest))] +[JsonSerializable(typeof(QueueSnapshotResult))] +[JsonSerializable(typeof(QueueUpdateTextRequest))] +[JsonSerializable(typeof(QueueUpdateTextResult))] [JsonSerializable(typeof(QueuedCommandResult))] [JsonSerializable(typeof(RegisterEventInterestParams))] [JsonSerializable(typeof(RegisterEventInterestResult))] @@ -25248,14 +29810,23 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(RemoteSessionMetadataValue))] [JsonSerializable(typeof(RunOptions))] [JsonSerializable(typeof(SandboxConfig))] +[JsonSerializable(typeof(SandboxConfigAuth))] [JsonSerializable(typeof(SandboxConfigUserPolicy))] [JsonSerializable(typeof(SandboxConfigUserPolicyExperimental))] [JsonSerializable(typeof(SandboxConfigUserPolicyExperimentalSeatbelt))] [JsonSerializable(typeof(SandboxConfigUserPolicyFilesystem))] [JsonSerializable(typeof(SandboxConfigUserPolicyNetwork))] +[JsonSerializable(typeof(SandboxConfigUserPolicyNetworkProxy))] [JsonSerializable(typeof(SandboxConfigUserPolicySeatbelt))] +[JsonSerializable(typeof(ScheduleAddAtRequest))] +[JsonSerializable(typeof(ScheduleAddCronRequest))] +[JsonSerializable(typeof(ScheduleAddRequest))] +[JsonSerializable(typeof(ScheduleAddResult))] +[JsonSerializable(typeof(ScheduleAddSelfPacedRequest))] [JsonSerializable(typeof(ScheduleEntry))] +[JsonSerializable(typeof(ScheduleHasSelfPacedResult))] [JsonSerializable(typeof(ScheduleList))] +[JsonSerializable(typeof(ScheduleRearmSelfPacedRequest))] [JsonSerializable(typeof(ScheduleStopRequest))] [JsonSerializable(typeof(ScheduleStopResult))] [JsonSerializable(typeof(SecretsAddFilterValuesRequest))] @@ -25266,6 +29837,7 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(SendMessagesResult))] [JsonSerializable(typeof(SendRequest))] [JsonSerializable(typeof(SendResult))] +[JsonSerializable(typeof(SendSystemNotificationRequest))] [JsonSerializable(typeof(ServerAgentList))] [JsonSerializable(typeof(ServerInstructionSourceList))] [JsonSerializable(typeof(ServerSkill))] @@ -25274,11 +29846,15 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(SessionAgentDeselectRequest))] [JsonSerializable(typeof(SessionAgentGetCurrentRequest))] [JsonSerializable(typeof(SessionAgentListRequest))] +[JsonSerializable(typeof(SessionAgentListRequestWithSession))] [JsonSerializable(typeof(SessionAgentReloadRequest))] [JsonSerializable(typeof(SessionAuthStatus))] [JsonSerializable(typeof(SessionBulkDeleteResult))] +[JsonSerializable(typeof(SessionCancelAllBackgroundAgentsRequest))] [JsonSerializable(typeof(SessionCanvasListOpenRequest))] [JsonSerializable(typeof(SessionCanvasListRequest))] +[JsonSerializable(typeof(SessionCommandsListRequest))] +[JsonSerializable(typeof(SessionCommandsListRequestWithSession))] [JsonSerializable(typeof(SessionCompletionItem))] [JsonSerializable(typeof(SessionCompletionsGetTriggerCharactersRequest))] [JsonSerializable(typeof(SessionContext))] @@ -25307,15 +29883,28 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(SessionFsSqliteExistsResult))] [JsonSerializable(typeof(SessionFsSqliteQueryRequest))] [JsonSerializable(typeof(SessionFsSqliteQueryResult))] +[JsonSerializable(typeof(SessionFsSqliteTransactionError))] +[JsonSerializable(typeof(SessionFsSqliteTransactionRequest))] +[JsonSerializable(typeof(SessionFsSqliteTransactionResult))] +[JsonSerializable(typeof(SessionFsSqliteTransactionStatement))] [JsonSerializable(typeof(SessionFsStatRequest))] [JsonSerializable(typeof(SessionFsStatResult))] [JsonSerializable(typeof(SessionFsWriteFileRequest))] [JsonSerializable(typeof(SessionGitHubAuthGetStatusRequest))] [JsonSerializable(typeof(SessionHistoryAbortManualCompactionRequest))] [JsonSerializable(typeof(SessionHistoryCancelBackgroundCompactionRequest))] +[JsonSerializable(typeof(SessionHistoryCompactRequest))] +[JsonSerializable(typeof(SessionHistoryCompactRequestWithSession))] +[JsonSerializable(typeof(SessionHistoryListRewindPointsRequest))] [JsonSerializable(typeof(SessionHistorySummarizeForHandoffRequest))] [JsonSerializable(typeof(SessionInstalledPlugin))] [JsonSerializable(typeof(SessionInstructionsGetSourcesRequest))] +[JsonSerializable(typeof(SessionLimitPredictionBaselineData))] +[JsonSerializable(typeof(SessionLimitPredictionDetails))] +[JsonSerializable(typeof(SessionLimitPredictionPredictRequest))] +[JsonSerializable(typeof(SessionLimitPredictionPredictRequestWithSession))] +[JsonSerializable(typeof(SessionLimitPredictionResult))] +[JsonSerializable(typeof(SessionLimitPredictionTierOption))] [JsonSerializable(typeof(SessionList))] [JsonSerializable(typeof(SessionListEntry))] [JsonSerializable(typeof(SessionListFilter))] @@ -25333,6 +29922,8 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(SessionModeGetRequest))] [JsonSerializable(typeof(SessionModelGetCurrentRequest))] [JsonSerializable(typeof(SessionModelList))] +[JsonSerializable(typeof(SessionModelListRequest))] +[JsonSerializable(typeof(SessionModelListRequestWithSession))] [JsonSerializable(typeof(SessionModelPriceCategory))] [JsonSerializable(typeof(SessionNameGetRequest))] [JsonSerializable(typeof(SessionOpenResult))] @@ -25341,11 +29932,21 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(SessionPlanReadSqlTodosRequest))] [JsonSerializable(typeof(SessionPlanReadSqlTodosWithDependenciesRequest))] [JsonSerializable(typeof(SessionPluginsListRequest))] +[JsonSerializable(typeof(SessionPluginsReloadRequest))] +[JsonSerializable(typeof(SessionPluginsReloadRequestWithSession))] +[JsonSerializable(typeof(SessionProviderGetEndpointRequest))] +[JsonSerializable(typeof(SessionProviderGetEndpointRequestWithSession))] [JsonSerializable(typeof(SessionPruneResult))] [JsonSerializable(typeof(SessionQueueClearRequest))] +[JsonSerializable(typeof(SessionQueueEnqueueResumePendingRequest))] +[JsonSerializable(typeof(SessionQueueHasPendingRequest))] [JsonSerializable(typeof(SessionQueuePendingItemsRequest))] +[JsonSerializable(typeof(SessionQueueProcessRequest))] [JsonSerializable(typeof(SessionQueueRemoveMostRecentRequest))] +[JsonSerializable(typeof(SessionQueueSnapshotRequest))] [JsonSerializable(typeof(SessionRemoteDisableRequest))] +[JsonSerializable(typeof(SessionScheduleHasSelfPacedRequest))] +[JsonSerializable(typeof(SessionScheduleHydrateRequest))] [JsonSerializable(typeof(SessionScheduleListRequest))] [JsonSerializable(typeof(SessionSetCredentialsParams))] [JsonSerializable(typeof(SessionSetCredentialsResult))] @@ -25380,14 +29981,18 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(SessionUsageGetMetricsRequest))] [JsonSerializable(typeof(SessionVisibilityGetRequest))] [JsonSerializable(typeof(SessionWorkingDirectoryContext))] +[JsonSerializable(typeof(SessionWorkspacesAutopilotObjectiveExistsRequest))] +[JsonSerializable(typeof(SessionWorkspacesDeleteAutopilotObjectiveRequest))] [JsonSerializable(typeof(SessionWorkspacesGetWorkspaceRequest))] [JsonSerializable(typeof(SessionWorkspacesListCheckpointsRequest))] [JsonSerializable(typeof(SessionWorkspacesListFilesRequest))] +[JsonSerializable(typeof(SessionWorkspacesReadAutopilotObjectiveRequest))] [JsonSerializable(typeof(SessionsBulkDeleteRequest))] [JsonSerializable(typeof(SessionsCheckInUseRequest))] [JsonSerializable(typeof(SessionsCheckInUseResult))] [JsonSerializable(typeof(SessionsCloseRequest))] [JsonSerializable(typeof(SessionsCloseResult))] +[JsonSerializable(typeof(SessionsDeleteRequest))] [JsonSerializable(typeof(SessionsEnrichMetadataRequest))] [JsonSerializable(typeof(SessionsFindByPrefixRequest))] [JsonSerializable(typeof(SessionsFindByPrefixResult))] @@ -25401,8 +30006,12 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(SessionsGetEventFilePathResult))] [JsonSerializable(typeof(SessionsGetLastForContextRequest))] [JsonSerializable(typeof(SessionsGetLastForContextResult))] +[JsonSerializable(typeof(SessionsGetMetadataRequest))] +[JsonSerializable(typeof(SessionsGetMetadataResult))] [JsonSerializable(typeof(SessionsGetPersistedRemoteSteerableRequest))] [JsonSerializable(typeof(SessionsGetPersistedRemoteSteerableResult))] +[JsonSerializable(typeof(SessionsListNonEmptySessionIdsRequest))] +[JsonSerializable(typeof(SessionsListNonEmptySessionIdsResult))] [JsonSerializable(typeof(SessionsListRequest))] [JsonSerializable(typeof(SessionsLoadDeferredRepoHooksRequest))] [JsonSerializable(typeof(SessionsOpenProgress))] @@ -25424,8 +30033,10 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(ShellExecRequest))] [JsonSerializable(typeof(ShellExecResult))] [JsonSerializable(typeof(ShellExecuteUserRequestedRequest))] +[JsonSerializable(typeof(ShellInitScript))] [JsonSerializable(typeof(ShellKillRequest))] [JsonSerializable(typeof(ShellKillResult))] +[JsonSerializable(typeof(ShellOptions))] [JsonSerializable(typeof(ShutdownRequest))] [JsonSerializable(typeof(Skill))] [JsonSerializable(typeof(SkillDiscoveryPath))] @@ -25511,13 +30122,21 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(VisibilitySetResult))] [JsonSerializable(typeof(WorkspaceDiffFileChange))] [JsonSerializable(typeof(WorkspaceDiffResult))] +[JsonSerializable(typeof(WorkspacesAddSummaryRequest))] +[JsonSerializable(typeof(WorkspacesAddSummaryResult))] +[JsonSerializable(typeof(WorkspacesAddSummaryResultSummary))] +[JsonSerializable(typeof(WorkspacesAddSummaryResultWorkspace))] +[JsonSerializable(typeof(WorkspacesAutopilotObjectiveExistsResult))] [JsonSerializable(typeof(WorkspacesCheckpoints))] [JsonSerializable(typeof(WorkspacesCreateFileRequest))] +[JsonSerializable(typeof(WorkspacesDeleteAutopilotObjectiveResult))] [JsonSerializable(typeof(WorkspacesDiffRequest))] +[JsonSerializable(typeof(WorkspacesEnsureRequest))] [JsonSerializable(typeof(WorkspacesGetWorkspaceResult))] [JsonSerializable(typeof(WorkspacesGetWorkspaceResultWorkspace))] [JsonSerializable(typeof(WorkspacesListCheckpointsResult))] [JsonSerializable(typeof(WorkspacesListFilesResult))] +[JsonSerializable(typeof(WorkspacesReadAutopilotObjectiveResult))] [JsonSerializable(typeof(WorkspacesReadCheckpointRequest))] [JsonSerializable(typeof(WorkspacesReadCheckpointResult))] [JsonSerializable(typeof(WorkspacesReadFileRequest))] @@ -25525,4 +30144,8 @@ public static void RegisterClientGlobalApiHandlers(JsonRpc rpc, ClientGlobalApiH [JsonSerializable(typeof(WorkspacesSaveLargePasteRequest))] [JsonSerializable(typeof(WorkspacesSaveLargePasteResult))] [JsonSerializable(typeof(WorkspacesSaveLargePasteResultSaved))] +[JsonSerializable(typeof(WorkspacesTruncateSummariesRequest))] +[JsonSerializable(typeof(WorkspacesUpdateMetadataRequest))] +[JsonSerializable(typeof(WorkspacesWriteAutopilotObjectiveRequest))] +[JsonSerializable(typeof(WorkspacesWriteAutopilotObjectiveResult))] internal partial class RpcJsonContext : JsonSerializerContext; \ No newline at end of file diff --git a/dotnet/src/Generated/SessionEvents.cs b/dotnet/src/Generated/SessionEvents.cs index a8c70df7c..90eb08263 100644 --- a/dotnet/src/Generated/SessionEvents.cs +++ b/dotnet/src/Generated/SessionEvents.cs @@ -52,6 +52,7 @@ namespace GitHub.Copilot; [JsonDerivedType(typeof(ExitPlanModeRequestedEvent), "exit_plan_mode.requested")] [JsonDerivedType(typeof(ExternalToolCompletedEvent), "external_tool.completed")] [JsonDerivedType(typeof(ExternalToolRequestedEvent), "external_tool.requested")] +[JsonDerivedType(typeof(FactoryRunUpdatedEvent), "factory.run_updated")] [JsonDerivedType(typeof(HookEndEvent), "hook.end")] [JsonDerivedType(typeof(HookProgressEvent), "hook.progress")] [JsonDerivedType(typeof(HookStartEvent), "hook.start")] @@ -85,6 +86,7 @@ namespace GitHub.Copilot; [JsonDerivedType(typeof(SessionCompactionCompleteEvent), "session.compaction_complete")] [JsonDerivedType(typeof(SessionCompactionStartEvent), "session.compaction_start")] [JsonDerivedType(typeof(SessionContextChangedEvent), "session.context_changed")] +[JsonDerivedType(typeof(SessionContextClearedEvent), "session.context_cleared")] [JsonDerivedType(typeof(SessionCustomAgentsUpdatedEvent), "session.custom_agents_updated")] [JsonDerivedType(typeof(SessionCustomNotificationEvent), "session.custom_notification")] [JsonDerivedType(typeof(SessionErrorEvent), "session.error")] @@ -517,6 +519,19 @@ public sealed partial class SessionUsageInfoEvent : SessionEvent public required SessionUsageInfoData Data { get; set; } } +/// Context-cleared details emitted when the host clears the conversation (the session.history.clearContext RPC / Session.clearContextMessages). +/// Represents the session.context_cleared event. +public sealed partial class SessionContextClearedEvent : SessionEvent +{ + /// + [JsonIgnore] + public override string Type => "session.context_cleared"; + + /// The session.context_cleared event payload. + [JsonPropertyName("data")] + public required SessionContextClearedData Data { get; set; } +} + /// Context window breakdown at the start of LLM-powered conversation compaction. /// Represents the session.compaction_start event. public sealed partial class SessionCompactionStartEvent : SessionEvent @@ -1338,7 +1353,7 @@ public sealed partial class SessionAutoModeResolvedEvent : SessionEvent public required SessionAutoModeResolvedData Data { get; set; } } -/// Enterprise managed-settings resolution: the effective managed settings the session applied and where they came from, so SDK clients can show users what is enterprise-managed and by which authority. Fires whenever managed policy is (re)applied — at session start, on resume, and on account switch. This is an ephemeral live snapshot (delivered to subscribers but not persisted to the session event log), because at session start it resolves before `session.start` is emitted; for a session-independent pull, use the SDK `getManagedSettings()` API, which returns the identical payload. Managed settings have a single authoritative source, so the highest-authority present layer (server > device) wins wholesale; `bypassPermissionsDisabled` is deny-wins across layers. Marked experimental while the managed-settings surface stabilizes. +/// Enterprise managed-settings resolution: the effective managed settings the session applied and which channels contributed, so SDK clients can show users what is enterprise-managed. Fires whenever managed policy is (re)applied — at session start, on resume, and on account switch. This is an ephemeral live snapshot (delivered to subscribers but not persisted to the session event log), because at session start it resolves before `session.start` is emitted. Device values take precedence over server values per ordinary key, while permissions compose restrictively across device, server, and SDK-client layers. The account-scoped `getManagedSettings()` API does not include session-local client injection. Marked experimental while the managed-settings surface stabilizes. /// Represents the session.managed_settings_resolved event. [Experimental(Diagnostics.Experimental)] public sealed partial class SessionManagedSettingsResolvedEvent : SessionEvent @@ -1444,6 +1459,20 @@ public sealed partial class SessionBackgroundTasksChangedEvent : SessionEvent public required SessionBackgroundTasksChangedData Data { get; set; } } +/// Ephemeral invalidation signal for a changed factory run. +/// Represents the factory.run_updated event. +[Experimental(Diagnostics.Experimental)] +public sealed partial class FactoryRunUpdatedEvent : SessionEvent +{ + /// + [JsonIgnore] + public override string Type => "factory.run_updated"; + + /// The factory.run_updated event payload. + [JsonPropertyName("data")] + public required FactoryRunUpdatedData Data { get; set; } +} + /// Payload of `session.skills_loaded` listing resolved skill metadata. /// Represents the session.skills_loaded event. public sealed partial class SessionSkillsLoadedEvent : SessionEvent @@ -1685,6 +1714,11 @@ public sealed partial class SessionStartData [JsonPropertyName("detachedFromSpawningParentSessionId")] public string? DetachedFromSpawningParentSessionId { get; set; } + /// Per-session GitHub MCP override persisted for cold resume. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("githubMcpToolConfig")] + public GitHubMcpToolConfig? GitHubMcpToolConfig { get; set; } + /// Identifier of the software producing the events (e.g., "copilot-agent"). [JsonPropertyName("producer")] public required string Producer { get; set; } @@ -1750,7 +1784,7 @@ public sealed partial class SessionResumeData [JsonPropertyName("contextTier")] public ContextTier? ContextTier { get; set; } - /// When true, tool calls and permission requests left in flight by the previous session lifetime remain pending after resume and the agentic loop awaits their results. User sends are queued behind the pending work until all such requests reach a terminal state. When false (the default), any such tool calls and permission requests are immediately marked as interrupted on resume. + /// When true, tool calls and permission requests left in flight by the previous session lifetime remain pending after resume and the agentic loop awaits their results. User sends are queued behind the pending work until all such requests reach a terminal state. When false or omitted, pending work is normally marked as interrupted unless the resume passively joined live work owned by another client; sessionWasActive distinguishes that case. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("continuePendingWork")] public bool? ContinuePendingWork { get; set; } @@ -1793,7 +1827,7 @@ public sealed partial class SessionResumeData [JsonPropertyName("sessionLimits")] public SessionLimitsConfig? SessionLimits { get; set; } - /// True when this resume attached to a session that the runtime already had running in-memory (for example, an extension joining a session another client was actively driving). False (or omitted) for cold resumes — the runtime had to reconstitute the session from its persisted event log. + /// True when this resume passively joined a session that already had live work running in the runtime - an agent turn, a native queue run, a queued resume continuation, or an in-flight send (for example, an extension joining a session another client was actively driving). False (or omitted) when the session had no live work or when the resume explicitly abandoned pending work, including cold resumes and suspended sessions that remain resident in memory. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("sessionWasActive")] public bool? SessionWasActive { get; set; } @@ -1904,6 +1938,11 @@ public sealed partial class SessionScheduleCreatedData [JsonPropertyName("intervalMs")] public TimeSpan? Interval { get; set; } + /// Who created the schedule (`user` or `model`). Persisted so a resumed session keeps gating non-user schedules from firing skills that opted out of model invocation. Absent on entries created before this field existed; a missing origin fails closed (treated the same as a non-user origin), so such a schedule may not resolve a `disable-model-invocation` skill. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("origin")] + public ScheduleOrigin? Origin { get; set; } + /// Prompt text that gets enqueued on every tick. [JsonPropertyName("prompt")] public required string Prompt { get; set; } @@ -2004,7 +2043,7 @@ public sealed partial class SessionWarningData /// Model change details including previous and new model identifiers. public sealed partial class SessionModelChangeData { - /// Reason the change happened, when not user-initiated. Currently `"rate_limit_auto_switch"` for changes triggered by the auto-mode-switch rate-limit recovery path. UI clients can use this to render contextual copy. + /// Reason the change happened, when not user-initiated. `"rate_limit_auto_switch"` for changes triggered by the auto-mode-switch rate-limit recovery path, or `"refusal_fallback"` when the active model declined a request (content refusal) and the runtime switched to the configured refusal-fallback model. UI clients can use this to render contextual copy. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("cause")] public string? Cause { get; set; } @@ -2337,6 +2376,11 @@ public sealed partial class SessionContextChangedData [JsonPropertyName("hostType")] public WorkingDirectoryContextHostType? HostType { get; set; } + /// Set on the immediate preliminary event of a working-directory change, before the git context is resolved. A settled follow-up event (enriched with git context, or cwd-only for a non-repository) is always emitted afterward, so observers may defer to it. Absent on standalone/final events (e.g. relay context changes). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("pendingGitContext")] + public bool? PendingGitContext { get; set; } + /// Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("repository")] @@ -2384,6 +2428,19 @@ public sealed partial class SessionUsageInfoData public long? ToolDefinitionsTokens { get; set; } } +/// Context-cleared details emitted when the host clears the conversation (the session.history.clearContext RPC / Session.clearContextMessages). +public sealed partial class SessionContextClearedData +{ + /// Optional initial message set after clearing. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("initialMessage")] + public string? InitialMessage { get; set; } + + /// Number of conversation messages that were cleared. + [JsonPropertyName("messagesCleared")] + public required long MessagesCleared { get; set; } +} + /// Context window breakdown at the start of LLM-powered conversation compaction. public sealed partial class SessionCompactionStartData { @@ -2392,6 +2449,11 @@ public sealed partial class SessionCompactionStartData [JsonPropertyName("conversationTokens")] public long? ConversationTokens { get; set; } + /// Total context tokens (system + conversation + tool definitions) at compaction start, when known. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("currentTokens")] + public long? CurrentTokens { get; set; } + /// Model identifier used for compaction, when known. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("model")] @@ -2402,10 +2464,20 @@ public sealed partial class SessionCompactionStartData [JsonPropertyName("systemTokens")] public long? SystemTokens { get; set; } + /// Model context window token limit the compaction is targeting, when known. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("tokenLimit")] + public long? TokenLimit { get; set; } + /// Token count from tool definitions at compaction start. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("toolDefinitionsTokens")] public long? ToolDefinitionsTokens { get; set; } + + /// What initiated this compaction, when known. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("trigger")] + public CompactionTrigger? Trigger { get; set; } } /// Conversation compaction results including success status, metrics, and optional error details. @@ -2490,6 +2562,11 @@ public sealed partial class SessionCompactionCompleteData [JsonPropertyName("systemTokens")] public long? SystemTokens { get; set; } + /// Model context window token limit the compaction was targeting, when known. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("tokenLimit")] + public long? TokenLimit { get; set; } + /// Number of tokens removed during compaction. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("tokensRemoved")] @@ -2499,12 +2576,32 @@ public sealed partial class SessionCompactionCompleteData [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("toolDefinitionsTokens")] public long? ToolDefinitionsTokens { get; set; } + + /// What initiated this compaction, when known. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("trigger")] + public CompactionTrigger? Trigger { get; set; } } /// Task completion notification with summary from the agent. public sealed partial class SessionTaskCompleteData { - /// Whether the tool call succeeded. False when validation failed (e.g., invalid arguments). + /// Active autopilot objective ID evaluated by the completion reviewer. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("objectiveId")] + public long? ObjectiveId { get; set; } + + /// Semantic completion decision. Absent on legacy events and invalid tool calls. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("outcome")] + public TaskCompletionOutcome? Outcome { get; set; } + + /// Label-safe runtime rationale for the completion decision (e.g. a cancellation or pause/resume downgrade), when one applies. Reviewer-authored rationale is intentionally omitted here because this event has no IFC label channel; the reviewer's findings remain available through its own labeled sub-agent events. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("reason")] + public string? Reason { get; set; } + + /// Whether the task was accepted as complete. False when validation failed or completion was rejected or blocked by the reviewer. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("success")] public bool? Success { get; set; } @@ -2557,7 +2654,7 @@ public sealed partial class UserMessageData [JsonPropertyName("parentAgentTaskId")] public string? ParentAgentTaskId { get; set; } - /// Origin of this message, used for timeline filtering (e.g., "skill-pdf" for skill-injected messages that should be hidden from the user). + /// Origin of this message, used for timeline filtering and attribution (e.g., `skill-pdf` for hidden skill injection or `agent-<agent-id>` for an inter-agent prompt). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("source")] public string? Source { get; set; } @@ -2648,6 +2745,11 @@ public sealed partial class AssistantReasoningData /// Unique identifier for this reasoning block. [JsonPropertyName("reasoningId")] public required string ReasoningId { get; set; } + + /// Gets or sets the rte value. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("rte")] + public bool? Rte { get; set; } } /// Streaming reasoning delta for incremental extended thinking updates. @@ -2700,6 +2802,16 @@ public sealed partial class AssistantMessageData [JsonPropertyName("apiCallId")] public string? ApiCallId { get; set; } + /// Total messages the model call's response was split into, one per reasoning boundary. Absent for a single-message response; the last chunk is the one where chunkIndex is chunkCount - 1. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("chunkCount")] + public long? ChunkCount { get; set; } + + /// Zero-based position of this message within its model call's response. Absent when the response was not split into chunks. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("chunkIndex")] + public long? ChunkIndex { get; set; } + /// Provider-agnostic citations linking spans of this message's content to the sources that support them. Experimental; only populated when citation emission is enabled. [Experimental(Diagnostics.Experimental)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -2773,6 +2885,11 @@ public sealed partial class AssistantMessageData [JsonPropertyName("requestId")] public string? RequestId { get; set; } + /// Gets or sets the rte value. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("rte")] + public bool? Rte { get; set; } + /// Neutral provider-tagged server-side tool-use payload (tool search, advisor) for verbatim round-tripping. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("serverTools")] @@ -2863,6 +2980,12 @@ public sealed partial class AssistantUsageData [JsonPropertyName("apiEndpoint")] public AssistantUsageApiEndpoint? ApiEndpoint { get; set; } + /// Number of tools available to the model for this call. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonInclude] + [JsonPropertyName("availableToolCount")] + internal long? AvailableToolCount { get; set; } + /// Updated prompt-cache expiration for this model call. Present only when the call establishes or refreshes known cache state. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("cacheExpiresAt")] @@ -2915,6 +3038,11 @@ public sealed partial class AssistantUsageData [JsonPropertyName("inputTokens")] public long? InputTokens { get; set; } + /// Coarse classification of the interaction that produced this call, mirroring the session's per-request agent context (e.g. `conversation-agent`, `conversation-subagent`, `conversation-sampling`, `conversation-background`, `conversation-compaction`, `conversation-user`). Non-billing; lets consumers attribute a model call to a call class (e.g. sub-agent/sidekick) independently of the billing initiator. Absent when the runtime did not classify the request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("interactionType")] + public string? InteractionType { get; set; } + /// Average inter-token latency in milliseconds. Only available for streaming requests. [JsonConverter(typeof(MillisecondsTimeSpanConverter))] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -2925,6 +3053,12 @@ public sealed partial class AssistantUsageData [JsonPropertyName("model")] public required string Model { get; set; } + /// Number of tool calls returned by the model. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonInclude] + [JsonPropertyName("numToolCalls")] + internal long? NumToolCalls { get; set; } + /// Number of output tokens produced. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("outputTokens")] @@ -2960,6 +3094,11 @@ public sealed partial class AssistantUsageData [JsonPropertyName("reasoningTokens")] public long? ReasoningTokens { get; set; } + /// Gets or sets the rte value. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("rte")] + public bool? Rte { get; set; } + /// Copilot service request ID (x-copilot-service-request-id header) for CAPI log correlation. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("serviceRequestId")] @@ -2970,6 +3109,18 @@ public sealed partial class AssistantUsageData [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("timeToFirstTokenMs")] public TimeSpan? TimeToFirstToken { get; set; } + + /// Tool-call counts keyed by tool name. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonInclude] + [JsonPropertyName("toolCounts")] + internal IDictionary? ToolCounts { get; set; } + + /// Number of tokens used by tool definitions for this call. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonInclude] + [JsonPropertyName("toolTokenCount")] + internal long? ToolTokenCount { get; set; } } /// Failed LLM API call metadata for telemetry. @@ -3067,6 +3218,11 @@ public sealed partial class ModelCallFailureData [JsonPropertyName("requestFingerprint")] public ModelCallFailureRequestFingerprint? RequestFingerprint { get; set; } + /// Gets or sets the rte value. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("rte")] + public bool? Rte { get; set; } + /// Copilot service request ID (x-copilot-service-request-id header) for CAPI log correlation. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("serviceRequestId")] @@ -3095,6 +3251,12 @@ public sealed partial class ModelCallStartData [JsonPropertyName("model")] public string? Model { get; set; } + /// Previous response or interaction identifier included in the model request, when present. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonInclude] + [JsonPropertyName("previousResponseId")] + internal string? PreviousResponseId { get; set; } + /// Identifier of the assistant turn that initiated the model call. [JsonPropertyName("turnId")] public required string TurnId { get; set; } @@ -3162,6 +3324,11 @@ public sealed partial class ToolExecutionStartData [JsonPropertyName("parentToolCallId")] public string? ParentToolCallId { get; set; } + /// Gets or sets the rte value. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("rte")] + public bool? Rte { get; set; } + /// Shell-tool path hints derived from the command at start time for shell tools (bash/powershell/local_shell). Produced by the same shell-aware extractor as PermissionRequestShell.possiblePaths, so it is present even when the command is auto-approved and no permission request fires. Absent for non-shell tools. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("shellToolInfo")] @@ -3253,6 +3420,11 @@ public sealed partial class ToolExecutionCompleteData [JsonPropertyName("result")] public ToolExecutionCompleteResult? Result { get; set; } + /// Gets or sets the rte value. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("rte")] + public bool? Rte { get; set; } + /// Whether this tool execution ran inside a sandbox container. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("sandboxed")] @@ -3381,6 +3553,11 @@ public sealed partial class SubagentCompletedData [JsonPropertyName("agentName")] public required string AgentName { get; set; } + /// Whether the sub-agent was torn down by cancellation - its own abort, or an ancestor being killed - instead of finishing its work. Cancellation is not a failure, so the run still reports completion; this distinguishes a torn-down sub-agent from one that ran to the end. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("cancelled")] + public bool? Cancelled { get; set; } + /// Wall-clock duration of the sub-agent execution in milliseconds. [JsonConverter(typeof(MillisecondsTimeSpanConverter))] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -3567,6 +3744,11 @@ public sealed partial class SystemMessageData [JsonPropertyName("content")] public required string Content { get; set; } + /// Logical interaction identifier for the model run receiving this prompt. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("interactionId")] + public string? InteractionId { get; set; } + /// Metadata about the prompt template and its construction. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("metadata")] @@ -3614,6 +3796,11 @@ public sealed partial class PermissionRequestedData [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("resolvedByHook")] public bool? ResolvedByHook { get; set; } + + /// Neutral risk metadata supplied by the tool host. Consumers may display this value but must not use it to bypass the permission decision. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("riskAssessment")] + public JsonElement? RiskAssessment { get; set; } } /// Permission request completion notification signaling UI dismissal. @@ -4021,6 +4208,11 @@ public sealed partial class SessionLimitsExhaustedCompletedData [Experimental(Diagnostics.Experimental)] public sealed partial class SessionAutoModeResolvedData { + /// Models offered to the router for this resolution. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("availableModels")] + public string[]? AvailableModels { get; set; } + /// Ordered candidate model list the router returned, when not a fallback. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("candidateModels")] @@ -4035,11 +4227,36 @@ public sealed partial class SessionAutoModeResolvedData [JsonPropertyName("chosenModel")] public required string ChosenModel { get; set; } + /// The chosen model's score shortfall relative to the top candidate. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("chosenShortfall")] + public double? ChosenShortfall { get; set; } + /// Classifier confidence for the predicted label, when available. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("confidence")] public double? Confidence { get; set; } + /// End-to-end client wait time for the router request in milliseconds. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("endToEndLatencyMs")] + public double? EndToEndLatencyMs { get; set; } + + /// Whether the router fell back to the standard Auto selection. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("fallback")] + public bool? Fallback { get; set; } + + /// Server-provided reason for falling back, when available. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("fallbackReason")] + public string? FallbackReason { get; set; } + + /// Whether the routed prompt contained an image. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("hasImage")] + public bool? HasImage { get; set; } + /// The predicted classifier label (e.g. `needs_reasoning`), when available. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("predictedLabel")] @@ -4049,9 +4266,24 @@ public sealed partial class SessionAutoModeResolvedData [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("reasoningBucket")] public AutoModeResolvedReasoningBucket? ReasoningBucket { get; set; } + + /// Server-reported router processing time in milliseconds. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("routerLatencyMs")] + public double? RouterLatencyMs { get; set; } + + /// The routing method the server applied, when Auto Intent ran. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("routingMethod")] + public string? RoutingMethod { get; set; } + + /// Whether a sticky model choice overrode the router result. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("stickyOverride")] + public bool? StickyOverride { get; set; } } -/// Enterprise managed-settings resolution: the effective managed settings the session applied and where they came from, so SDK clients can show users what is enterprise-managed and by which authority. Fires whenever managed policy is (re)applied — at session start, on resume, and on account switch. This is an ephemeral live snapshot (delivered to subscribers but not persisted to the session event log), because at session start it resolves before `session.start` is emitted; for a session-independent pull, use the SDK `getManagedSettings()` API, which returns the identical payload. Managed settings have a single authoritative source, so the highest-authority present layer (server > device) wins wholesale; `bypassPermissionsDisabled` is deny-wins across layers. Marked experimental while the managed-settings surface stabilizes. +/// Enterprise managed-settings resolution: the effective managed settings the session applied and which channels contributed, so SDK clients can show users what is enterprise-managed. Fires whenever managed policy is (re)applied — at session start, on resume, and on account switch. This is an ephemeral live snapshot (delivered to subscribers but not persisted to the session event log), because at session start it resolves before `session.start` is emitted. Device values take precedence over server values per ordinary key, while permissions compose restrictively across device, server, and SDK-client layers. The account-scoped `getManagedSettings()` API does not include session-local client injection. Marked experimental while the managed-settings surface stabilizes. [Experimental(Diagnostics.Experimental)] public sealed partial class SessionManagedSettingsResolvedData { @@ -4059,7 +4291,12 @@ public sealed partial class SessionManagedSettingsResolvedData [JsonPropertyName("bypassPermissionsDisabled")] public required bool BypassPermissionsDisabled { get; set; } - /// Whether the device (MDM/plist/registry/file) managed-settings layer was present. + /// Whether a session-local permissions layer injected by the SDK host was present. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("clientManaged")] + public bool? ClientManaged { get; set; } + + /// Whether an actual device MDM/plist/registry/file managed-settings layer was present. [JsonPropertyName("deviceManaged")] public required bool DeviceManaged { get; set; } @@ -4071,6 +4308,11 @@ public sealed partial class SessionManagedSettingsResolvedData [JsonPropertyName("managedKeys")] public required string[] ManagedKeys { get; set; } + /// Whether at least two managed sources supplied permission allowlists, so enforcement intersects them and the flattened settings payload omits `permissions.allow`. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("permissionsAllowIntersected")] + public bool? PermissionsAllowIntersected { get; set; } + /// Whether the server (account/org) managed-settings layer was present. [JsonPropertyName("serverManaged")] public required bool ServerManaged { get; set; } @@ -4080,7 +4322,7 @@ public sealed partial class SessionManagedSettingsResolvedData [JsonPropertyName("settings")] public JsonElement? Settings { get; set; } - /// Which channel supplied the effective managed settings (the winning layer), or `none` when no policy is in force. + /// Channel summary: `server`, `device`, or `client` when exactly one channel contributed; `mixed` when multiple channels contributed; otherwise `none`. Consult the per-channel booleans for exact provenance. [JsonPropertyName("source")] public required ManagedSettingsResolvedSource Source { get; set; } } @@ -4193,6 +4435,19 @@ public sealed partial class SessionBackgroundTasksChangedData { } +/// Ephemeral invalidation signal for a changed factory run. +[Experimental(Diagnostics.Experimental)] +public sealed partial class FactoryRunUpdatedData +{ + /// Monotonic revision now available for the run. + [JsonPropertyName("revision")] + public required long Revision { get; set; } + + /// Gets or sets the runId value. + [JsonPropertyName("runId")] + public required string RunId { get; set; } +} + /// Payload of `session.skills_loaded` listing resolved skill metadata. public sealed partial class SessionSkillsLoadedData { @@ -4237,7 +4492,7 @@ public sealed partial class SessionMcpServerStatusChangedData [JsonPropertyName("serverName")] public required string ServerName { get; set; } - /// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured. + /// Connection status: connected, failed, needs-auth, pending, disabled, stopped, or not_configured. [JsonPropertyName("status")] public required McpServerStatus Status { get; set; } } @@ -4497,6 +4752,11 @@ public sealed partial class WorkingDirectoryContext [JsonPropertyName("hostType")] public WorkingDirectoryContextHostType? HostType { get; set; } + /// Set on the immediate preliminary event of a working-directory change, before the git context is resolved. A settled follow-up event (enriched with git context, or cwd-only for a non-repository) is always emitted afterward, so observers may defer to it. Absent on standalone/final events (e.g. relay context changes). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("pendingGitContext")] + public bool? PendingGitContext { get; set; } + /// Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("repository")] @@ -5652,6 +5912,12 @@ public sealed partial class ModelCallFailureRequestFingerprint /// Nested data type for ToolExecutionStartShellToolInfo. public sealed partial class ToolExecutionStartShellToolInfo { + /// The command with a redundant leading `cd` into the working directory removed, present only when there was one to remove. Computed with the same routine the shell driver applies before spawning, so a surface that renders this shows the text that actually runs. Consumers that display it should keep the original tool arguments available on demand. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("displayCommand")] + public string? DisplayCommand { get; set; } + /// Whether the command includes a file write redirection (e.g., > or >>). [JsonPropertyName("hasWriteFileRedirection")] public required bool HasWriteFileRedirection { get; set; } @@ -6670,6 +6936,74 @@ public sealed partial class SystemNotificationInstructionDiscovered : SystemNoti public required string TriggerTool { get; set; } } +/// System notification metadata for a factory execution attempt that reached a terminal state. +/// The factory_completed variant of . +public sealed partial class SystemNotificationFactoryCompleted : SystemNotification +{ + /// + [JsonIgnore] + public override string Type => "factory_completed"; + + /// Execution attempt that reached this terminal state. + [JsonPropertyName("attempt")] + public required long Attempt { get; set; } + + /// Consumed AI usage in nano-AIU. + [JsonPropertyName("consumedNanoAiu")] + public required long ConsumedNanoAiu { get; set; } + + /// Subagents consumed by the run across all attempts. + [JsonPropertyName("consumedSubagents")] + public required long ConsumedSubagents { get; set; } + + /// Accumulated active execution time in milliseconds. + [JsonPropertyName("elapsedMs")] + public required long ElapsedMs { get; set; } + + /// Persisted factory name. + [JsonPropertyName("factoryName")] + public required string FactoryName { get; set; } + + /// Machine-readable terminal failure details, when present. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("failure")] + public JsonElement? Failure { get; set; } + + /// Bounded prompt-safe preview of the completed result. + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] + [MaxLength(256)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("resultPreview")] + public string? ResultPreview { get; set; } + + /// Actionable run_factory resume guidance for a resource-limit failure. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("retryGuidance")] + public string? RetryGuidance { get; set; } + + /// Factory run identifier. + [JsonPropertyName("runId")] + public required string RunId { get; set; } + + /// Terminal status reached by this execution attempt. + [JsonPropertyName("status")] + public required SystemNotificationFactoryCompletedStatus Status { get; set; } +} + +/// System notification metadata from an external host that does not match a runtime-owned notification kind. +/// The unclassified variant of . +public sealed partial class SystemNotificationUnclassified : SystemNotification +{ + /// + [JsonIgnore] + public override string Type => "unclassified"; + + /// Opaque metadata supplied by the external host, when present. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("metadata")] + public JsonElement? Metadata { get; set; } +} + /// Structured metadata identifying what triggered this notification. /// Polymorphic base type discriminated by type. [JsonPolymorphic( @@ -6681,6 +7015,8 @@ public sealed partial class SystemNotificationInstructionDiscovered : SystemNoti [JsonDerivedType(typeof(SystemNotificationShellCompleted), "shell_completed")] [JsonDerivedType(typeof(SystemNotificationShellDetachedCompleted), "shell_detached_completed")] [JsonDerivedType(typeof(SystemNotificationInstructionDiscovered), "instruction_discovered")] +[JsonDerivedType(typeof(SystemNotificationFactoryCompleted), "factory_completed")] +[JsonDerivedType(typeof(SystemNotificationUnclassified), "unclassified")] public partial class SystemNotification { /// The type discriminator. @@ -6702,6 +7038,19 @@ public sealed partial class PermissionRequestShellCommand public required bool ReadOnly { get; set; } } +/// A parsed shell command segment used for argument-aware managed policy matching. +/// Nested data type for PermissionRequestShellCommandSegment. +public sealed partial class PermissionRequestShellCommandSegment +{ + /// Full text of this command segment, including arguments. + [JsonPropertyName("fullCommandText")] + public required string FullCommandText { get; set; } + + /// Command identifier (e.g., executable name). + [JsonPropertyName("identifier")] + public required string Identifier { get; set; } +} + /// A URL that may be accessed by a command in a shell permission request. /// Nested data type for PermissionRequestShellPossibleUrl. public sealed partial class PermissionRequestShellPossibleUrl @@ -6727,6 +7076,11 @@ public sealed partial class PermissionRequestShell : PermissionRequest [JsonPropertyName("commands")] public required PermissionRequestShellCommand[] Commands { get; set; } + /// Parsed command segments, including arguments, used for managed policy matching. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("commandSegments")] + public PermissionRequestShellCommandSegment[]? CommandSegments { get; set; } + /// The complete shell command text to be executed. [JsonPropertyName("fullCommandText")] public required string FullCommandText { get; set; } @@ -6739,6 +7093,15 @@ public sealed partial class PermissionRequestShell : PermissionRequest [JsonPropertyName("intention")] public required string Intention { get; set; } + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("managedApprovalRequired")] + public override bool? ManagedApprovalRequired + { + get => base.ManagedApprovalRequired; + set => base.ManagedApprovalRequired = value; + } + /// File paths that may be read or written by the command. [JsonPropertyName("possiblePaths")] public required string[] PossiblePaths { get; set; } @@ -6792,6 +7155,15 @@ public sealed partial class PermissionRequestWrite : PermissionRequest [JsonPropertyName("intention")] public required string Intention { get; set; } + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("managedApprovalRequired")] + public override bool? ManagedApprovalRequired + { + get => base.ManagedApprovalRequired; + set => base.ManagedApprovalRequired = value; + } + /// Complete new file contents for newly created files. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("newFileContents")] @@ -6825,6 +7197,15 @@ public sealed partial class PermissionRequestRead : PermissionRequest [JsonPropertyName("intention")] public required string Intention { get; set; } + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("managedApprovalRequired")] + public override bool? ManagedApprovalRequired + { + get => base.ManagedApprovalRequired; + set => base.ManagedApprovalRequired = value; + } + /// Path of the file or directory being read. [JsonPropertyName("path")] public required string Path { get; set; } @@ -6892,6 +7273,20 @@ public sealed partial class PermissionRequestUrl : PermissionRequest [JsonPropertyName("intention")] public required string Intention { get; set; } + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("managedApprovalRequired")] + public override bool? ManagedApprovalRequired + { + get => base.ManagedApprovalRequired; + set => base.ManagedApprovalRequired = value; + } + + /// Immediately preceding URL when this request is for a redirect target. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("redirectedFrom")] + public string? RedirectedFrom { get; set; } + /// True when this URL fetch is requesting to bypass the sandbox network policy: either the model set requestSandboxBypass: true, or the tool re-issued the request as an interactive bypass after the network policy denied the approved URL (host opted in via sandbox.allowBypass). This is a request, not a grant: the fetch runs only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("requestSandboxBypass")] @@ -7033,6 +7428,98 @@ public sealed partial class PermissionRequestExtensionManagement : PermissionReq public string? ToolCallId { get; set; } } +/// A declared phase shown in a factory permission prompt. +/// Nested data type for FactoryPermissionPhase. +public sealed partial class FactoryPermissionPhase +{ + /// Optional phase detail. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("detail")] + public string? Detail { get; set; } + + /// Phase title. + [JsonPropertyName("title")] + public required string Title { get; set; } +} + +/// Factory run or authoring permission request. +/// The factory variant of . +public sealed partial class PermissionRequestFactory : PermissionRequest +{ + /// + [JsonIgnore] + public override string Kind => "factory"; + + /// Canonical key used for scoped factory approvals. + [JsonPropertyName("approvalKey")] + public required string ApprovalKey { get; set; } + + /// Whether this factory is eligible for persistent approval. + [JsonPropertyName("canPersistApproval")] + public required bool CanPersistApproval { get; set; } + + /// Gets or sets the declaredMaxAiCredits value. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("declaredMaxAiCredits")] + public double? DeclaredMaxAiCredits { get; set; } + + /// Gets or sets the declaredMaxConcurrentSubagents value. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("declaredMaxConcurrentSubagents")] + public long? DeclaredMaxConcurrentSubagents { get; set; } + + /// Gets or sets the declaredMaxTotalSubagents value. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("declaredMaxTotalSubagents")] + public long? DeclaredMaxTotalSubagents { get; set; } + + /// Gets or sets the declaredTimeoutSeconds value. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("declaredTimeoutSeconds")] + public double? DeclaredTimeoutSeconds { get; set; } + + /// Factory description. + [JsonPropertyName("description")] + public required string Description { get; set; } + + /// Effective AI-credit limit; omitted means unlimited. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("maxAiCredits")] + public double? MaxAiCredits { get; set; } + + /// Effective concurrent-subagent limit; omitted means unlimited. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("maxConcurrentSubagents")] + public long? MaxConcurrentSubagents { get; set; } + + /// Effective total-subagent limit; omitted means unlimited. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("maxTotalSubagents")] + public long? MaxTotalSubagents { get; set; } + + /// Factory name. + [JsonPropertyName("name")] + public required string Name { get; set; } + + /// Factory operation, either run or author. + [JsonPropertyName("operation")] + public required FactoryPermissionOperation Operation { get; set; } + + /// Declared factory phases. + [JsonPropertyName("phases")] + public required FactoryPermissionPhase[] Phases { get; set; } + + /// Effective active-time limit in seconds; omitted means unlimited. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("timeoutSeconds")] + public double? TimeoutSeconds { get; set; } + + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } +} + /// Extension permission access request. /// The extension-permission-access variant of . public sealed partial class PermissionRequestExtensionPermissionAccess : PermissionRequest @@ -7069,12 +7556,18 @@ public sealed partial class PermissionRequestExtensionPermissionAccess : Permiss [JsonDerivedType(typeof(PermissionRequestCustomTool), "custom-tool")] [JsonDerivedType(typeof(PermissionRequestHook), "hook")] [JsonDerivedType(typeof(PermissionRequestExtensionManagement), "extension-management")] +[JsonDerivedType(typeof(PermissionRequestFactory), "factory")] [JsonDerivedType(typeof(PermissionRequestExtensionPermissionAccess), "extension-permission-access")] public partial class PermissionRequest { /// The type discriminator. [JsonPropertyName("kind")] public virtual string Kind { get; set; } = string.Empty; + + /// Whether managed policy requires a human response and forbids host auto-approval. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("managedApprovalRequired")] + public virtual bool? ManagedApprovalRequired { get; set; } } @@ -7083,6 +7576,16 @@ public partial class PermissionRequest [Experimental(Diagnostics.Experimental)] public sealed partial class PermissionAutoApproval { + /// Classified cause of an `error` recommendation. Absent for every other recommendation. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("failureReason")] + public AutoApprovalJudgeFailureReason? FailureReason { get; set; } + + /// Model id that produced the recommendation, when the judge was consulted and reported one. Absent for `excluded` (the judge was not consulted) and for failures that occurred before a model was selected. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("model")] + public string? Model { get; set; } + /// Human-readable reason for the judge's recommendation, when available. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("reason")] @@ -7123,6 +7626,11 @@ public sealed partial class PermissionPromptRequestCommands : PermissionPromptRe [JsonPropertyName("intention")] public required string Intention { get; set; } + /// Whether managed policy requires a human response and forbids host auto-approval. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("managedApprovalRequired")] + public bool? ManagedApprovalRequired { get; set; } + /// Tool call ID that triggered this permission request. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("toolCallId")] @@ -7164,6 +7672,11 @@ public sealed partial class PermissionPromptRequestWrite : PermissionPromptReque [JsonPropertyName("intention")] public required string Intention { get; set; } + /// Whether managed policy requires a human response and forbids host auto-approval. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("managedApprovalRequired")] + public bool? ManagedApprovalRequired { get; set; } + /// Complete new file contents for newly created files. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("newFileContents")] @@ -7193,6 +7706,11 @@ public sealed partial class PermissionPromptRequestRead : PermissionPromptReques [JsonPropertyName("intention")] public required string Intention { get; set; } + /// Whether managed policy requires a human response and forbids host auto-approval. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("managedApprovalRequired")] + public bool? ManagedApprovalRequired { get; set; } + /// Path of the file or directory being read. [JsonPropertyName("path")] public required string Path { get; set; } @@ -7258,6 +7776,16 @@ public sealed partial class PermissionPromptRequestUrl : PermissionPromptRequest [JsonPropertyName("intention")] public required string Intention { get; set; } + /// Whether managed policy requires a human response and forbids host auto-approval. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("managedApprovalRequired")] + public bool? ManagedApprovalRequired { get; set; } + + /// Immediately preceding URL when this prompt is for a redirect target. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("redirectedFrom")] + public string? RedirectedFrom { get; set; } + /// True when this URL fetch is requesting to bypass the sandbox network policy: either the model set requestSandboxBypass: true, or the tool re-issued the request as an interactive bypass after the network policy denied the approved URL (host opted in via sandbox.allowBypass). This is a request, not a grant: the fetch runs only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("requestSandboxBypass")] @@ -7451,13 +7979,17 @@ public sealed partial class PermissionPromptRequestExtensionManagement : Permiss public string? ToolCallId { get; set; } } -/// Extension permission access prompt. -/// The extension-permission-access variant of . -public sealed partial class PermissionPromptRequestExtensionPermissionAccess : PermissionPromptRequest +/// Factory run or authoring permission prompt. +/// The factory variant of . +public sealed partial class PermissionPromptRequestFactory : PermissionPromptRequest { /// [JsonIgnore] - public override string Kind => "extension-permission-access"; + public override string Kind => "factory"; + + /// Canonical key used for scoped factory approvals. + [JsonPropertyName("approvalKey")] + public required string ApprovalKey { get; set; } /// Auto-approval judge information for this request; present only when auto mode is enabled. [Experimental(Diagnostics.Experimental)] @@ -7465,7 +7997,92 @@ public sealed partial class PermissionPromptRequestExtensionPermissionAccess : P [JsonPropertyName("autoApproval")] public PermissionAutoApproval? AutoApproval { get; set; } - /// Capabilities the extension is requesting. + /// Whether this factory is eligible for persistent approval. + [JsonPropertyName("canPersistApproval")] + public required bool CanPersistApproval { get; set; } + + /// Gets or sets the declaredMaxAiCredits value. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("declaredMaxAiCredits")] + public double? DeclaredMaxAiCredits { get; set; } + + /// Gets or sets the declaredMaxConcurrentSubagents value. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("declaredMaxConcurrentSubagents")] + public long? DeclaredMaxConcurrentSubagents { get; set; } + + /// Gets or sets the declaredMaxTotalSubagents value. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("declaredMaxTotalSubagents")] + public long? DeclaredMaxTotalSubagents { get; set; } + + /// Gets or sets the declaredTimeoutSeconds value. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("declaredTimeoutSeconds")] + public double? DeclaredTimeoutSeconds { get; set; } + + /// Factory description. + [JsonPropertyName("description")] + public required string Description { get; set; } + + /// Whether managed policy requires a human response and forbids host auto-approval. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("managedApprovalRequired")] + public bool? ManagedApprovalRequired { get; set; } + + /// Effective AI-credit limit; omitted means unlimited. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("maxAiCredits")] + public double? MaxAiCredits { get; set; } + + /// Effective concurrent-subagent limit; omitted means unlimited. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("maxConcurrentSubagents")] + public long? MaxConcurrentSubagents { get; set; } + + /// Effective total-subagent limit; omitted means unlimited. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("maxTotalSubagents")] + public long? MaxTotalSubagents { get; set; } + + /// Factory name. + [JsonPropertyName("name")] + public required string Name { get; set; } + + /// Factory operation, either run or author. + [JsonPropertyName("operation")] + public required FactoryPermissionOperation Operation { get; set; } + + /// Declared factory phases. + [JsonPropertyName("phases")] + public required FactoryPermissionPhase[] Phases { get; set; } + + /// Effective active-time limit in seconds; omitted means unlimited. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("timeoutSeconds")] + public double? TimeoutSeconds { get; set; } + + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } +} + +/// Extension permission access prompt. +/// The extension-permission-access variant of . +public sealed partial class PermissionPromptRequestExtensionPermissionAccess : PermissionPromptRequest +{ + /// + [JsonIgnore] + public override string Kind => "extension-permission-access"; + + /// Auto-approval judge information for this request; present only when auto mode is enabled. + [Experimental(Diagnostics.Experimental)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("autoApproval")] + public PermissionAutoApproval? AutoApproval { get; set; } + + /// Capabilities the extension is requesting. [JsonPropertyName("capabilities")] public required string[] Capabilities { get; set; } @@ -7494,6 +8111,7 @@ public sealed partial class PermissionPromptRequestExtensionPermissionAccess : P [JsonDerivedType(typeof(PermissionPromptRequestPath), "path")] [JsonDerivedType(typeof(PermissionPromptRequestHook), "hook")] [JsonDerivedType(typeof(PermissionPromptRequestExtensionManagement), "extension-management")] +[JsonDerivedType(typeof(PermissionPromptRequestFactory), "factory")] [JsonDerivedType(typeof(PermissionPromptRequestExtensionPermissionAccess), "extension-permission-access")] public partial class PermissionPromptRequest { @@ -7596,6 +8214,20 @@ public sealed partial class UserToolSessionApprovalExtensionManagement : UserToo public string? Operation { get; set; } } +/// Session-scoped factory approval, optionally narrowed by approval key. +/// The factory variant of . +public sealed partial class UserToolSessionApprovalFactory : UserToolSessionApproval +{ + /// + [JsonIgnore] + public override string Kind => "factory"; + + /// Optional factory operation name or canonical approval key. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("approvalKey")] + public string? ApprovalKey { get; set; } +} + /// Session-scoped tool-approval rule for an extension's permission-gated capability access, keyed by extension name. /// The extension-permission-access variant of . public sealed partial class UserToolSessionApprovalExtensionPermissionAccess : UserToolSessionApproval @@ -7621,6 +8253,7 @@ public sealed partial class UserToolSessionApprovalExtensionPermissionAccess : U [JsonDerivedType(typeof(UserToolSessionApprovalMemory), "memory")] [JsonDerivedType(typeof(UserToolSessionApprovalCustomTool), "custom-tool")] [JsonDerivedType(typeof(UserToolSessionApprovalExtensionManagement), "extension-management")] +[JsonDerivedType(typeof(UserToolSessionApprovalFactory), "factory")] [JsonDerivedType(typeof(UserToolSessionApprovalExtensionPermissionAccess), "extension-permission-access")] public partial class UserToolSessionApproval { @@ -7941,6 +8574,11 @@ public sealed partial class SkillsLoadedSkill [JsonPropertyName("argumentHint")] public string? ArgumentHint { get; set; } + /// Canonical slash command name used to invoke the skill, without the leading '/'. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("commandName")] + public string? CommandName { get; set; } + /// Description of what the skill does. [JsonPropertyName("description")] public required string Description { get; set; } @@ -8033,7 +8671,7 @@ public sealed partial class McpServersLoadedServer [JsonPropertyName("source")] public McpServerSource? Source { get; set; } - /// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured. + /// Connection status: connected, failed, needs-auth, pending, disabled, stopped, or not_configured. [JsonPropertyName("status")] public required McpServerStatus Status { get; set; } @@ -8412,6 +9050,67 @@ public override void Write(Utf8JsonWriter writer, Verbosity value, JsonSerialize } } +/// Who created the schedule: `user` (an explicit user action such as `/every` or `/after`) or `model` (the agent via the `manage_schedule` tool). Gates whether a scheduled skill that opted out of model invocation may fire: only user-created schedules may. +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct ScheduleOrigin : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public ScheduleOrigin(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// The schedule was created by an explicit user action, such as `/every` or `/after`. + public static ScheduleOrigin User { get; } = new("user"); + + /// The schedule was created by the agent via the `manage_schedule` tool. + public static ScheduleOrigin Model { get; } = new("model"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ScheduleOrigin left, ScheduleOrigin right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ScheduleOrigin left, ScheduleOrigin right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is ScheduleOrigin other && Equals(other); + + /// + public bool Equals(ScheduleOrigin other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override ScheduleOrigin Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, ScheduleOrigin value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ScheduleOrigin)); + } + } +} + /// The type of operation performed on the autopilot objective state file. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] @@ -8919,6 +9618,140 @@ public override void Write(Utf8JsonWriter writer, ShutdownType value, JsonSerial } } +/// What initiated a conversation compaction. +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct CompactionTrigger : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public CompactionTrigger(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Background compaction started automatically because context utilization crossed the background threshold. + public static CompactionTrigger Threshold { get; } = new("threshold"); + + /// Compaction forced by a context-limit model response (e.g. HTTP 413) before retrying the request. + public static CompactionTrigger ContextLimitRetry { get; } = new("context_limit_retry"); + + /// User-requested compaction, e.g. the /compact command or the history.compact API. + public static CompactionTrigger Manual { get; } = new("manual"); + + /// Emergency compaction triggered by high process memory usage. + public static CompactionTrigger MemoryPressure { get; } = new("memory_pressure"); + + /// Compaction requested while switching to a model with a smaller context window. + public static CompactionTrigger ModelSwitch { get; } = new("model_switch"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(CompactionTrigger left, CompactionTrigger right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(CompactionTrigger left, CompactionTrigger right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is CompactionTrigger other && Equals(other); + + /// + public bool Equals(CompactionTrigger other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override CompactionTrigger Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, CompactionTrigger value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(CompactionTrigger)); + } + } +} + +/// Semantic result of evaluating a task completion request. +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct TaskCompletionOutcome : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public TaskCompletionOutcome(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// The completion request was accepted and the objective is complete. + public static TaskCompletionOutcome Completed { get; } = new("completed"); + + /// The completion request was rejected because more work or validation remains. + public static TaskCompletionOutcome Continue { get; } = new("continue"); + + /// Completion cannot proceed without intervention; the active objective is paused when one is identified. + public static TaskCompletionOutcome Blocked { get; } = new("blocked"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(TaskCompletionOutcome left, TaskCompletionOutcome right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(TaskCompletionOutcome left, TaskCompletionOutcome right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is TaskCompletionOutcome other && Equals(other); + + /// + public bool Equals(TaskCompletionOutcome other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override TaskCompletionOutcome Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, TaskCompletionOutcome value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskCompletionOutcome)); + } + } +} + /// The agent mode that was active when this message was sent. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] @@ -9643,6 +10476,9 @@ public AbortReason(string value) /// An MCP server delivered a user.abort notification. public static AbortReason UserAbort { get; } = new("user_abort"); + /// Autopilot stopped the run because the active objective reached its user-set --max-ai-credits limit. + public static AbortReason AutopilotCreditLimit { get; } = new("autopilot_credit_limit"); + /// Returns a value indicating whether two instances are equivalent. public static bool operator ==(AbortReason left, AbortReason right) => left.Equals(right); @@ -10292,6 +11128,73 @@ public override void Write(Utf8JsonWriter writer, SystemNotificationAgentComplet } } +/// Terminal status reached by a factory execution attempt. +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct SystemNotificationFactoryCompletedStatus : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public SystemNotificationFactoryCompletedStatus(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// The factory completed successfully. + public static SystemNotificationFactoryCompletedStatus Completed { get; } = new("completed"); + + /// The factory was halted. + public static SystemNotificationFactoryCompletedStatus Halted { get; } = new("halted"); + + /// The factory was cancelled. + public static SystemNotificationFactoryCompletedStatus Cancelled { get; } = new("cancelled"); + + /// The factory failed. + public static SystemNotificationFactoryCompletedStatus Error { get; } = new("error"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SystemNotificationFactoryCompletedStatus left, SystemNotificationFactoryCompletedStatus right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SystemNotificationFactoryCompletedStatus left, SystemNotificationFactoryCompletedStatus right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is SystemNotificationFactoryCompletedStatus other && Equals(other); + + /// + public bool Equals(SystemNotificationFactoryCompletedStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override SystemNotificationFactoryCompletedStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, SystemNotificationFactoryCompletedStatus value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SystemNotificationFactoryCompletedStatus)); + } + } +} + /// Whether this is a store or vote memory operation. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] @@ -10414,6 +11317,138 @@ public override void Write(Utf8JsonWriter writer, PermissionRequestMemoryDirecti } } +/// Operation gated by a factory permission request. +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct FactoryPermissionOperation : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public FactoryPermissionOperation(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Running a registered factory, which spends subagents, active time, and AI credits under the approved limits. + public static FactoryPermissionOperation Run { get; } = new("run"); + + /// Authoring a factory, which writes JavaScript into a session-scoped extension and loads it. + public static FactoryPermissionOperation Author { get; } = new("author"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(FactoryPermissionOperation left, FactoryPermissionOperation right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(FactoryPermissionOperation left, FactoryPermissionOperation right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is FactoryPermissionOperation other && Equals(other); + + /// + public bool Equals(FactoryPermissionOperation other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override FactoryPermissionOperation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, FactoryPermissionOperation value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(FactoryPermissionOperation)); + } + } +} + +/// Why the auto-approval judge produced no usable recommendation. Present only alongside an `error` recommendation, where the human-readable reason is a fixed string and therefore cannot distinguish these cases. Intended to make a judge failure reportable by a consumer that has no access to the host's logs. +[Experimental(Diagnostics.Experimental)] +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct AutoApprovalJudgeFailureReason : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public AutoApprovalJudgeFailureReason(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// The judge model call exceeded its deadline. + public static AutoApprovalJudgeFailureReason Timeout { get; } = new("timeout"); + + /// The judge model call was cancelled before it returned. + public static AutoApprovalJudgeFailureReason Abort { get; } = new("abort"); + + /// The judge model call completed but returned no content. + public static AutoApprovalJudgeFailureReason EmptyResponse { get; } = new("empty_response"); + + /// The judge model call failed (for example a transport, authentication, or rate-limit error). + public static AutoApprovalJudgeFailureReason ModelError { get; } = new("model_error"); + + /// The judge model replied, but the reply carried no ALLOW/DENY verdict. + public static AutoApprovalJudgeFailureReason ParseError { get; } = new("parse_error"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(AutoApprovalJudgeFailureReason left, AutoApprovalJudgeFailureReason right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(AutoApprovalJudgeFailureReason left, AutoApprovalJudgeFailureReason right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is AutoApprovalJudgeFailureReason other && Equals(other); + + /// + public bool Equals(AutoApprovalJudgeFailureReason other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override AutoApprovalJudgeFailureReason Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, AutoApprovalJudgeFailureReason value, JsonSerializerOptions options) + { + GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AutoApprovalJudgeFailureReason)); + } + } +} + /// Outcome of the auto-approval safety judge for a permission request. Present only when auto mode is enabled; its absence means the judge did not evaluate the request (auto mode was off). [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] @@ -11122,7 +12157,7 @@ public override void Write(Utf8JsonWriter writer, AutoModeResolvedReasoningBucke } } -/// Which channel supplied the effective enterprise managed settings (highest-authority present layer wins wholesale). +/// Summary of which managed-settings channels contributed to the effective session policy. Use the per-channel booleans for exact provenance. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] public readonly struct ManagedSettingsResolvedSource : IEquatable @@ -11141,13 +12176,19 @@ public ManagedSettingsResolvedSource(string value) /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Account/org policy self-fetched from the GitHub managed-settings endpoint (higher authority). + /// Only the server/account channel contributed. public static ManagedSettingsResolvedSource Server { get; } = new("server"); - /// Device-level MDM policy discovered from plist/registry/file (lower authority). + /// Only the device MDM/plist/registry/file channel contributed. public static ManagedSettingsResolvedSource Device { get; } = new("device"); - /// No managed policy is in force (no layer contributed). + /// Only session-local SDK-host injection contributed. + public static ManagedSettingsResolvedSource Client { get; } = new("client"); + + /// More than one channel contributed. Ordinary keys resolve device over server per key, while permissions compose restrictively across all present layers. + public static ManagedSettingsResolvedSource Mixed { get; } = new("mixed"); + + /// No managed policy is in force (no channel contributed). public static ManagedSettingsResolvedSource None { get; } = new("none"); /// Returns a value indicating whether two instances are equivalent. @@ -11524,7 +12565,7 @@ public override void Write(Utf8JsonWriter writer, McpServerSource value, JsonSer } } -/// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured. +/// Connection status: connected, failed, needs-auth, pending, disabled, stopped, or not_configured. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] public readonly struct McpServerStatus : IEquatable @@ -11558,6 +12599,9 @@ public McpServerStatus(string value) /// The server is configured but disabled. public static McpServerStatus Disabled { get; } = new("disabled"); + /// The server was intentionally stopped and can be restarted on demand when policy permits; a server quarantined by restrictive managed policy stays stopped and cannot be restarted until the policy allows it. + public static McpServerStatus Stopped { get; } = new("stopped"); + /// The server is not configured for this session. public static McpServerStatus NotConfigured { get; } = new("not_configured"); @@ -11908,6 +12952,10 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(ExternalToolCompletedEvent))] [JsonSerializable(typeof(ExternalToolRequestedData))] [JsonSerializable(typeof(ExternalToolRequestedEvent))] +[JsonSerializable(typeof(FactoryPermissionPhase))] +[JsonSerializable(typeof(FactoryRunUpdatedData))] +[JsonSerializable(typeof(FactoryRunUpdatedEvent))] +[JsonSerializable(typeof(GitHubMcpToolConfig))] [JsonSerializable(typeof(GitHubRepoRef))] [JsonSerializable(typeof(HandoffRepository))] [JsonSerializable(typeof(HeaderEntry))] @@ -11957,6 +13005,7 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(PermissionPromptRequestCustomTool))] [JsonSerializable(typeof(PermissionPromptRequestExtensionManagement))] [JsonSerializable(typeof(PermissionPromptRequestExtensionPermissionAccess))] +[JsonSerializable(typeof(PermissionPromptRequestFactory))] [JsonSerializable(typeof(PermissionPromptRequestHook))] [JsonSerializable(typeof(PermissionPromptRequestMcp))] [JsonSerializable(typeof(PermissionPromptRequestMemory))] @@ -11968,12 +13017,14 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(PermissionRequestCustomTool))] [JsonSerializable(typeof(PermissionRequestExtensionManagement))] [JsonSerializable(typeof(PermissionRequestExtensionPermissionAccess))] +[JsonSerializable(typeof(PermissionRequestFactory))] [JsonSerializable(typeof(PermissionRequestHook))] [JsonSerializable(typeof(PermissionRequestMcp))] [JsonSerializable(typeof(PermissionRequestMemory))] [JsonSerializable(typeof(PermissionRequestRead))] [JsonSerializable(typeof(PermissionRequestShell))] [JsonSerializable(typeof(PermissionRequestShellCommand))] +[JsonSerializable(typeof(PermissionRequestShellCommandSegment))] [JsonSerializable(typeof(PermissionRequestShellPossibleUrl))] [JsonSerializable(typeof(PermissionRequestUrl))] [JsonSerializable(typeof(PermissionRequestWrite))] @@ -12022,6 +13073,8 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(SessionCompactionStartEvent))] [JsonSerializable(typeof(SessionContextChangedData))] [JsonSerializable(typeof(SessionContextChangedEvent))] +[JsonSerializable(typeof(SessionContextClearedData))] +[JsonSerializable(typeof(SessionContextClearedEvent))] [JsonSerializable(typeof(SessionCustomAgentsUpdatedData))] [JsonSerializable(typeof(SessionCustomAgentsUpdatedEvent))] [JsonSerializable(typeof(SessionCustomNotificationData))] @@ -12126,10 +13179,12 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(SystemNotificationAgentIdle))] [JsonSerializable(typeof(SystemNotificationData))] [JsonSerializable(typeof(SystemNotificationEvent))] +[JsonSerializable(typeof(SystemNotificationFactoryCompleted))] [JsonSerializable(typeof(SystemNotificationInstructionDiscovered))] [JsonSerializable(typeof(SystemNotificationNewInboxMessage))] [JsonSerializable(typeof(SystemNotificationShellCompleted))] [JsonSerializable(typeof(SystemNotificationShellDetachedCompleted))] +[JsonSerializable(typeof(SystemNotificationUnclassified))] [JsonSerializable(typeof(ToolExecutionCompleteContent))] [JsonSerializable(typeof(ToolExecutionCompleteContentAudio))] [JsonSerializable(typeof(ToolExecutionCompleteContentImage))] @@ -12182,6 +13237,7 @@ public override void Write(Utf8JsonWriter writer, ExtensionsLoadedExtensionStatu [JsonSerializable(typeof(UserToolSessionApprovalCustomTool))] [JsonSerializable(typeof(UserToolSessionApprovalExtensionManagement))] [JsonSerializable(typeof(UserToolSessionApprovalExtensionPermissionAccess))] +[JsonSerializable(typeof(UserToolSessionApprovalFactory))] [JsonSerializable(typeof(UserToolSessionApprovalMcp))] [JsonSerializable(typeof(UserToolSessionApprovalMemory))] [JsonSerializable(typeof(UserToolSessionApprovalRead))] diff --git a/dotnet/src/JsonRpc.cs b/dotnet/src/JsonRpc.cs index bf1684f17..36289d2e6 100644 --- a/dotnet/src/JsonRpc.cs +++ b/dotnet/src/JsonRpc.cs @@ -29,6 +29,8 @@ internal sealed partial class JsonRpc : IDisposable { private const int ErrorCodeMethodNotFound = -32601; private const int ErrorCodeInternalError = -32603; + private const int InitialReadBufferSize = 256; + private const int MaximumRetainedReadBufferSize = 1024 * 1024; private readonly Stream _sendStream; private readonly Stream _receiveStream; @@ -259,7 +261,7 @@ private static byte[] BuildFrame(ReadOnlySpan json, out int frameLen) private async Task ReadLoopAsync(CancellationToken cancellationToken) { - var buffer = new byte[256]; + var buffer = new byte[InitialReadBufferSize]; int carried = 0; // bytes in buffer carried over from previous read try { @@ -298,6 +300,17 @@ private async Task ReadLoopAsync(CancellationToken cancellationToken) Buffer.BlockCopy(buffer, contentLength, buffer, 0, carried); } + if (buffer.Length > MaximumRetainedReadBufferSize) + { + var retainedBuffer = new byte[Math.Max(InitialReadBufferSize, carried)]; + if (carried > 0) + { + Buffer.BlockCopy(buffer, 0, retainedBuffer, 0, carried); + } + + buffer = retainedBuffer; + } + if (message is not { } parsed) { continue; diff --git a/dotnet/src/PermissionDecision.cs b/dotnet/src/PermissionDecision.cs index 54e123791..3eb1d0e08 100644 --- a/dotnet/src/PermissionDecision.cs +++ b/dotnet/src/PermissionDecision.cs @@ -43,4 +43,13 @@ public static PermissionDecision Reject(string? feedback = null) => /// connected client to answer instead. /// public static PermissionDecision NoResult() => new PermissionDecisionNoResult(); + + /// + /// Optional provenance describing how and where this decision was made. + /// This is never serialized as part of the decision itself: the SDK forwards + /// it to the runtime as a sibling of result so that auto-approval + /// telemetry can be attributed correctly. + /// + [JsonIgnore] + public PermissionDecisionContext? DecisionContext { get; set; } } diff --git a/dotnet/src/PermissionHandlers.cs b/dotnet/src/PermissionHandlers.cs index 4386e8ba6..d990ca653 100644 --- a/dotnet/src/PermissionHandlers.cs +++ b/dotnet/src/PermissionHandlers.cs @@ -9,7 +9,34 @@ namespace GitHub.Copilot; /// Provides pre-built permission request handlers. public static class PermissionHandler { - /// A permission handler that approves all permission requests. + /// + /// A permission handler that approves requests when managed settings are disabled. + /// public static Func> ApproveAll { get; } = - (_, _) => Task.FromResult(PermissionDecision.ApproveOnce()); + (request, invocation) => invocation.ManagedSettingsEnabled + ? Task.FromException( + new InvalidOperationException("ApproveAll cannot be used when managed settings are enabled")) + : RequiresManagedApproval(request) + ? Task.FromResult(PermissionDecision.NoResult()) + : Task.FromResult(PermissionDecision.ApproveOnce()); + + private static bool RequiresManagedApproval(PermissionRequest request) + { + if (request.ManagedApprovalRequired is true) + { + return true; + } + + return request.GetType() == typeof(PermissionRequest) + && request.Kind is not ("shell" + or "write" + or "read" + or "mcp" + or "url" + or "memory" + or "custom-tool" + or "hook" + or "extension-management" + or "extension-permission-access"); + } } diff --git a/dotnet/src/Session.cs b/dotnet/src/Session.cs index 04306b7f6..0ce10c290 100644 --- a/dotnet/src/Session.cs +++ b/dotnet/src/Session.cs @@ -1,4 +1,4 @@ -/*--------------------------------------------------------------------------------------------- +/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/ @@ -63,6 +63,7 @@ public sealed partial class CopilotSession : IAsyncDisposable private readonly CopilotClient _parentClient; private volatile Func>? _permissionHandler; + private bool _managedSettingsEnabled; private volatile Func>? _mcpAuthHandler; private volatile Func>? _userInputHandler; private volatile Func>? _elicitationHandler; @@ -262,7 +263,7 @@ public Task SendAsync(string prompt, CancellationToken cancellationToken /// Prompt = "Explain this code", /// Attachments = new List<Attachment> /// { - /// new() { Type = "file", Path = "./Program.cs" } + /// new AttachmentFile { Path = "./Program.cs", DisplayName = "Program.cs" } /// } /// }); /// @@ -557,13 +558,17 @@ internal void RegisterTools(ICollection tools) /// Registers a handler for permission requests. /// /// The permission handler function. + /// Whether managed settings are enabled for the session. /// /// When the assistant needs permission to perform certain actions (e.g., file operations), /// this handler is called to approve or deny the request. /// - internal void RegisterPermissionHandler(Func>? handler) + internal void RegisterPermissionHandler( + Func>? handler, + bool managedSettingsEnabled) { _permissionHandler = handler; + _managedSettingsEnabled = managedSettingsEnabled; } internal void RegisterMcpAuthHandler(Func>? handler) @@ -590,7 +595,8 @@ internal async Task HandlePermissionRequestAsync(JsonElement var invocation = new PermissionInvocation { - SessionId = SessionId + SessionId = SessionId, + ManagedSettingsEnabled = _managedSettingsEnabled }; var permissionTimestamp = Stopwatch.GetTimestamp(); @@ -932,7 +938,8 @@ private async Task ExecutePermissionAndRespondAsync(string requestId, Permission { var invocation = new PermissionInvocation { - SessionId = SessionId + SessionId = SessionId, + ManagedSettingsEnabled = _managedSettingsEnabled }; var permissionTimestamp = Stopwatch.GetTimestamp(); @@ -947,15 +954,16 @@ private async Task ExecutePermissionAndRespondAsync(string requestId, Permission return; } var responseRpcTimestamp = Stopwatch.GetTimestamp(); - await Rpc.Permissions.HandlePendingPermissionRequestAsync(requestId, decision); + await Rpc.Permissions.HandlePendingPermissionRequestAsync(requestId, decision, decision.DecisionContext); LoggingHelpers.LogTiming(_logger, LogLevel.Debug, null, "CopilotSession.ExecutePermissionAndRespondAsync response sent successfully. Elapsed={Elapsed}, SessionId={SessionId}, RequestId={RequestId}", responseRpcTimestamp, SessionId, requestId); } - catch (Exception) + catch (Exception ex) { + _logger.LogError(ex, "Permission handler or response delivery failed. SessionId={SessionId}, RequestId={RequestId}", SessionId, requestId); try { await Rpc.Permissions.HandlePendingPermissionRequestAsync(requestId, PermissionDecision.UserNotAvailable()); @@ -1145,7 +1153,7 @@ internal void SetCanvasHandler(ICanvasHandler? handler) ClientSessionApis.Canvas = handler is null ? null : new CanvasHandlerAdapter(handler); } - private static readonly JsonElement NullJsonElement = JsonDocument.Parse("null").RootElement.Clone(); + private static readonly JsonElement NullJsonElement = JsonElement.Parse("null"); private static JsonElement SerializeActionResult(object? value) { @@ -1605,6 +1613,11 @@ internal void RegisterHooks(SessionHooks hooks) JsonSerializer.Deserialize(input.GetRawText(), SessionJsonContext.Default.UserPromptSubmittedHookInput)!, invocation) : null, + "userPromptTransformed" => hooks.OnUserPromptTransformed != null + ? await hooks.OnUserPromptTransformed( + JsonSerializer.Deserialize(input.GetRawText(), SessionJsonContext.Default.UserPromptTransformedHookInput)!, + invocation) + : null, "sessionStart" => hooks.OnSessionStart != null ? await hooks.OnSessionStart( JsonSerializer.Deserialize(input.GetRawText(), SessionJsonContext.Default.SessionStartHookInput)!, @@ -1620,6 +1633,11 @@ internal void RegisterHooks(SessionHooks hooks) JsonSerializer.Deserialize(input.GetRawText(), SessionJsonContext.Default.ErrorOccurredHookInput)!, invocation) : null, + "agentStop" => hooks.OnAgentStop != null + ? await hooks.OnAgentStop( + JsonSerializer.Deserialize(input.GetRawText(), SessionJsonContext.Default.AgentStopHookInput)!, + invocation) + : null, _ => null }; } @@ -1777,7 +1795,7 @@ public async Task AbortAsync(CancellationToken cancellationToken = default) /// The new model takes effect for the next message. Conversation history is preserved. /// /// Model ID to switch to (e.g., "gpt-5.4"). - /// Reasoning effort level (e.g., "low", "medium", "high", "xhigh"). + /// Reasoning effort level (e.g., "low", "medium", "high", "xhigh", "max"). /// Per-property overrides for model capabilities, deep-merged over runtime defaults. /// Optional cancellation token. /// @@ -1818,6 +1836,7 @@ await Rpc.Model.SwitchToAsync( null, options.ModelCapabilities, options.ContextTier, + null, cancellationToken); } @@ -1987,6 +2006,8 @@ internal void ThrowIfDisposed() AllowOutOfOrderMetadataProperties = true, NumberHandling = JsonNumberHandling.AllowReadingFromString, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] + [JsonSerializable(typeof(AgentStopHookInput))] + [JsonSerializable(typeof(AgentStopHookOutput))] [JsonSerializable(typeof(AutoModeSwitchRequest))] [JsonSerializable(typeof(AutoModeSwitchResponse))] [JsonSerializable(typeof(Dictionary))] @@ -2017,5 +2038,7 @@ internal void ThrowIfDisposed() [JsonSerializable(typeof(Attachment))] [JsonSerializable(typeof(UserPromptSubmittedHookInput))] [JsonSerializable(typeof(UserPromptSubmittedHookOutput))] + [JsonSerializable(typeof(UserPromptTransformedHookInput))] + [JsonSerializable(typeof(UserPromptTransformedHookOutput))] internal partial class SessionJsonContext : JsonSerializerContext; } diff --git a/dotnet/src/SessionFsProvider.cs b/dotnet/src/SessionFsProvider.cs index fbb8df507..a353c93ad 100644 --- a/dotnet/src/SessionFsProvider.cs +++ b/dotnet/src/SessionFsProvider.cs @@ -3,6 +3,7 @@ *--------------------------------------------------------------------------------------------*/ using GitHub.Copilot.Rpc; +using System.Diagnostics.CodeAnalysis; using System.Text.Json; namespace GitHub.Copilot; @@ -27,6 +28,23 @@ public sealed class SessionFsSqliteResult public long? LastInsertRowid { get; set; } } +/// +/// One statement in an atomic SQLite transaction passed to +/// . +/// +[Experimental(Diagnostics.Experimental)] +public sealed class SessionFsSqliteStatement +{ + /// How to execute: "exec", "query", or "run". + public SessionFsSqliteQueryType QueryType { get; set; } + + /// SQL statement to execute. + public string Query { get; set; } = string.Empty; + + /// Optional named bind parameters. + public IDictionary? Params { get; set; } +} + /// /// Optional interface for subclasses that support /// per-session SQLite databases. Implement this interface on your provider to enable @@ -55,6 +73,52 @@ public interface ISessionFsSqliteProvider Task ExistsAsync(CancellationToken cancellationToken); } +/// +/// Optional capability for session filesystem providers that support atomic SQLite transactions. +/// +public interface ISessionFsSqliteTransactionProvider +{ + /// + /// Executes atomically against the per-session database. + /// + /// Statements to execute in order, inside a single transaction. + /// Cancellation token. + /// One result per statement, in the same order as . + /// + /// Thrown to tell the runtime how the failure should be classified. Any other exception + /// is reported as . + /// + Task> TransactionAsync( + IList statements, + CancellationToken cancellationToken); +} + +/// +/// Thrown by an to classify a failed SQLite transaction. +/// guarantees the transaction +/// rolled back and is safe to retry; +/// must never be retried. +/// +[Experimental(Diagnostics.Experimental)] +public sealed class SessionFsSqliteTransactionException : Exception +{ + /// Initializes a new instance of the class. + /// Human-readable failure description. + /// How the runtime should classify the failure. + /// Optional underlying exception. + public SessionFsSqliteTransactionException( + string message, + SessionFsSqliteTransactionErrorClass errorClass, + Exception? innerException = null) + : base(message, innerException) + { + ErrorClass = errorClass; + } + + /// Gets the failure classification reported to the runtime. + public SessionFsSqliteTransactionErrorClass ErrorClass { get; } +} + /// /// Base class for session filesystem providers. Subclasses override the /// virtual methods and use normal C# patterns (return values, throw exceptions). @@ -297,7 +361,7 @@ async Task ISessionFsHandler.SqliteQueryAsync(Sessio { Rows = result?.Rows?.Select(row => (IDictionary)row.ToDictionary( kvp => kvp.Key, - kvp => CopilotClient.ToJsonElementForWire(kvp.Value)!.Value)).ToList() ?? [], + kvp => ToJsonElement(kvp.Value))).ToList() ?? [], Columns = result?.Columns ?? [], RowsAffected = result?.RowsAffected ?? 0, LastInsertRowid = result?.LastInsertRowid, @@ -309,6 +373,78 @@ async Task ISessionFsHandler.SqliteQueryAsync(Sessio } } + async Task ISessionFsHandler.SqliteTransactionAsync(SessionFsSqliteTransactionRequest request, CancellationToken cancellationToken) + { + if (this is not ISessionFsSqliteTransactionProvider transactionProvider) + { + return new SessionFsSqliteTransactionResult + { + Error = new SessionFsSqliteTransactionError + { + ErrorClass = SessionFsSqliteTransactionErrorClass.Fatal, + Message = "SQLite is not supported by this provider.", + }, + }; + } + + IList results; + try + { + var statements = request.Statements.Select(statement => new SessionFsSqliteStatement + { + QueryType = statement.QueryType, + Query = statement.Query, + Params = statement.Params?.ToDictionary(kvp => kvp.Key, kvp => JsonElementToValue(kvp.Value)), + }).ToList(); + results = await transactionProvider.TransactionAsync(statements, cancellationToken).ConfigureAwait(false); + } + catch (SessionFsSqliteTransactionException ex) + { + return new SessionFsSqliteTransactionResult + { + Error = new SessionFsSqliteTransactionError { ErrorClass = ex.ErrorClass, Message = ex.Message }, + }; + } + catch (Exception ex) + { + return new SessionFsSqliteTransactionResult + { + Error = new SessionFsSqliteTransactionError + { + ErrorClass = SessionFsSqliteTransactionErrorClass.Fatal, + Message = ex.Message, + }, + }; + } + + try + { + return new SessionFsSqliteTransactionResult + { + Results = results.Select(result => new SessionFsSqliteQueryResult + { + Rows = result.Rows?.Select(row => (IDictionary)row.ToDictionary( + kvp => kvp.Key, + kvp => ToJsonElement(kvp.Value))).ToList() ?? [], + Columns = result.Columns ?? [], + RowsAffected = result.RowsAffected, + LastInsertRowid = result.LastInsertRowid, + }).ToList(), + }; + } + catch (Exception ex) + { + return new SessionFsSqliteTransactionResult + { + Error = new SessionFsSqliteTransactionError + { + ErrorClass = SessionFsSqliteTransactionErrorClass.PostCommitAmbiguous, + Message = ex.Message, + }, + }; + } + } + async Task ISessionFsHandler.SqliteExistsAsync(SessionFsSqliteExistsRequest request, CancellationToken cancellationToken) { if (this is not ISessionFsSqliteProvider sqliteProvider) @@ -336,6 +472,9 @@ private static SessionFsError ToSessionFsError(Exception ex) return new SessionFsError { Code = code, Message = ex.Message }; } + private static JsonElement ToJsonElement(object? value) => + CopilotClient.ToJsonElementForWire(value) ?? JsonElement.Parse("null"); + private static object? JsonElementToValue(JsonElement element) => element.ValueKind switch { JsonValueKind.Null => null, diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index 127cf4030..c0810b387 100644 --- a/dotnet/src/Types.cs +++ b/dotnet/src/Types.cs @@ -309,6 +309,7 @@ private CopilotClientOptions(CopilotClientOptions? other) Connection = other.Connection; WorkingDirectory = other.WorkingDirectory; BaseDirectory = other.BaseDirectory; + BuiltinPluginDirectories = other.BuiltinPluginDirectories is null ? null : [.. other.BuiltinPluginDirectories]; Environment = other.Environment; GitHubToken = other.GitHubToken; Logger = other.Logger; @@ -358,6 +359,13 @@ private CopilotClientOptions(CopilotClientOptions? other) /// public string? BaseDirectory { get; set; } + /// + /// Absolute paths to trusted plugin directories bundled by the host. + /// When non-empty, the complete set is registered with the runtime during + /// startup before sessions can be created. + /// + public IList? BuiltinPluginDirectories { get; set; } + /// /// Log level for the Copilot runtime. Use the well-known values on /// (, @@ -833,6 +841,9 @@ public sealed class PermissionInvocation /// Identifier of the session that triggered the permission request. /// public string SessionId { get; set; } = string.Empty; + + /// Whether managed settings are enabled for this session. + public bool ManagedSettingsEnabled { get; set; } } // ============================================================================ @@ -1653,6 +1664,55 @@ public sealed class UserPromptSubmittedHookOutput public bool? SuppressOutput { get; set; } } +/// +/// Input for a user-prompt-transformed hook. +/// +public sealed class UserPromptTransformedHookInput +{ + /// + /// The runtime session ID of the session that triggered the hook. + /// + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + + /// + /// Unix timestamp in milliseconds when the prompt was transformed. + /// + [JsonPropertyName("timestamp")] + [JsonConverter(typeof(UnixMillisecondsDateTimeOffsetConverter))] + public DateTimeOffset Timestamp { get; set; } + + /// + /// Current working directory of the session. + /// + [JsonPropertyName("cwd")] + public string WorkingDirectory { get; set; } = string.Empty; + + /// + /// The user prompt after any user-prompt-submitted hooks have run. + /// + [JsonPropertyName("prompt")] + public string Prompt { get; set; } = string.Empty; + + /// + /// The model-facing prompt after runtime transformations. + /// + [JsonPropertyName("transformedPrompt")] + public string TransformedPrompt { get; set; } = string.Empty; +} + +/// +/// Output for a user-prompt-transformed hook. +/// +public sealed class UserPromptTransformedHookOutput +{ + /// + /// Replacement model-facing prompt to persist and send to the model. + /// + [JsonPropertyName("modifiedTransformedPrompt")] + public string? ModifiedTransformedPrompt { get; set; } +} + /// /// Input for a session-start hook. /// @@ -1871,6 +1931,67 @@ public sealed class ErrorOccurredHookOutput public string? UserNotification { get; set; } } +/// +/// Input for an agent-stop hook. +/// +public sealed class AgentStopHookInput +{ + /// + /// The runtime session ID of the session that triggered the hook. + /// + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + + /// + /// Unix timestamp in milliseconds when the agent stopped. + /// + [JsonPropertyName("timestamp")] + [JsonConverter(typeof(UnixMillisecondsDateTimeOffsetConverter))] + public DateTimeOffset Timestamp { get; set; } + + /// + /// Current working directory of the session. + /// + [JsonPropertyName("cwd")] + public string WorkingDirectory { get; set; } = string.Empty; + + /// + /// Reason the agent stopped. + /// + [JsonPropertyName("stopReason")] + public string? StopReason { get; set; } + + /// + /// Path to the on-disk session transcript. + /// + [JsonPropertyName("transcriptPath")] + public string? TranscriptPath { get; set; } + + /// + /// Whether this stop follows a previous block decision from the hook. + /// + [JsonPropertyName("stop_hook_active")] + public bool? StopHookActive { get; set; } +} + +/// +/// Output for an agent-stop hook. +/// +public sealed class AgentStopHookOutput +{ + /// + /// Set to "block" to keep the agent running. + /// + [JsonPropertyName("decision")] + public string? Decision { get; set; } + + /// + /// Follow-up instruction supplied when the stop is blocked. + /// + [JsonPropertyName("reason")] + public string? Reason { get; set; } +} + /// /// Hook handlers configuration for a session. /// @@ -1903,6 +2024,11 @@ public sealed class SessionHooks /// public Func>? OnUserPromptSubmitted { get; set; } + /// + /// Handler called after the runtime transforms a submitted prompt and before it is stored. + /// + public Func>? OnUserPromptTransformed { get; set; } + /// /// Handler called when a session starts. /// @@ -1917,6 +2043,11 @@ public sealed class SessionHooks /// Handler called when an error occurs. /// public Func>? OnErrorOccurred { get; set; } + + /// + /// Handler called when the top-level agent reaches a natural stop. + /// + public Func>? OnAgentStop { get; set; } } /// @@ -2275,7 +2406,7 @@ public sealed class CapiSessionOptions public sealed class AzureOptions { /// - /// Azure OpenAI API version to use (e.g., "2024-02-01"). + /// Azure OpenAI API version. When omitted, the runtime uses the GA versionless v1 route. /// [JsonPropertyName("apiVersion")] public string? ApiVersion { get; set; } @@ -2660,8 +2791,8 @@ public sealed class CustomAgentConfig /// /// Reasoning effort level for this agent's model. - /// When omitted, no per-agent override is sent and the backend chooses its - /// default. The parent session effort is not inherited. + /// When omitted, the runtime resolves model configuration, then inherits + /// the parent effort only if this agent uses the same model. /// [JsonPropertyName("reasoningEffort")] public string? ReasoningEffort { get; set; } @@ -2891,6 +3022,100 @@ public sealed class CopilotExpAssignmentResponse public string AssignmentContext { get; set; } = string.Empty; } +/// +/// Configuration for the built-in GitHub MCP server. +/// +public sealed class GitHubMcpToolConfig +{ + /// Enables all GitHub MCP tools. + [JsonPropertyName("enableAllTools")] + public bool? EnableAllTools { get; set; } + + /// Additional GitHub MCP toolsets to enable. + [JsonPropertyName("additionalToolsets")] + public IList? AdditionalToolsets { get; set; } + + /// Additional GitHub MCP tools to enable. + [JsonPropertyName("additionalTools")] + public IList? AdditionalTools { get; set; } + + /// Enables GitHub MCP insiders-mode tools. + [JsonPropertyName("enableInsidersMode")] + public bool? EnableInsidersMode { get; set; } + + /// + /// Disables form deferral for GitHub MCP tools. This only applies to the + /// built-in GitHub MCP server and only has an effect when MCP Apps and + /// form-backed GitHub tools are enabled. + /// + [JsonPropertyName("disableFormDeferral")] + public bool? DisableFormDeferral { get; set; } +} + +/// +/// Controls whether bypass-permissions mode is available in a managed session. +/// +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum DisableBypassPermissionsMode +{ + /// Turn off bypass-permissions mode. + [JsonStringEnumMemberName("disable")] + Disable +} + +/// +/// Permission rules injected as a managed-settings layer at session bootstrap. +/// All fields are optional; omitted fields impose no constraint from this layer. +/// +/// +/// This layer composes restrictively with any server- or device-level managed +/// settings: and rules are unioned across +/// layers, every present list must admit a tool for it to be +/// allowed, and is honored if any +/// layer sets it (deny-wins). +/// +public sealed class ManagedSettingsPermissions +{ + /// + /// When set to "disable", bypass-permissions mode is turned off for the + /// session regardless of other layers. Serialized as + /// disableBypassPermissionsMode. + /// + [JsonPropertyName("disableBypassPermissionsMode")] + public DisableBypassPermissionsMode? DisableBypassPermissionsMode { get; set; } + + /// Tool-permission patterns that are always denied. + [JsonPropertyName("deny")] + public IList? Deny { get; set; } + + /// Tool-permission patterns that require an explicit ask. + [JsonPropertyName("ask")] + public IList? Ask { get; set; } + + /// Tool-permission patterns that are allowed without prompting. + [JsonPropertyName("allow")] + public IList? Allow { get; set; } +} + +/// +/// Managed-settings layer injected at session startup. Currently carries only a +/// object. +/// +/// +/// This layer is startup-only and is not persisted with the session. It must be +/// re-supplied on to remain in +/// effect; omitting it on resume clears the previously injected layer. It can be +/// combined with . Older +/// runtimes may ignore this additive field, so hosts must not rely on injected +/// policy until they ship a compatible runtime. +/// +public sealed class ManagedSettings +{ + /// Permission rules for this managed-settings layer. + [JsonPropertyName("permissions")] + public ManagedSettingsPermissions? Permissions { get; set; } +} + /// /// Shared configuration properties for creating or resuming a Copilot session. /// Use when creating a new session, or @@ -2917,7 +3142,9 @@ protected SessionConfigBase(SessionConfigBase? other) DefaultAgent = other.DefaultAgent; Agent = other.Agent; DisabledSkills = other.DisabledSkills is not null ? [.. other.DisabledSkills] : null; + DisabledMcpServers = other.DisabledMcpServers is not null ? [.. other.DisabledMcpServers] : null; EnableCitations = other.EnableCitations; + EnableFileChangeTracking = other.EnableFileChangeTracking; EnableConfigDiscovery = other.EnableConfigDiscovery; SkipEmbeddingRetrieval = other.SkipEmbeddingRetrieval; EmbeddingCacheStorage = other.EmbeddingCacheStorage; @@ -2928,6 +3155,20 @@ protected SessionConfigBase(SessionConfigBase? other) EnableSessionStore = other.EnableSessionStore; EnableSkills = other.EnableSkills; EnableMcpApps = other.EnableMcpApps; + GitHubMcpToolConfig = other.GitHubMcpToolConfig is null + ? null + : new GitHubMcpToolConfig + { + EnableAllTools = other.GitHubMcpToolConfig.EnableAllTools, + AdditionalToolsets = other.GitHubMcpToolConfig.AdditionalToolsets is not null + ? [.. other.GitHubMcpToolConfig.AdditionalToolsets] + : null, + AdditionalTools = other.GitHubMcpToolConfig.AdditionalTools is not null + ? [.. other.GitHubMcpToolConfig.AdditionalTools] + : null, + EnableInsidersMode = other.GitHubMcpToolConfig.EnableInsidersMode, + DisableFormDeferral = other.GitHubMcpToolConfig.DisableFormDeferral, + }; ExcludedBuiltInAgents = other.ExcludedBuiltInAgents is not null ? [.. other.ExcludedBuiltInAgents] : null; ExcludedTools = other.ExcludedTools is not null ? [.. other.ExcludedTools] : null; Hooks = other.Hooks; @@ -2955,6 +3196,7 @@ protected SessionConfigBase(SessionConfigBase? other) Providers = other.Providers is not null ? [.. other.Providers] : null; Models = other.Models is not null ? [.. other.Models] : null; EnableSessionTelemetry = other.EnableSessionTelemetry; + EnableExperimentalMode = other.EnableExperimentalMode; SkipCustomInstructions = other.SkipCustomInstructions; CustomAgentsLocalOnly = other.CustomAgentsLocalOnly; CoauthorEnabled = other.CoauthorEnabled; @@ -2967,6 +3209,7 @@ protected SessionConfigBase(SessionConfigBase? other) RemoteSession = other.RemoteSession; ExpAssignments = other.ExpAssignments; EnableManagedSettings = other.EnableManagedSettings; + ManagedSettings = other.ManagedSettings; #pragma warning disable GHCP001 Canvases = other.Canvases is not null ? [.. other.Canvases] : null; RequestCanvasRenderer = other.RequestCanvasRenderer; @@ -2985,6 +3228,7 @@ protected SessionConfigBase(SessionConfigBase? other) SystemMessage = other.SystemMessage; Tools = other.Tools is not null ? [.. other.Tools] : null; WorkingDirectory = other.WorkingDirectory; + AdditionalDirectories = other.AdditionalDirectories is not null ? [.. other.AdditionalDirectories] : null; } /// Client name to identify the application using the SDK. @@ -2995,7 +3239,7 @@ protected SessionConfigBase(SessionConfigBase? other) /// /// Reasoning effort level for models that support it. - /// Valid values: "low", "medium", "high", "xhigh". + /// Valid values: "low", "medium", "high", "xhigh", "max". /// Only applies to models where capabilities.supports.reasoningEffort is true. /// public string? ReasoningEffort { get; set; } @@ -3028,6 +3272,17 @@ protected SessionConfigBase(SessionConfigBase? other) [Experimental(Diagnostics.Experimental)] public bool? EnableCitations { get; set; } + /// + /// Opts in to capturing file changes for session rewind and cumulative + /// session diff. + /// + /// + /// On create, capture starts with the first turn. On resume, tracking can be + /// enabled only when the session still has a valid baseline; earlier untracked + /// changes cannot be reconstructed. + /// + public bool? EnableFileChangeTracking { get; set; } + /// /// Override the default configuration directory location. /// When specified, the session will use this directory for storing config and state. @@ -3035,15 +3290,8 @@ protected SessionConfigBase(SessionConfigBase? other) public string? ConfigDirectory { get; set; } /// - /// When , automatically discovers MCP server configurations - /// (e.g. .mcp.json, .vscode/mcp.json) and skill directories from - /// the working directory and merges them with any explicitly provided - /// and , with explicit - /// values taking precedence on name collision. - /// - /// Custom instruction files (.github/copilot-instructions.md, AGENTS.md, etc.) - /// are always loaded from the working directory regardless of this setting. - /// + /// Enables runtime discovery of supported configuration. Explicitly supplied + /// configuration takes precedence over discovered values. /// public bool? EnableConfigDiscovery { get; set; } @@ -3164,6 +3412,15 @@ protected SessionConfigBase(SessionConfigBase? other) /// public bool? EnableSessionTelemetry { get; set; } + /// + /// Controls whether the session enables experimental features. + /// + /// + /// Defaults to in . + /// Otherwise, the runtime decides when left . + /// + public bool? EnableExperimentalMode { get; set; } + /// /// When , suppresses loading of custom instruction files /// (e.g. .github/copilot-instructions.md, AGENTS.md) from the working directory. @@ -3243,12 +3500,25 @@ protected SessionConfigBase(SessionConfigBase? other) [Experimental(Diagnostics.Experimental)] public bool EnableMcpApps { get; set; } + /// + /// Configuration for the built-in GitHub MCP server. + /// DisableFormDeferral only applies to that server and only has an + /// effect when MCP Apps and form-backed GitHub tools are enabled. + /// + public GitHubMcpToolConfig? GitHubMcpToolConfig { get; set; } + /// Hook handlers for session lifecycle events. public SessionHooks? Hooks { get; set; } /// Working directory for the session. public string? WorkingDirectory { get; set; } + /// + /// Additional directories the agent may access beyond . + /// Relative paths resolve against the session working directory. Re-supply them when resuming. + /// + public IList? AdditionalDirectories { get; set; } + /// /// Enable streaming of assistant message and reasoning chunks. /// When true, assistant.message_delta and assistant.reasoning_delta events @@ -3316,6 +3586,13 @@ protected SessionConfigBase(SessionConfigBase? other) /// List of skill names to disable. public IList? DisabledSkills { get; set; } + /// + /// Exact MCP server names to disable for this session. Disabled servers are not + /// started or authenticated on create or cold resume; a resident resume cannot + /// stop servers that are already running. + /// + public IList? DisabledMcpServers { get; set; } + /// /// Infinite session configuration for persistent workspaces and automatic compaction. /// When enabled (default), sessions automatically manage context limits and persist state. @@ -3409,6 +3686,17 @@ protected SessionConfigBase(SessionConfigBase? other) /// public bool? EnableManagedSettings { get; set; } + /// + /// Optional managed-settings layer injected at session bootstrap. Currently + /// carries a permissions object that composes restrictively with any + /// server- or device-level managed settings. This layer is startup-only and + /// is not persisted: it must be re-supplied on resume to remain in effect, + /// and omitting it on resume clears the previously injected layer. Can be + /// combined with . Serialized on the wire + /// as managedSettings. + /// + public ManagedSettings? ManagedSettings { get; set; } + #pragma warning disable GHCP001 /// /// Canvas declarations advertised by this connection. The runtime forwards diff --git a/dotnet/test/E2E/AskUserE2ETests.cs b/dotnet/test/E2E/AskUserE2ETests.cs index db1a4dd92..e08ba10cb 100644 --- a/dotnet/test/E2E/AskUserE2ETests.cs +++ b/dotnet/test/E2E/AskUserE2ETests.cs @@ -30,13 +30,11 @@ public async Task Should_Invoke_User_Input_Handler_When_Model_Uses_Ask_User_Tool } }); - await session.SendAsync(new MessageOptions + await session.SendAndWaitAsync(new MessageOptions { Prompt = "Ask me to choose between 'Option A' and 'Option B' using the ask_user tool. Wait for my response before continuing." }); - await TestHelper.GetFinalAssistantMessageAsync(session); - // Should have received at least one user input request Assert.NotEmpty(userInputRequests); @@ -62,13 +60,11 @@ public async Task Should_Receive_Choices_In_User_Input_Request() } }); - await session.SendAsync(new MessageOptions + await session.SendAndWaitAsync(new MessageOptions { Prompt = "Use the ask_user tool to ask me to pick between exactly two options: 'Red' and 'Blue'. These should be provided as choices. Wait for my answer." }); - await TestHelper.GetFinalAssistantMessageAsync(session); - // Should have received a request Assert.NotEmpty(userInputRequests); diff --git a/dotnet/test/E2E/ClientE2ETests.cs b/dotnet/test/E2E/ClientE2ETests.cs index d166223d6..b6bdfd90f 100644 --- a/dotnet/test/E2E/ClientE2ETests.cs +++ b/dotnet/test/E2E/ClientE2ETests.cs @@ -11,6 +11,9 @@ namespace GitHub.Copilot.Test.E2E; // Other test classes should instead inherit from E2ETestBase public class ClientE2ETests(E2ETestFixture fixture) : IClassFixture { + private const string FailingCliScript = + "process.stderr.write('nonexistent test flag on stderr\\n'); process.exit(1);"; + private E2ETestContext Ctx => fixture.Ctx; [Theory] @@ -177,11 +180,14 @@ public async Task Should_Not_Throw_When_Disposing_Session_After_Stopping_Client( [InlineData(false)] // TCP transport public async Task Should_Report_Error_With_Stderr_When_CLI_Fails_To_Start(bool useStdio) { + var cliPath = Path.Join(Ctx.WorkDir, $"failing-cli-{Guid.NewGuid():N}.js"); + await File.WriteAllTextAsync(cliPath, FailingCliScript); + var client = new CopilotClient(new CopilotClientOptions { Connection = useStdio - ? RuntimeConnection.ForStdio(args: ["--nonexistent-flag-for-testing"]) - : RuntimeConnection.ForTcp(args: ["--nonexistent-flag-for-testing"]) + ? RuntimeConnection.ForStdio(path: cliPath) + : RuntimeConnection.ForTcp(path: cliPath) }); var ex = await Assert.ThrowsAsync(() => client.StartAsync()); diff --git a/dotnet/test/E2E/ClientOptionsE2ETests.cs b/dotnet/test/E2E/ClientOptionsE2ETests.cs index bf995563c..5391e4bdb 100644 --- a/dotnet/test/E2E/ClientOptionsE2ETests.cs +++ b/dotnet/test/E2E/ClientOptionsE2ETests.cs @@ -179,6 +179,65 @@ public async Task Should_Omit_EnableSessionTelemetry_When_Not_Set() await session.DisposeAsync(); } + [Fact] + public async Task Should_Forward_CustomAgentsLocalOnly_In_Create_Wire_Request() + { + var (cliPath, capturePath) = await CreateFakeCliCaptureAsync(); + + await using var client = Ctx.CreateClient(options: new CopilotClientOptions + { + Connection = RuntimeConnection.ForStdio(path: cliPath, args: ["--capture-file", capturePath]), + UseLoggedInUser = false, + }); + + await client.StartAsync(); + + var session = await client.CreateSessionAsync(new SessionConfig + { + CustomAgentsLocalOnly = false, + OnPermissionRequest = PermissionHandler.ApproveAll, + }); + + using var capture = JsonDocument.Parse(await File.ReadAllTextAsync(capturePath)); + var createRequest = GetCapturedRequestParams(capture.RootElement, "session.create"); + Assert.False(createRequest.GetProperty("customAgentsLocalOnly").GetBoolean()); + + await session.DisposeAsync(); + } + + [Fact] + public async Task Should_Forward_CustomAgentsLocalOnly_In_Resume_Wire_Request() + { + var (cliPath, capturePath) = await CreateFakeCliCaptureAsync(); + + await using var client = Ctx.CreateClient(options: new CopilotClientOptions + { + Connection = RuntimeConnection.ForStdio(path: cliPath, args: ["--capture-file", capturePath]), + UseLoggedInUser = false, + }); + + await client.StartAsync(); + + var createSession = await client.CreateSessionAsync(new SessionConfig + { + OnPermissionRequest = PermissionHandler.ApproveAll, + }); + var sessionId = createSession.SessionId; + await createSession.DisposeAsync(); + + var resumeSession = await client.ResumeSessionAsync(sessionId, new ResumeSessionConfig + { + CustomAgentsLocalOnly = false, + OnPermissionRequest = PermissionHandler.ApproveAll, + }); + + using var capture = JsonDocument.Parse(await File.ReadAllTextAsync(capturePath)); + var resumeRequest = GetCapturedRequestParams(capture.RootElement, "session.resume"); + Assert.False(resumeRequest.GetProperty("customAgentsLocalOnly").GetBoolean()); + + await resumeSession.DisposeAsync(); + } + [Fact] public async Task Should_Forward_Granular_Multitenancy_Fields_In_Create_Wire_Request() { @@ -451,6 +510,7 @@ public async Task Should_Apply_Empty_Mode_Defaults_To_CreateSession_Wire_Request Assert.False(createRequest.GetProperty("enableHostGitOperations").GetBoolean()); Assert.False(createRequest.GetProperty("enableSessionStore").GetBoolean()); Assert.False(createRequest.GetProperty("enableSkills").GetBoolean()); + Assert.True(createRequest.GetProperty("customAgentsLocalOnly").GetBoolean()); Assert.False(createRequest.TryGetProperty("organizationCustomInstructions", out _)); await session.DisposeAsync(); @@ -725,6 +785,7 @@ public async Task Should_Apply_Empty_Mode_Defaults_To_ResumeSession_Wire_Request Assert.False(resumeRequest.GetProperty("enableHostGitOperations").GetBoolean()); Assert.False(resumeRequest.GetProperty("enableSessionStore").GetBoolean()); Assert.False(resumeRequest.GetProperty("enableSkills").GetBoolean()); + Assert.True(resumeRequest.GetProperty("customAgentsLocalOnly").GetBoolean()); Assert.False(resumeRequest.TryGetProperty("organizationCustomInstructions", out _)); await session.DisposeAsync(); diff --git a/dotnet/test/E2E/CommandsE2ETests.cs b/dotnet/test/E2E/CommandsE2ETests.cs index 20db2d7cb..5b778f9cc 100644 --- a/dotnet/test/E2E/CommandsE2ETests.cs +++ b/dotnet/test/E2E/CommandsE2ETests.cs @@ -30,7 +30,7 @@ public async Task Session_Commands_List_Returns_Builtins_And_Respects_Client_Com await TestHelper.WaitForConditionAsync( async () => { - clientCommands = await session.Rpc.Commands.ListAsync(new CommandsListRequest + clientCommands = await session.Rpc.Commands.ListAsync(new SessionCommandsListRequest { IncludeBuiltins = false, IncludeClientCommands = true, @@ -45,7 +45,7 @@ await TestHelper.WaitForConditionAsync( Assert.Contains(clientCommands.Commands, c => IsCommand(c, "rollback", SlashCommandKind.Client)); Assert.DoesNotContain(clientCommands.Commands, c => c.Kind == SlashCommandKind.Builtin); - var builtinCommands = await session.Rpc.Commands.ListAsync(new CommandsListRequest + var builtinCommands = await session.Rpc.Commands.ListAsync(new SessionCommandsListRequest { IncludeBuiltins = true, IncludeClientCommands = false, @@ -64,7 +64,7 @@ public async Task Session_Commands_Invoke_Known_Builtin_Returns_Expected_Result( { var session = await CreateSessionAsync(); - var builtinCommands = await session.Rpc.Commands.ListAsync(new CommandsListRequest + var builtinCommands = await session.Rpc.Commands.ListAsync(new SessionCommandsListRequest { IncludeBuiltins = true, IncludeClientCommands = false, @@ -128,7 +128,7 @@ public async Task Session_Commands_Execute_Runs_Registered_Command_Handler() await TestHelper.WaitForConditionAsync( async () => { - var commands = await session.Rpc.Commands.ListAsync(new CommandsListRequest + var commands = await session.Rpc.Commands.ListAsync(new SessionCommandsListRequest { IncludeBuiltins = false, IncludeClientCommands = true, @@ -202,8 +202,9 @@ public async Task Session_With_Commands_Creates_Successfully() [Fact] public async Task Session_With_Commands_Resumes_Successfully() { - var session1 = await CreateSessionAsync(); + await using var session1 = await CreateSessionAsync(); var sessionId = session1.SessionId; + await SuspendAndUntrackSessionForResumeAsync(session1); var session2 = await ResumeSessionAsync(sessionId, new ResumeSessionConfig { diff --git a/dotnet/test/E2E/HookLifecycleAndOutputE2ETests.cs b/dotnet/test/E2E/HookLifecycleAndOutputE2ETests.cs index b9366c134..decdb3190 100644 --- a/dotnet/test/E2E/HookLifecycleAndOutputE2ETests.cs +++ b/dotnet/test/E2E/HookLifecycleAndOutputE2ETests.cs @@ -11,8 +11,9 @@ namespace GitHub.Copilot.Test.E2E; /// /// E2E coverage for every handler exposed on : /// OnPreToolUse, OnPostToolUse, OnPostToolUseFailure, OnUserPromptSubmitted, -/// OnSessionStart, OnSessionEnd, OnErrorOccurred. Output-shape behavior -/// (modifiedPrompt / additionalContext / errorHandling / modifiedArgs / +/// OnUserPromptTransformed, OnSessionStart, OnSessionEnd, OnErrorOccurred, +/// OnAgentStop. Output-shape behavior (modifiedPrompt / modifiedTransformedPrompt / +/// additionalContext / errorHandling / modifiedArgs / /// modifiedResult / sessionSummary) is asserted alongside hook invocation. If a /// new handler is added to SessionHooks, add a corresponding test here. /// @@ -163,6 +164,37 @@ public async Task Should_Invoke_UserPromptSubmitted_Hook_And_Modify_Prompt() Assert.Contains("HOOKED_PROMPT", response?.Data.Content ?? string.Empty); } + [Fact] + public async Task Should_Invoke_UserPromptTransformed_Hook_And_Modify_Transformed_Prompt() + { + var inputs = new List(); + var session = await CreateSessionAsync(new SessionConfig + { + Hooks = new SessionHooks + { + OnUserPromptTransformed = (input, invocation) => + { + inputs.Add(input); + Assert.False(string.IsNullOrWhiteSpace(invocation.SessionId)); + return Task.FromResult(new UserPromptTransformedHookOutput + { + ModifiedTransformedPrompt = "Reply with exactly: HOOKED_TRANSFORMED_PROMPT", + }); + }, + }, + }); + + var response = await session.SendAndWaitAsync(new MessageOptions { Prompt = "Answer the request above." }); + + Assert.NotEmpty(inputs); + Assert.Contains("Answer the request above.", inputs[0].Prompt); + Assert.Contains("Answer the request above.", inputs[0].TransformedPrompt); + Assert.Contains("", inputs[0].TransformedPrompt); + Assert.True(inputs[0].Timestamp > DateTimeOffset.UnixEpoch); + Assert.False(string.IsNullOrEmpty(inputs[0].WorkingDirectory)); + Assert.Contains("HOOKED_TRANSFORMED_PROMPT", response?.Data.Content ?? string.Empty); + } + [Fact] public async Task Should_Invoke_SessionStart_Hook() { @@ -255,6 +287,45 @@ await session.SendAndWaitAsync(new MessageOptions Assert.NotNull(session.SessionId); } + [Fact] + public async Task Should_Invoke_AgentStop_Hook_And_Apply_Block_Response() + { + var inputs = new List(); + var session = await CreateSessionAsync(new SessionConfig + { + Hooks = new SessionHooks + { + OnAgentStop = (input, invocation) => + { + inputs.Add(input); + Assert.False(string.IsNullOrWhiteSpace(invocation.SessionId)); + if (inputs.Count == 1) + { + return Task.FromResult(new AgentStopHookOutput + { + Decision = "block", + Reason = "Reply with exactly: AGENT_STOP_CONTINUED", + }); + } + + return Task.FromResult(null); + }, + }, + }); + + var response = await session.SendAndWaitAsync(new MessageOptions + { + Prompt = "Reply with exactly: AGENT_STOP_INITIAL", + }); + + Assert.Equal(2, inputs.Count); + Assert.NotEqual(true, inputs[0].StopHookActive); + Assert.True(inputs[1].StopHookActive); + Assert.Equal("end_turn", inputs[0].StopReason); + Assert.False(string.IsNullOrWhiteSpace(inputs[0].TranscriptPath)); + Assert.Contains("AGENT_STOP_CONTINUED", response?.Data.Content ?? string.Empty); + } + [Fact] public async Task Should_Allow_PreToolUse_To_Return_ModifiedArgs_And_SuppressOutput() { diff --git a/dotnet/test/E2E/InMemorySessionFsSqliteHandler.cs b/dotnet/test/E2E/InMemorySessionFsSqliteHandler.cs index 4e573ff5c..caf49fa6d 100644 --- a/dotnet/test/E2E/InMemorySessionFsSqliteHandler.cs +++ b/dotnet/test/E2E/InMemorySessionFsSqliteHandler.cs @@ -17,7 +17,7 @@ internal record SqliteCall(string SessionId, string QueryType, string Query); /// for file operations instead of touching disk. /// internal sealed class InMemorySessionFsSqliteHandler(string sessionId, List sqliteCalls) - : SessionFsProvider, ISessionFsSqliteProvider + : SessionFsProvider, ISessionFsSqliteProvider, ISessionFsSqliteTransactionProvider { internal ConcurrentDictionary Files { get; } = new(); private readonly ConcurrentDictionary _directories = new(); @@ -45,28 +45,82 @@ private SqliteConnection GetOrCreateDb() string query, IDictionary? bindParams, CancellationToken cancellationToken) + { + return Task.FromResult(RunStatement(GetOrCreateDb(), null, queryType, query, bindParams)); + } + + public Task> TransactionAsync( + IList statements, + CancellationToken cancellationToken) + { + var db = GetOrCreateDb(); + using var transaction = db.BeginTransaction(); + try + { + IList results = statements + .Select(statement => RunStatement(db, transaction, statement.QueryType, statement.Query, statement.Params) + ?? new SessionFsSqliteResult()) + .ToList(); + try + { + transaction.Commit(); + } + catch (Exception ex) + { + throw new SessionFsSqliteTransactionException( + ex.Message, + SessionFsSqliteTransactionErrorClass.PostCommitAmbiguous, + ex); + } + return Task.FromResult(results); + } + catch (SessionFsSqliteTransactionException) + { + throw; + } + catch (SqliteException ex) + { + transaction.Rollback(); + var errorClass = ex.SqliteErrorCode is 5 or 6 + ? SessionFsSqliteTransactionErrorClass.BusyOrLocked + : SessionFsSqliteTransactionErrorClass.Fatal; + throw new SessionFsSqliteTransactionException(ex.Message, errorClass, ex); + } + catch (Exception ex) + { + transaction.Rollback(); + throw new SessionFsSqliteTransactionException(ex.Message, SessionFsSqliteTransactionErrorClass.Fatal, ex); + } + } + + private SessionFsSqliteResult? RunStatement( + SqliteConnection db, + SqliteTransaction? transaction, + SessionFsSqliteQueryType queryType, + string query, + IDictionary? bindParams) { sqliteCalls.Add(new SqliteCall(sessionId, queryType.Value, query)); var trimmed = query.Trim(); if (trimmed.Length == 0) { - return Task.FromResult(null); + return null; } - var db = GetOrCreateDb(); - if (queryType == SessionFsSqliteQueryType.Exec) { using var cmd = db.CreateCommand(); + cmd.Transaction = transaction; cmd.CommandText = trimmed; cmd.ExecuteNonQuery(); - return Task.FromResult(null); + return null; } if (queryType == SessionFsSqliteQueryType.Query) { using var cmd = db.CreateCommand(); + cmd.Transaction = transaction; cmd.CommandText = trimmed; AddParams(cmd, bindParams); @@ -88,33 +142,35 @@ private SqliteConnection GetOrCreateDb() rows.Add(row); } - return Task.FromResult(new SessionFsSqliteResult + return new SessionFsSqliteResult { Columns = columns, Rows = rows, RowsAffected = 0, - }); + }; } if (queryType == SessionFsSqliteQueryType.Run) { using var cmd = db.CreateCommand(); + cmd.Transaction = transaction; cmd.CommandText = trimmed; AddParams(cmd, bindParams); var rowsAffected = cmd.ExecuteNonQuery(); using var rowidCmd = db.CreateCommand(); + rowidCmd.Transaction = transaction; rowidCmd.CommandText = "SELECT last_insert_rowid()"; var lastRowid = rowidCmd.ExecuteScalar(); - return Task.FromResult(new SessionFsSqliteResult + return new SessionFsSqliteResult { Columns = [], Rows = [], RowsAffected = rowsAffected, LastInsertRowid = lastRowid is long l ? l : null, - }); + }; } throw new ArgumentException($"Unknown queryType: {queryType}"); diff --git a/dotnet/test/E2E/RewindE2ETests.cs b/dotnet/test/E2E/RewindE2ETests.cs new file mode 100644 index 000000000..ced06b93f --- /dev/null +++ b/dotnet/test/E2E/RewindE2ETests.cs @@ -0,0 +1,81 @@ +// Copyright (c) GitHub, Inc. +// Licensed under the MIT License. + +using GitHub.Copilot.Rpc; +using GitHub.Copilot.Test.Harness; + +using Xunit; +using Xunit.Abstractions; + +namespace GitHub.Copilot.Test.E2E; + +public class RewindE2ETests(E2ETestFixture fixture, ITestOutputHelper output) + : E2ETestBase(fixture, "rewind", output) +{ + private const string FileName = "rewind-sdk.txt"; + private const string FileContent = "SDK rewind content"; + + [Fact] + public async Task Should_Restore_Tracked_File_And_Conversation() + { + var filePath = Path.Join(Ctx.WorkDir, FileName); + await using var session = await CreateSessionAsync(new SessionConfig + { + Model = "claude-sonnet-4.5", + EnableFileChangeTracking = true, + }); + + var response = await session.SendAndWaitAsync( + new MessageOptions + { + Prompt = $"Use the create tool to create {FileName} containing exactly {FileContent}. " + + "After the tool succeeds, reply with exactly SDK_REWIND_DONE.", + }, + TimeSpan.FromSeconds(30)); + + Assert.Equal("SDK_REWIND_DONE", response?.Data.Content); + Assert.True(File.Exists(filePath)); + Assert.Equal(FileContent, await File.ReadAllTextAsync(filePath)); + + HistoryListRewindPointsResult? rewindPoints = null; + await TestHelper.WaitForConditionAsync( + async () => + { + rewindPoints = await session.Rpc.History.ListRewindPointsAsync(); + return rewindPoints.UnavailableReason is null; + }, + timeout: TimeSpan.FromSeconds(10), + timeoutMessage: "Timed out waiting for rewind points to become available.", + pollInterval: TimeSpan.FromMilliseconds(100)); + + Assert.NotNull(rewindPoints); + Assert.True(rewindPoints.FileChangeTrackingEnabled); + var rewindPoint = Assert.Single(rewindPoints.Points); + Assert.True(rewindPoint.CanRestoreFiles); + Assert.Equal(1, rewindPoint.FileCount); + + var preview = await session.Rpc.History.PreviewRewindAsync(rewindPoint.EventId); + Assert.True(preview.Available); + var previewFile = Assert.Single(preview.Files); + Assert.Equal( + Path.GetFullPath(filePath), + Path.GetFullPath(previewFile.Path), + OperatingSystem.IsWindows() ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal); + + var rewind = await session.Rpc.History.RewindAsync( + rewindPoint.EventId, + HistoryRewindMode.ConversationAndFiles); + + Assert.Equal(HistoryRewindOutcome.Success, rewind.Outcome); + Assert.True(rewind.EventsRemoved > 0); + var restoredFile = Assert.Single(rewind.RestoredFiles); + Assert.Equal( + Path.GetFullPath(filePath), + Path.GetFullPath(restoredFile), + OperatingSystem.IsWindows() ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal); + Assert.False(File.Exists(filePath)); + + var events = await session.GetEventsAsync(); + Assert.DoesNotContain(events, sessionEvent => sessionEvent.Id.ToString() == rewindPoint.EventId); + } +} diff --git a/dotnet/test/E2E/RpcShellAndFleetE2ETests.cs b/dotnet/test/E2E/RpcShellAndFleetE2ETests.cs index a51fc7dae..2946b7bbe 100644 --- a/dotnet/test/E2E/RpcShellAndFleetE2ETests.cs +++ b/dotnet/test/E2E/RpcShellAndFleetE2ETests.cs @@ -28,7 +28,7 @@ public async Task Should_Execute_Shell_Command() [Fact] public async Task Should_Kill_Shell_Process() { - var session = await CreateSessionAsync(); + await using var session = await CreateSessionAsync(); var command = OperatingSystem.IsWindows() ? "powershell -NoLogo -NoProfile -Command \"Start-Sleep -Seconds 30\"" : "sleep 30"; diff --git a/dotnet/test/E2E/RpcTasksAndHandlersE2ETests.cs b/dotnet/test/E2E/RpcTasksAndHandlersE2ETests.cs index 989fef7c5..640e4f72f 100644 --- a/dotnet/test/E2E/RpcTasksAndHandlersE2ETests.cs +++ b/dotnet/test/E2E/RpcTasksAndHandlersE2ETests.cs @@ -152,29 +152,38 @@ await TestHelper.WaitForConditionAsync( async () => { task = await FindAgentTaskAsync(session, started.AgentId); - return task?.LatestResponse?.Contains("TASK_AGENT_DONE", StringComparison.Ordinal) == true - || task?.Result?.Contains("TASK_AGENT_DONE", StringComparison.Ordinal) == true - || task?.Status == GitHub.Copilot.Rpc.TaskStatus.Completed - || task?.Status == GitHub.Copilot.Rpc.TaskStatus.Failed; + return task is null + || task.Status == GitHub.Copilot.Rpc.TaskStatus.Completed + || task.Status == GitHub.Copilot.Rpc.TaskStatus.Failed + || task.Status == GitHub.Copilot.Rpc.TaskStatus.Cancelled + || task.Status == GitHub.Copilot.Rpc.TaskStatus.Idle; }, timeout: TimeSpan.FromSeconds(60), timeoutMessage: $"Background agent task '{started.AgentId}' did not produce a final observable state."); - Assert.NotNull(task); - Assert.Contains("TASK_AGENT_DONE", task.LatestResponse ?? task.Result ?? string.Empty); - await taskCompletionNotification.Task.WaitAsync(TimeSpan.FromSeconds(30)); - - if (task.Status == GitHub.Copilot.Rpc.TaskStatus.Idle) + if (task is not null) { - var cancel = await session.Rpc.Tasks.CancelAsync(started.AgentId); - Assert.True(cancel.Cancelled); - } + Assert.Contains("TASK_AGENT_DONE", task.LatestResponse ?? task.Result ?? string.Empty); + + if (task.Status == GitHub.Copilot.Rpc.TaskStatus.Idle) + { + var cancel = await session.Rpc.Tasks.CancelAsync(started.AgentId); + Assert.True(cancel.Cancelled); + } - var remove = await session.Rpc.Tasks.RemoveAsync(started.AgentId); - Assert.True(remove.Removed); + var remove = await session.Rpc.Tasks.RemoveAsync(started.AgentId); + // Completion delivery also removes finished tasks, so this call may lose that race. + Assert.True( + remove.Removed || taskCompletionNotification.Task.IsCompleted, + $"Background agent task '{started.AgentId}' was not removed before its completion notification was delivered."); + } var afterRemove = await session.Rpc.Tasks.ListAsync(); - Assert.DoesNotContain(afterRemove.Tasks.OfType(), t => string.Equals(t.Id, started.AgentId, StringComparison.Ordinal)); + var taskAfterRemove = afterRemove.Tasks.OfType() + .SingleOrDefault(t => string.Equals(t.Id, started.AgentId, StringComparison.Ordinal)); + Assert.Null(taskAfterRemove); + + await taskCompletionNotification.Task.WaitAsync(TimeSpan.FromSeconds(30)); } [Fact] diff --git a/dotnet/test/E2E/RpcWorkspaceCheckpointsE2ETests.cs b/dotnet/test/E2E/RpcWorkspaceCheckpointsE2ETests.cs index ea4ae15b4..092b28971 100644 --- a/dotnet/test/E2E/RpcWorkspaceCheckpointsE2ETests.cs +++ b/dotnet/test/E2E/RpcWorkspaceCheckpointsE2ETests.cs @@ -29,7 +29,7 @@ public async Task Should_Return_Null_Or_Empty_Content_For_Unknown_Checkpoint() { await using var session = await CreateSessionAsync(); - var result = await session.Rpc.Workspaces.ReadCheckpointAsync(long.MaxValue); + var result = await session.Rpc.Workspaces.ReadCheckpointAsync(uint.MaxValue); Assert.True(string.IsNullOrEmpty(result.Content)); } diff --git a/dotnet/test/E2E/SessionConfigE2ETests.cs b/dotnet/test/E2E/SessionConfigE2ETests.cs index ad313b116..1bc4c52eb 100644 --- a/dotnet/test/E2E/SessionConfigE2ETests.cs +++ b/dotnet/test/E2E/SessionConfigE2ETests.cs @@ -173,9 +173,11 @@ public async Task Should_Apply_All_ReasoningEffort_Values_On_Session_Create(stri [Trait(E2ETestTraits.Backend, E2ETestTraits.SelfConfiguredBackend)] public async Task Should_Apply_ReasoningEffort_On_Session_Resume() { - var originalSession = await CreateSessionAsync(); + await using var originalSession = await CreateSessionAsync(); + var sessionId = originalSession.SessionId; + await SuspendAndUntrackSessionForResumeAsync(originalSession); const string reasoningModelId = "custom-reasoning-model"; - var resumedSession = await ResumeSessionAsync(originalSession.SessionId, new ResumeSessionConfig + var resumedSession = await ResumeSessionAsync(sessionId, new ResumeSessionConfig { Model = reasoningModelId, Provider = CreateProxyProvider("resume-reasoning"), @@ -187,7 +189,6 @@ public async Task Should_Apply_ReasoningEffort_On_Session_Resume() Assert.Equal("high", resumeEvent.Data.ReasoningEffort); await resumedSession.DisposeAsync(); - await originalSession.DisposeAsync(); } [Fact] @@ -233,8 +234,9 @@ public async Task Should_Forward_Custom_Provider_Headers_On_Create() [Trait(E2ETestTraits.Backend, E2ETestTraits.SelfConfiguredBackend)] public async Task Should_Forward_Custom_Provider_Headers_On_Resume() { - var session1 = await CreateSessionAsync(); + await using var session1 = await CreateSessionAsync(); var sessionId = session1.SessionId; + await SuspendAndUntrackSessionForResumeAsync(session1); var session2 = await ResumeSessionAsync(sessionId, new ResumeSessionConfig { @@ -339,8 +341,9 @@ public async Task Should_Apply_WorkingDirectory_On_Session_Resume() Directory.CreateDirectory(subDir); await File.WriteAllTextAsync(Path.Join(subDir, "resume-marker.txt"), "I am in the resume working directory"); - var session1 = await CreateSessionAsync(); + await using var session1 = await CreateSessionAsync(); var sessionId = session1.SessionId; + await SuspendAndUntrackSessionForResumeAsync(session1); var session2 = await ResumeSessionAsync(sessionId, new ResumeSessionConfig { @@ -360,8 +363,9 @@ public async Task Should_Apply_WorkingDirectory_On_Session_Resume() [Fact] public async Task Should_Apply_SystemMessage_On_Session_Resume() { - var session1 = await CreateSessionAsync(); + await using var session1 = await CreateSessionAsync(); var sessionId = session1.SessionId; + await SuspendAndUntrackSessionForResumeAsync(session1); var resumeInstruction = "End the response with RESUME_SYSTEM_MESSAGE_SENTINEL."; var session2 = await ResumeSessionAsync(sessionId, new ResumeSessionConfig @@ -422,11 +426,13 @@ await File.WriteAllTextAsync( Path.Join(instructionFilesDir, "extra.instructions.md"), $"Always include {sentinel}."); - var session1 = await CreateSessionAsync(new SessionConfig + await using var session1 = await CreateSessionAsync(new SessionConfig { WorkingDirectory = projectDir, }); - var session2 = await ResumeSessionAsync(session1.SessionId, new ResumeSessionConfig + var sessionId = session1.SessionId; + await SuspendAndUntrackSessionForResumeAsync(session1); + var session2 = await ResumeSessionAsync(sessionId, new ResumeSessionConfig { WorkingDirectory = projectDir, InstructionDirectories = [instructionDir], @@ -438,14 +444,14 @@ await File.WriteAllTextAsync( Assert.Contains(sentinel, GetSystemMessage(exchange)); await session2.DisposeAsync(); - await session1.DisposeAsync(); } [Fact] public async Task Should_Apply_AvailableTools_On_Session_Resume() { - var session1 = await CreateSessionAsync(); + await using var session1 = await CreateSessionAsync(); var sessionId = session1.SessionId; + await SuspendAndUntrackSessionForResumeAsync(session1); var session2 = await ResumeSessionAsync(sessionId, new ResumeSessionConfig { @@ -493,8 +499,10 @@ public async Task Should_Apply_Session_Limits_On_Create() [Fact] public async Task Should_Apply_Session_Limits_On_Resume() { - var session1 = await CreateSessionAsync(); - var session2 = await ResumeSessionAsync(session1.SessionId, new ResumeSessionConfig + await using var session1 = await CreateSessionAsync(); + var sessionId = session1.SessionId; + await SuspendAndUntrackSessionForResumeAsync(session1); + var session2 = await ResumeSessionAsync(sessionId, new ResumeSessionConfig { SessionLimits = new SessionLimitsConfig { @@ -513,7 +521,6 @@ public async Task Should_Apply_Session_Limits_On_Resume() finally { await session2.DisposeAsync(); - await session1.DisposeAsync(); } } @@ -558,8 +565,10 @@ public async Task Should_Apply_Excluded_Built_In_Agents_On_Resume() { const string excludedAgent = "explore"; - var session1 = await CreateSessionAsync(); - var session2 = await ResumeSessionAsync(session1.SessionId, new ResumeSessionConfig + await using var session1 = await CreateSessionAsync(); + var sessionId = session1.SessionId; + await SuspendAndUntrackSessionForResumeAsync(session1); + var session2 = await ResumeSessionAsync(sessionId, new ResumeSessionConfig { ExcludedBuiltInAgents = [excludedAgent], }); @@ -575,7 +584,6 @@ public async Task Should_Apply_Excluded_Built_In_Agents_On_Resume() finally { await session2.DisposeAsync(); - await session1.DisposeAsync(); } } diff --git a/dotnet/test/E2E/SessionE2ETests.cs b/dotnet/test/E2E/SessionE2ETests.cs index bcc8fc268..27ef7437f 100644 --- a/dotnet/test/E2E/SessionE2ETests.cs +++ b/dotnet/test/E2E/SessionE2ETests.cs @@ -228,7 +228,7 @@ public async Task Should_Create_Session_With_Custom_Tool() [Fact] public async Task Should_Reject_Resuming_Active_Session_Using_The_Same_Client() { - var session1 = await CreateSessionAsync(); + await using var session1 = await CreateSessionAsync(); var sessionId = session1.SessionId; var exception = await Assert.ThrowsAsync(() => @@ -245,8 +245,7 @@ public async Task Should_Resume_A_Session_Using_A_New_Client() var session1 = await CreateSessionAsync(); var sessionId = session1.SessionId; - await session1.SendAsync(new MessageOptions { Prompt = "What is 1+1?" }); - var answer = await TestHelper.GetFinalAssistantMessageAsync(session1); + var answer = await session1.SendAndWaitAsync(new MessageOptions { Prompt = "What is 1+1?" }); Assert.NotNull(answer); Assert.Contains("2", answer!.Data.Content ?? string.Empty); @@ -332,10 +331,10 @@ await session.SendAsync(new MessageOptions // Verify an abort event exists in messages Assert.Contains(messages, m => m is AbortEvent); - // We should be able to send another message - var answer = await session.SendAndWaitAsync(new MessageOptions { Prompt = "What is 2+2?" }); - Assert.NotNull(answer); - Assert.Contains("4", answer!.Data.Content ?? string.Empty); + await session.SendAsync(new MessageOptions { Prompt = "What is 2+2?" }); + var recoveryMessage = await TestHelper.GetFinalAssistantMessageAsync(session); + Assert.NotNull(recoveryMessage); + Assert.Contains("4", recoveryMessage.Data.Content ?? string.Empty); } [Fact] @@ -612,14 +611,17 @@ public async Task Should_Set_Model_On_Existing_Session() [Fact] public async Task Should_Set_Model_With_ReasoningEffort() { - var session = await CreateSessionAsync(); + await using var isolatedCtx = await E2ETestContext.CreateAsync(); + await isolatedCtx.ConfigureForTestAsync("session", nameof(Should_Set_Model_With_ReasoningEffort)); + var isolatedClient = isolatedCtx.CreateClient(); + await using var session = await isolatedCtx.CreateSessionAsync(isolatedClient); var modelChangedTask = TestHelper.GetNextEventOfTypeAsync(session); - await session.SetModelAsync("gpt-4.1", "high"); + await session.SetModelAsync("gpt-5.4", "high"); var modelChanged = await modelChangedTask; - Assert.Equal("gpt-4.1", modelChanged.Data.NewModel); + Assert.Equal("gpt-5.4", modelChanged.Data.NewModel); Assert.Equal("high", modelChanged.Data.ReasoningEffort); } @@ -984,8 +986,9 @@ public async Task Should_Create_Session_With_Azure_Provider() [Trait(E2ETestTraits.Backend, E2ETestTraits.SelfConfiguredBackend)] public async Task Should_Resume_Session_With_Custom_Provider() { - var session = await CreateSessionAsync(); + await using var session = await CreateSessionAsync(); var sessionId = session.SessionId; + await SuspendAndUntrackSessionForResumeAsync(session); var session2 = await ResumeSessionAsync(sessionId, new ResumeSessionConfig { @@ -1007,7 +1010,5 @@ public async Task Should_Resume_Session_With_Custom_Provider() { // disconnect may fail since the provider is fake } - - await session.DisposeAsync(); } } diff --git a/dotnet/test/E2E/SessionFsE2ETests.cs b/dotnet/test/E2E/SessionFsE2ETests.cs index cf91e3ddc..cc02b5abf 100644 --- a/dotnet/test/E2E/SessionFsE2ETests.cs +++ b/dotnet/test/E2E/SessionFsE2ETests.cs @@ -581,7 +581,8 @@ private static string NormalizeRelativePathSegment(string segment, string paramN return normalized; } - private sealed class ThrowingSessionFsProvider(Exception exception) : SessionFsProvider, ISessionFsSqliteProvider + private sealed class ThrowingSessionFsProvider(Exception exception) + : SessionFsProvider, ISessionFsSqliteProvider, ISessionFsSqliteTransactionProvider { protected override Task ReadFileAsync(string path, CancellationToken cancellationToken) => Task.FromException(exception); @@ -616,6 +617,9 @@ protected override Task RenameAsync(string src, string dest, CancellationToken c Task ISessionFsSqliteProvider.QueryAsync(SessionFsSqliteQueryType queryType, string query, IDictionary? bindParams, CancellationToken cancellationToken) => Task.FromException(exception); + Task> ISessionFsSqliteTransactionProvider.TransactionAsync(IList statements, CancellationToken cancellationToken) => + Task.FromException>(exception); + Task ISessionFsSqliteProvider.ExistsAsync(CancellationToken cancellationToken) => Task.FromException(exception); } diff --git a/dotnet/test/E2E/SkillsE2ETests.cs b/dotnet/test/E2E/SkillsE2ETests.cs index 76f84106f..3b005fc01 100644 --- a/dotnet/test/E2E/SkillsE2ETests.cs +++ b/dotnet/test/E2E/SkillsE2ETests.cs @@ -208,13 +208,14 @@ public async Task Should_Apply_Skill_On_Session_Resume_With_SkillDirectories() var skillsDir = CreateSkillDir(); // Create a session without skills first - var session1 = await CreateSessionAsync(); + await using var session1 = await CreateSessionAsync(); var sessionId = session1.SessionId; // First message without skill - marker should not appear var message1 = await session1.SendAndWaitAsync(new MessageOptions { Prompt = "Say hi." }); Assert.NotNull(message1); Assert.DoesNotContain(SkillMarker, message1!.Data.Content); + await SuspendAndUntrackSessionForResumeAsync(session1); // Resume with skillDirectories - skill should now be active var session2 = await ResumeSessionAsync(sessionId, new ResumeSessionConfig diff --git a/dotnet/test/E2E/StreamingFidelityE2ETests.cs b/dotnet/test/E2E/StreamingFidelityE2ETests.cs index 4df9ca442..bea4760c8 100644 --- a/dotnet/test/E2E/StreamingFidelityE2ETests.cs +++ b/dotnet/test/E2E/StreamingFidelityE2ETests.cs @@ -150,8 +150,12 @@ public async Task Should_Emit_Streaming_Deltas_With_Reasoning_Effort_Configured( { // Verifies that setting ReasoningEffort alongside Streaming=true does not break // the streaming pipeline — deltas still arrive and complete successfully. - var session = await CreateSessionAsync(new SessionConfig + await using var isolatedCtx = await E2ETestContext.CreateAsync(); + await isolatedCtx.ConfigureForTestAsync("streaming_fidelity", nameof(Should_Emit_Streaming_Deltas_With_Reasoning_Effort_Configured)); + var isolatedClient = isolatedCtx.CreateClient(); + await using var session = await isolatedCtx.CreateSessionAsync(isolatedClient, new SessionConfig { + Model = "gpt-5.4", Streaming = true, ReasoningEffort = "high", }); @@ -177,8 +181,6 @@ public async Task Should_Emit_Streaming_Deltas_With_Reasoning_Effort_Configured( var messages = await session.GetEventsAsync(); var startEvent = Assert.Single(messages.OfType()); Assert.Equal("high", startEvent.Data.ReasoningEffort); - - await session.DisposeAsync(); } [Fact] diff --git a/dotnet/test/Harness/E2ETestBase.cs b/dotnet/test/Harness/E2ETestBase.cs index a3006389b..3eb0f0e97 100644 --- a/dotnet/test/Harness/E2ETestBase.cs +++ b/dotnet/test/Harness/E2ETestBase.cs @@ -59,6 +59,7 @@ internal static string GetTestName(ITestOutputHelper output) public async Task InitializeAsync() { + Ctx.PrepareForTest(); await Ctx.CleanupAfterTestAsync(); await Ctx.ConfigureForTestAsync(_snapshotCategory, _testName); } @@ -88,17 +89,40 @@ protected async Task ResumeSessionAsync(string sessionId, Resume config ??= new ResumeSessionConfig(); config.OnPermissionRequest ??= PermissionHandler.ApproveAll; - await Client.StartAsync(); - var port = Client.RuntimePort - ?? throw new InvalidOperationException("The shared E2E client must use TCP transport to support multi-client resume."); - - var client = Ctx.CreateClient(options: new CopilotClientOptions + CopilotClient client; + if (E2ETestContext.UsesInProcessTransport) + { + client = Client; + } + else { - Connection = RuntimeConnection.ForUri($"localhost:{port}", connectionToken: E2ETestFixture.SharedTcpConnectionToken), - }); + await Client.StartAsync(); + var port = Client.RuntimePort + ?? throw new InvalidOperationException("The shared E2E client must use TCP transport to support multi-client resume."); + + client = Ctx.CreateClient(options: new CopilotClientOptions + { + Connection = RuntimeConnection.ForUri($"localhost:{port}", connectionToken: E2ETestFixture.SharedTcpConnectionToken), + }); + } + return await Ctx.ResumeSessionAsync(client, sessionId, config); } + protected static async Task SuspendAndUntrackSessionForResumeAsync(CopilotSession session) + { + await session.Rpc.SuspendAsync(); + + // In-process clients host separate runtimes, while session.destroy removes the + // session from the current runtime. Untrack locally to exercise resume without + // either replacing an active wrapper or destroying the session first. + var removeFromClient = typeof(CopilotSession).GetMethod( + "RemoveFromClient", + BindingFlags.Instance | BindingFlags.NonPublic) + ?? throw new InvalidOperationException("CopilotSession.RemoveFromClient was not found."); + removeFromClient.Invoke(session, null); + } + protected static string GetSystemMessage(ParsedHttpExchange exchange) { return exchange.Request.Messages.FirstOrDefault(m => m.Role == "system")?.StringContent ?? string.Empty; diff --git a/dotnet/test/Harness/E2ETestContext.cs b/dotnet/test/Harness/E2ETestContext.cs index c01f4efcf..1c88d809b 100644 --- a/dotnet/test/Harness/E2ETestContext.cs +++ b/dotnet/test/Harness/E2ETestContext.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Logging; using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Text.RegularExpressions; namespace GitHub.Copilot.Test.Harness; @@ -12,10 +13,12 @@ namespace GitHub.Copilot.Test.Harness; public sealed class E2ETestContext : IAsyncDisposable { private const string DefaultGitHubToken = "fake-token-for-e2e-tests"; + private static readonly TimeSpan s_gracefulClientStopTimeout = TimeSpan.FromSeconds(30); public string HomeDir { get; } public string WorkDir { get; } public string ProxyUrl { get; } + internal static bool UsesInProcessTransport => IsInProcess(null); /// Optional logger injected by tests; applied to all clients created via . public ILogger? Logger { get; set; } @@ -141,20 +144,40 @@ private static string GetCliPath(string repoRoot) if (!string.IsNullOrEmpty(envPath)) return envPath; // As of CLI 1.0.64-1 the @github/copilot package is a thin loader; the - // runnable index.js ships in the installed platform package - // (e.g. @github/copilot-linux-x64). Exactly one is installed. + // runnable index.js ships in the installed platform package. var githubModules = Path.Join(repoRoot, "nodejs", "node_modules", "@github"); - if (Directory.Exists(githubModules)) + var packagePrefix = GetCliPackagePrefix(); + var candidates = Directory.Exists(githubModules) + ? Directory.EnumerateDirectories(githubModules, $"{packagePrefix}-*", SearchOption.TopDirectoryOnly) + .Select(directory => Path.Join(directory, "index.js")) + .Where(File.Exists) + .ToArray() + : []; + + return candidates.Length switch { - var candidate = Directory.EnumerateDirectories(githubModules, "copilot-*") - .Select(dir => Path.Join(dir, "index.js")) - .FirstOrDefault(File.Exists); - if (candidate != null) - return candidate; - } + 1 => candidates[0], + 0 => throw new InvalidOperationException( + $"CLI package matching '{packagePrefix}-*' not found under {githubModules}. " + + "Run 'npm install' in the nodejs directory first."), + _ => throw new InvalidOperationException( + $"Multiple CLI packages matching '{packagePrefix}-*' found under {githubModules}: " + + string.Join(", ", candidates.Select(Path.GetDirectoryName))), + }; + } - throw new InvalidOperationException( - $"CLI not found under {githubModules}. Run 'npm install' in the nodejs directory first."); + private static string GetCliPackagePrefix() + { + var platform = OperatingSystem.IsWindows() + ? "win32" + : OperatingSystem.IsMacOS() + ? "darwin" + : OperatingSystem.IsLinux() + ? RuntimeInformation.RuntimeIdentifier.StartsWith("linux-musl-", StringComparison.Ordinal) + ? "linuxmusl" + : "linux" + : throw new PlatformNotSupportedException("Unsupported operating system for Copilot CLI E2E tests."); + return $"copilot-{platform}"; } public async Task ConfigureForTestAsync(string testFile, [CallerMemberName] string? testName = null) @@ -301,22 +324,8 @@ public CopilotClient CreateClient( if (IsInProcess(options.Connection)) { - // In-process hosting: runtime code runs host-side in this process (the - // loaded cdylib) and reads the ambient process environment rather than - // the environment passed to copilot_runtime_host_start, so the per-test - // redirects, cleared tokens/HMAC, and isolated home must be mirrored - // onto this process's real environment. Restored after each test by - // InProcessEnvIsolationAttribute. - foreach (var (name, value) in env) - { - InProcessEnvIsolation.Apply(name, value); - } - - // A per-client WorkingDirectory is rejected in-process; instead point this - // process's cwd at the desired directory so the worker inherits it at spawn - // (restored after the test by InProcessEnvIsolationAttribute). options.WorkingDirectory = null; - InProcessEnvIsolation.SetWorkingDirectory(desiredWorkingDirectory); + ApplyInProcessEnvironment(env, desiredWorkingDirectory); } else if (options.Connection is ChildProcessRuntimeConnection child) { @@ -374,6 +383,29 @@ public Task ResumeSessionAsync( return client.ResumeSessionAsync(sessionId, config); } + internal void PrepareForTest() + { + if (UsesInProcessTransport) + { + ApplyInProcessEnvironment(GetEnvironment(), WorkDir); + } + } + + private static void ApplyInProcessEnvironment(IReadOnlyDictionary environment, string workingDirectory) + { + // Runtime code runs host-side in this process and reads its ambient environment, + // so restore the per-test redirects and isolated home after the assembly-level + // isolation attribute reset them at the end of the preceding test. + foreach (var (name, value) in environment) + { + InProcessEnvIsolation.Apply(name, value); + } + + // The worker inherits the host process cwd because the native host has no + // per-client working-directory parameter. + InProcessEnvIsolation.SetWorkingDirectory(workingDirectory); + } + public void UntrackClient(CopilotClient client) { lock (_clientsLock) @@ -518,7 +550,6 @@ private static bool IsInProcess(RuntimeConnection? connection) return false; } - // Inproc holds the session-store SQLite handle in-process; graceful StopAsync releases it so the temp-dir delete succeeds on Windows. private static async Task StopClientForCleanupAsync(CopilotClient client) { var isInProcess = string.Equals( @@ -527,7 +558,21 @@ private static async Task StopClientForCleanupAsync(CopilotClient client) StringComparison.OrdinalIgnoreCase); if (isInProcess) { - await client.StopAsync(); + var gracefulStop = client.StopAsync(); + try + { + await gracefulStop.WaitAsync(s_gracefulClientStopTimeout); + } + catch (TimeoutException) + { + Console.Error.WriteLine( + $"Graceful in-process client cleanup exceeded {s_gracefulClientStopTimeout}; forcing shutdown."); + await client.ForceStopAsync(); + + // Disposing the connection completes any session.destroy RPC that + // blocked graceful cleanup. Observe that task before continuing. + await gracefulStop.WaitAsync(s_gracefulClientStopTimeout); + } } else { diff --git a/dotnet/test/Harness/E2ETestFixture.cs b/dotnet/test/Harness/E2ETestFixture.cs index 95bebc139..e29f5f7f6 100644 --- a/dotnet/test/Harness/E2ETestFixture.cs +++ b/dotnet/test/Harness/E2ETestFixture.cs @@ -19,10 +19,15 @@ public async Task InitializeAsync() Ctx = await E2ETestContext.CreateAsync(); Client = Ctx.CreateClient(options: new CopilotClientOptions { - Connection = RuntimeConnection.ForTcp(connectionToken: SharedTcpConnectionToken), + Connection = CreateSharedConnection(E2ETestContext.UsesInProcessTransport), }, persistent: true); } + internal static RuntimeConnection CreateSharedConnection(bool useInProcessTransport) => + useInProcessTransport + ? RuntimeConnection.ForInProcess() + : RuntimeConnection.ForTcp(connectionToken: SharedTcpConnectionToken); + public async Task DisposeAsync() { await Ctx.DisposeAsync(); diff --git a/dotnet/test/Unit/ClientSessionLifetimeTests.cs b/dotnet/test/Unit/ClientSessionLifetimeTests.cs index e1143db17..a561ee44b 100644 --- a/dotnet/test/Unit/ClientSessionLifetimeTests.cs +++ b/dotnet/test/Unit/ClientSessionLifetimeTests.cs @@ -10,6 +10,7 @@ using System.Runtime.CompilerServices; using System.Text; using System.Text.Json; +using GitHub.Copilot.Rpc; using Xunit; namespace GitHub.Copilot.Test.Unit; @@ -182,6 +183,27 @@ public async Task StopAsync_Keeps_Session_Rooted_Until_Destroy_Completes() AssertSessionCount(client, sessions: 0); } + [Fact] + public async Task ForceStopAsync_Unblocks_StopAsync_When_Session_Destroy_Hangs() + { + await using var server = await FakeCopilotServer.StartAsync(); + server.DelayDestroy(); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); + + _ = await client.CreateSessionAsync(new SessionConfig + { + OnPermissionRequest = PermissionHandler.ApproveAll + }); + + var stopTask = client.StopAsync(); + await server.DestroyStarted; + + await client.ForceStopAsync(); + await stopTask.WaitAsync(TimeSpan.FromSeconds(5)); + + AssertSessionCount(client, sessions: 0); + } + [Fact] public async Task ResumeSessionAsync_Throws_When_Same_Client_Already_Tracks_Session() { @@ -255,6 +277,71 @@ public async Task CreateSessionAsync_Omits_CustomAgent_ReasoningEffort_When_Unse Assert.False(agent.TryGetProperty("reasoningEffort", out _)); } + [Fact] + public async Task SessionRequests_Serialize_AdditionalDirectories() + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); + + await using var created = await client.CreateSessionAsync(new SessionConfig + { + AdditionalDirectories = ["/repo/shared", "/repo/generated"], + OnPermissionRequest = PermissionHandler.ApproveAll + }); + + var createRequest = Assert.Single(server.Requests, request => request.Method == "session.create"); + Assert.Collection( + createRequest.Params.GetProperty("additionalDirectories").EnumerateArray(), + value => Assert.Equal("/repo/shared", value.GetString()), + value => Assert.Equal("/repo/generated", value.GetString())); + + server.ClearRequests(); + + await using var resumed = await client.ResumeSessionAsync("resume-with-additional-directories", new ResumeSessionConfig + { + AdditionalDirectories = ["/repo/resumed"], + OnPermissionRequest = PermissionHandler.ApproveAll + }); + + var resumeRequest = Assert.Single(server.Requests, request => request.Method == "session.resume"); + Assert.Collection( + resumeRequest.Params.GetProperty("additionalDirectories").EnumerateArray(), + value => Assert.Equal("/repo/resumed", value.GetString())); + } + + [Fact] + public async Task SessionRequests_Serialize_Terminal_Tools() + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); + var terminalTool = CopilotTool.DefineTool( + (Func)(() => "done"), + new CopilotToolOptions { IsTerminal = true }); + var plainTool = CopilotTool.DefineTool((Func)(() => "continue")); + + await using var created = await client.CreateSessionAsync(new SessionConfig + { + Tools = [terminalTool, plainTool], + OnPermissionRequest = PermissionHandler.ApproveAll + }); + + var createRequest = Assert.Single(server.Requests, request => request.Method == "session.create"); + var createTools = createRequest.Params.GetProperty("tools"); + Assert.True(createTools[0].GetProperty("isTerminal").GetBoolean()); + Assert.False(createTools[1].TryGetProperty("isTerminal", out _)); + + server.ClearRequests(); + + await using var resumed = await client.ResumeSessionAsync("resume-with-terminal-tool", new ResumeSessionConfig + { + Tools = [terminalTool], + OnPermissionRequest = PermissionHandler.ApproveAll + }); + + var resumeRequest = Assert.Single(server.Requests, request => request.Method == "session.resume"); + Assert.True(resumeRequest.Params.GetProperty("tools")[0].GetProperty("isTerminal").GetBoolean()); + } + [Fact] public async Task CreateSessionAsync_Registers_McpAuth_Interest_Only_When_Handler_Configured() { @@ -450,6 +537,296 @@ private static int GetPrivateDictionaryCount(CopilotClient client, string fieldN return (int)count.GetValue(dictionary)!; } + [Fact] + public async Task CreateSessionAsync_Serializes_ManagedSettings_Permissions() + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); + await client.StartAsync(); + var permissionInvocation = new TaskCompletionSource( + TaskCreationOptions.RunContinuationsAsynchronously); + + await using var session = await client.CreateSessionAsync(new SessionConfig + { + ManagedSettings = new ManagedSettings + { + Permissions = new ManagedSettingsPermissions + { + DisableBypassPermissionsMode = DisableBypassPermissionsMode.Disable, + Deny = ["shell(rm*)"], + Ask = ["write"], + Allow = [] + } + }, + OnPermissionRequest = (_, invocation) => + { + permissionInvocation.TrySetResult(invocation); + return Task.FromResult(PermissionDecision.NoResult()); + } + }); + + var request = Assert.Single(server.Requests, request => request.Method == "session.create"); + Assert.False(request.Params.TryGetProperty("enableManagedSettings", out _)); + var permissions = request.Params.GetProperty("managedSettings").GetProperty("permissions"); + Assert.Equal("disable", permissions.GetProperty("disableBypassPermissionsMode").GetString()); + Assert.Equal("shell(rm*)", Assert.Single(permissions.GetProperty("deny").EnumerateArray()).GetString()); + Assert.Equal("write", Assert.Single(permissions.GetProperty("ask").EnumerateArray()).GetString()); + Assert.Empty(permissions.GetProperty("allow").EnumerateArray()); + + DispatchEvent(session, new PermissionRequestedEvent + { + Data = new PermissionRequestedData + { + PermissionRequest = new PermissionRequest { Kind = "read" }, + RequestId = "managed-permission" + } + }); + var invocation = await permissionInvocation.Task.WaitAsync(TimeSpan.FromSeconds(5)); + Assert.True(invocation.ManagedSettingsEnabled); + } + + [Fact] + public async Task PermissionResponse_Forwards_DecisionContext_As_Sibling_Of_Result() + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); + await client.StartAsync(); + + await using var session = await client.CreateSessionAsync(new SessionConfig + { + OnPermissionRequest = (_, _) => Task.FromResult( + new PermissionDecisionApproveOnce + { + DecisionContext = new PermissionDecisionContext + { + Outcome = PermissionDecisionOutcome.AutoApproved, + Source = PermissionDecisionSource.HostPolicy, + Surface = PermissionDecisionSurface.Sdk + } + }) + }); + + DispatchEvent(session, new PermissionRequestedEvent + { + Data = new PermissionRequestedData + { + PermissionRequest = new PermissionRequest { Kind = "read" }, + RequestId = "req-with-context" + } + }); + + var request = await WaitForRequestAsync(server, "session.permissions.handlePendingPermissionRequest"); + + Assert.True(request.Params.TryGetProperty("decisionContext", out var decisionContext)); + Assert.Equal("auto_approved", decisionContext.GetProperty("outcome").GetString()); + Assert.Equal("host_policy", decisionContext.GetProperty("source").GetString()); + Assert.Equal("sdk", decisionContext.GetProperty("surface").GetString()); + + var result = request.Params.GetProperty("result"); + Assert.Equal("approve-once", result.GetProperty("kind").GetString()); + Assert.False(result.TryGetProperty("decisionContext", out _)); + } + + [Fact] + public async Task PermissionResponse_Omits_DecisionContext_When_Not_Supplied() + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); + await client.StartAsync(); + + await using var session = await client.CreateSessionAsync(new SessionConfig + { + OnPermissionRequest = (_, _) => Task.FromResult(PermissionDecision.ApproveOnce()) + }); + + DispatchEvent(session, new PermissionRequestedEvent + { + Data = new PermissionRequestedData + { + PermissionRequest = new PermissionRequest { Kind = "read" }, + RequestId = "req-no-context" + } + }); + + var request = await WaitForRequestAsync(server, "session.permissions.handlePendingPermissionRequest"); + + Assert.False(request.Params.TryGetProperty("decisionContext", out _)); + var result = request.Params.GetProperty("result"); + Assert.Equal("approve-once", result.GetProperty("kind").GetString()); + Assert.False(result.TryGetProperty("decisionContext", out _)); + } + + [Fact] + public async Task PermissionResponse_Uses_Latest_Context_When_Reassigned() + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); + await client.StartAsync(); + + await using var session = await client.CreateSessionAsync(new SessionConfig + { + OnPermissionRequest = (_, _) => + { + var decision = new PermissionDecisionApproveOnce + { + DecisionContext = new PermissionDecisionContext + { + Outcome = PermissionDecisionOutcome.PromptedUser, + Source = PermissionDecisionSource.HumanResponse, + Surface = PermissionDecisionSurface.Tui + } + }; + decision.DecisionContext = new PermissionDecisionContext + { + Outcome = PermissionDecisionOutcome.AutoApproved, + Source = PermissionDecisionSource.HostPolicy, + Surface = PermissionDecisionSurface.Sdk + }; + return Task.FromResult(decision); + } + }); + + DispatchEvent(session, new PermissionRequestedEvent + { + Data = new PermissionRequestedData + { + PermissionRequest = new PermissionRequest { Kind = "read" }, + RequestId = "req-replace-context" + } + }); + + var request = await WaitForRequestAsync(server, "session.permissions.handlePendingPermissionRequest"); + + var decisionContext = request.Params.GetProperty("decisionContext"); + Assert.Equal("auto_approved", decisionContext.GetProperty("outcome").GetString()); + Assert.Equal("host_policy", decisionContext.GetProperty("source").GetString()); + Assert.Equal("sdk", decisionContext.GetProperty("surface").GetString()); + } + + [Fact] + public async Task PermissionResponse_Is_Suppressed_For_NoResult_Even_With_Context() + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); + await client.StartAsync(); + + var handlerInvoked = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + await using var session = await client.CreateSessionAsync(new SessionConfig + { + OnPermissionRequest = (_, _) => + { + handlerInvoked.TrySetResult(); + return Task.FromResult( + new PermissionDecisionNoResult + { + DecisionContext = new PermissionDecisionContext + { + Outcome = PermissionDecisionOutcome.PromptedUser, + Source = PermissionDecisionSource.HumanResponse, + Surface = PermissionDecisionSurface.Sdk + } + }); + } + }); + + DispatchEvent(session, new PermissionRequestedEvent + { + Data = new PermissionRequestedData + { + PermissionRequest = new PermissionRequest { Kind = "read" }, + RequestId = "req-no-result" + } + }); + + await handlerInvoked.Task.WaitAsync(TimeSpan.FromSeconds(5)); + // Give the send path a chance to (incorrectly) fire before asserting suppression. + await Task.Delay(200); + + Assert.DoesNotContain(server.Requests, request => request.Method == "session.permissions.handlePendingPermissionRequest"); + } + + [Fact] + public async Task PermissionResponse_Never_Nests_DecisionContext_Inside_Result() + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); + await client.StartAsync(); + + await using var session = await client.CreateSessionAsync(new SessionConfig + { + OnPermissionRequest = (_, _) => Task.FromResult( + new PermissionDecisionReject + { + Feedback = "denied by policy", + DecisionContext = new PermissionDecisionContext + { + Outcome = PermissionDecisionOutcome.AutopilotDenied, + Source = PermissionDecisionSource.HostPolicy, + Surface = PermissionDecisionSurface.Sdk + } + }) + }); + + DispatchEvent(session, new PermissionRequestedEvent + { + Data = new PermissionRequestedData + { + PermissionRequest = new PermissionRequest { Kind = "read" }, + RequestId = "req-reject-context" + } + }); + + var request = await WaitForRequestAsync(server, "session.permissions.handlePendingPermissionRequest"); + + var result = request.Params.GetProperty("result"); + Assert.Equal("reject", result.GetProperty("kind").GetString()); + Assert.Equal("denied by policy", result.GetProperty("feedback").GetString()); + // The context provenance must never be serialized inside the decision itself. + Assert.False(result.TryGetProperty("decisionContext", out _)); + // It is forwarded as a sibling instead. + Assert.True(request.Params.TryGetProperty("decisionContext", out _)); + } + + [Fact] + public async Task CreateSessionAsync_Omits_ManagedSettings_When_Unset() + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); + await client.StartAsync(); + + await using var session = await client.CreateSessionAsync(new SessionConfig + { + OnPermissionRequest = PermissionHandler.ApproveAll + }); + + var request = Assert.Single(server.Requests, request => request.Method == "session.create"); + Assert.False(request.Params.TryGetProperty("managedSettings", out _)); + } + + [Fact] + public async Task ResumeSessionAsync_Serializes_ManagedSettings_Permissions() + { + await using var server = await FakeCopilotServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) }); + + await using var session = await client.ResumeSessionAsync("session-managed", new ResumeSessionConfig + { + ManagedSettings = new ManagedSettings + { + Permissions = new ManagedSettingsPermissions + { + Deny = ["shell(rm*)"] + } + }, + OnPermissionRequest = PermissionHandler.ApproveAll, + OnEvent = _ => { } + }); + + var request = Assert.Single(server.Requests, request => request.Method == "session.resume"); + var permissions = request.Params.GetProperty("managedSettings").GetProperty("permissions"); + Assert.Equal("shell(rm*)", Assert.Single(permissions.GetProperty("deny").EnumerateArray()).GetString()); + } + private static void DispatchEvent(CopilotSession session, SessionEvent evt) { var method = typeof(CopilotSession).GetMethod("DispatchEvent", BindingFlags.Instance | BindingFlags.NonPublic) @@ -668,6 +1045,10 @@ private async Task HandleRequestAsync(Stream stream, JsonElement request, Cancel { ["success"] = true }, + "session.permissions.handlePendingPermissionRequest" => new Dictionary + { + ["success"] = true + }, "session.delete" => new Dictionary { ["success"] = true diff --git a/dotnet/test/Unit/CloneTests.cs b/dotnet/test/Unit/CloneTests.cs index ec509ab16..4bacdfe33 100644 --- a/dotnet/test/Unit/CloneTests.cs +++ b/dotnet/test/Unit/CloneTests.cs @@ -20,6 +20,7 @@ public void CopilotClientOptions_Clone_CopiesAllProperties() GitHubToken = "ghp_test", UseLoggedInUser = false, BaseDirectory = "/custom/copilot/home", + BuiltinPluginDirectories = ["/plugins/core", "/plugins/github"], EnableRemoteSessions = true, SessionIdleTimeoutSeconds = 600, }; @@ -33,6 +34,8 @@ public void CopilotClientOptions_Clone_CopiesAllProperties() Assert.Equal(original.GitHubToken, clone.GitHubToken); Assert.Equal(original.UseLoggedInUser, clone.UseLoggedInUser); Assert.Equal(original.BaseDirectory, clone.BaseDirectory); + Assert.Equal(original.BuiltinPluginDirectories, clone.BuiltinPluginDirectories); + Assert.NotSame(original.BuiltinPluginDirectories, clone.BuiltinPluginDirectories); Assert.Equal(original.EnableRemoteSessions, clone.EnableRemoteSessions); Assert.Equal(original.SessionIdleTimeoutSeconds, clone.SessionIdleTimeoutSeconds); } @@ -75,9 +78,12 @@ public void SessionConfig_Clone_CopiesAllProperties() ExcludedTools = ["tool3"], ExcludedBuiltInAgents = ["explore", "task"], WorkingDirectory = "/workspace", + AdditionalDirectories = ["/shared", "/generated"], Streaming = true, EnableCitations = true, + EnableFileChangeTracking = true, EnableSessionTelemetry = false, + EnableExperimentalMode = true, EnableOnDemandInstructionDiscovery = true, IncludeSubAgentStreamingEvents = false, McpServers = new Dictionary { ["server1"] = new McpStdioServerConfig { Command = "echo" } }, @@ -98,6 +104,7 @@ public void SessionConfig_Clone_CopiesAllProperties() SkillDirectories = ["/skills"], InstructionDirectories = ["/instructions"], DisabledSkills = ["skill1"], + DisabledMcpServers = ["server1"], PluginDirectories = ["/plugins"], LargeOutput = new LargeToolOutputConfig { Enabled = true, MaxSizeBytes = 2048, OutputDirectory = "/tmp/out" }, Memory = new MemoryConfiguration { Enabled = true }, @@ -119,9 +126,12 @@ public void SessionConfig_Clone_CopiesAllProperties() Assert.Equal(original.ExcludedTools, clone.ExcludedTools); Assert.Equal(original.ExcludedBuiltInAgents, clone.ExcludedBuiltInAgents); Assert.Equal(original.WorkingDirectory, clone.WorkingDirectory); + Assert.Equal(original.AdditionalDirectories, clone.AdditionalDirectories); Assert.Equal(original.Streaming, clone.Streaming); Assert.Equal(original.EnableCitations, clone.EnableCitations); + Assert.Equal(original.EnableFileChangeTracking, clone.EnableFileChangeTracking); Assert.Equal(original.EnableSessionTelemetry, clone.EnableSessionTelemetry); + Assert.Equal(original.EnableExperimentalMode, clone.EnableExperimentalMode); Assert.Equal(original.EnableOnDemandInstructionDiscovery, clone.EnableOnDemandInstructionDiscovery); Assert.Equal(original.IncludeSubAgentStreamingEvents, clone.IncludeSubAgentStreamingEvents); Assert.Equal(original.McpServers.Count, clone.McpServers!.Count); @@ -136,6 +146,7 @@ public void SessionConfig_Clone_CopiesAllProperties() Assert.Equal(original.SkillDirectories, clone.SkillDirectories); Assert.Equal(original.InstructionDirectories, clone.InstructionDirectories); Assert.Equal(original.DisabledSkills, clone.DisabledSkills); + Assert.Equal(original.DisabledMcpServers, clone.DisabledMcpServers); Assert.Equal(original.PluginDirectories, clone.PluginDirectories); Assert.Same(original.LargeOutput, clone.LargeOutput); Assert.Same(original.Memory, clone.Memory); @@ -154,9 +165,11 @@ public void SessionConfig_Clone_CollectionsAreIndependent() ExcludedBuiltInAgents = ["explore"], McpServers = new Dictionary { ["s1"] = new McpStdioServerConfig { Command = "echo" } }, CustomAgents = [new CustomAgentConfig { Name = "a1" }], + AdditionalDirectories = ["/shared"], SkillDirectories = ["/skills"], InstructionDirectories = ["/instructions"], DisabledSkills = ["skill1"], + DisabledMcpServers = ["server1"], }; var clone = original.Clone(); @@ -167,9 +180,11 @@ public void SessionConfig_Clone_CollectionsAreIndependent() clone.ExcludedBuiltInAgents!.Add("task"); clone.McpServers!["s2"] = new McpStdioServerConfig { Command = "echo" }; clone.CustomAgents!.Add(new CustomAgentConfig { Name = "a2" }); + clone.AdditionalDirectories!.Add("/generated"); clone.SkillDirectories!.Add("/more"); clone.InstructionDirectories!.Add("/more-instructions"); clone.DisabledSkills!.Add("skill99"); + clone.DisabledMcpServers!.Add("server99"); // Original is unaffected Assert.Single(original.AvailableTools!); @@ -177,9 +192,11 @@ public void SessionConfig_Clone_CollectionsAreIndependent() Assert.Single(original.ExcludedBuiltInAgents!); Assert.Single(original.McpServers!); Assert.Single(original.CustomAgents!); + Assert.Single(original.AdditionalDirectories!); Assert.Single(original.SkillDirectories!); Assert.Single(original.InstructionDirectories!); Assert.Single(original.DisabledSkills!); + Assert.Single(original.DisabledMcpServers!); } [Fact] @@ -203,9 +220,11 @@ public void ResumeSessionConfig_Clone_CollectionsAreIndependent() ExcludedBuiltInAgents = ["explore"], McpServers = new Dictionary { ["s1"] = new McpStdioServerConfig { Command = "echo" } }, CustomAgents = [new CustomAgentConfig { Name = "a1" }], + AdditionalDirectories = ["/shared"], SkillDirectories = ["/skills"], InstructionDirectories = ["/instructions"], DisabledSkills = ["skill1"], + DisabledMcpServers = ["server1"], }; var clone = original.Clone(); @@ -216,9 +235,11 @@ public void ResumeSessionConfig_Clone_CollectionsAreIndependent() clone.ExcludedBuiltInAgents!.Add("task"); clone.McpServers!["s2"] = new McpStdioServerConfig { Command = "echo" }; clone.CustomAgents!.Add(new CustomAgentConfig { Name = "a2" }); + clone.AdditionalDirectories!.Add("/generated"); clone.SkillDirectories!.Add("/more"); clone.InstructionDirectories!.Add("/more-instructions"); clone.DisabledSkills!.Add("skill99"); + clone.DisabledMcpServers!.Add("server99"); // Original is unaffected Assert.Single(original.AvailableTools!); @@ -226,9 +247,11 @@ public void ResumeSessionConfig_Clone_CollectionsAreIndependent() Assert.Single(original.ExcludedBuiltInAgents!); Assert.Single(original.McpServers!); Assert.Single(original.CustomAgents!); + Assert.Single(original.AdditionalDirectories!); Assert.Single(original.SkillDirectories!); Assert.Single(original.InstructionDirectories!); Assert.Single(original.DisabledSkills!); + Assert.Single(original.DisabledMcpServers!); } [Fact] @@ -289,6 +312,7 @@ public void Clone_WithNullCollections_ReturnsNullCollections() Assert.Null(clone.SkillDirectories); Assert.Null(clone.InstructionDirectories); Assert.Null(clone.DisabledSkills); + Assert.Null(clone.DisabledMcpServers); Assert.Null(clone.Tools); Assert.Null(clone.DefaultAgent); Assert.True(clone.IncludeSubAgentStreamingEvents); @@ -373,6 +397,19 @@ public void ResumeSessionConfig_Clone_CopiesEnableSessionTelemetry() Assert.False(clone.EnableSessionTelemetry); } + [Fact] + public void ResumeSessionConfig_Clone_CopiesEnableExperimentalMode() + { + var original = new ResumeSessionConfig + { + EnableExperimentalMode = true, + }; + + var clone = original.Clone(); + + Assert.True(clone.EnableExperimentalMode); + } + [Fact] public void ResumeSessionConfig_Clone_CopiesContinuePendingWork() { @@ -460,6 +497,26 @@ public void ResumeSessionConfig_Clone_PreservesEnableSessionTelemetryDefault() Assert.Null(clone.EnableSessionTelemetry); } + [Fact] + public void SessionConfig_Clone_PreservesEnableExperimentalModeDefault() + { + var original = new SessionConfig(); + + var clone = original.Clone(); + + Assert.Null(clone.EnableExperimentalMode); + } + + [Fact] + public void ResumeSessionConfig_Clone_PreservesEnableExperimentalModeDefault() + { + var original = new ResumeSessionConfig(); + + var clone = original.Clone(); + + Assert.Null(clone.EnableExperimentalMode); + } + [Fact] public void SessionConfig_Clone_CopiesEnableOnDemandInstructionDiscovery() { diff --git a/dotnet/test/Unit/CopilotToolTests.cs b/dotnet/test/Unit/CopilotToolTests.cs index c3f586149..19fa6258b 100644 --- a/dotnet/test/Unit/CopilotToolTests.cs +++ b/dotnet/test/Unit/CopilotToolTests.cs @@ -34,6 +34,28 @@ public void DefineTool_Sets_Name_Description_And_Copilot_Metadata() Assert.Equal(CopilotToolDefer.Auto, defer); } + [Fact] + public void DefineTool_Sets_IsTerminal_Metadata() + { + var function = CopilotTool.DefineTool( + ReturnsOk, + new CopilotToolOptions + { + IsTerminal = true + }); + + Assert.True(function.AdditionalProperties.TryGetValue("is_terminal", out var isTerminal)); + Assert.True((bool)isTerminal!); + } + + [Fact] + public void DefineTool_Omits_IsTerminal_When_Not_Set() + { + var function = CopilotTool.DefineTool(ReturnsOk); + + Assert.False(function.AdditionalProperties.ContainsKey("is_terminal")); + } + [Fact] public void DefineTool_Omits_Copilot_Metadata_When_Flags_Are_False() { diff --git a/dotnet/test/Unit/E2ETestFixtureTests.cs b/dotnet/test/Unit/E2ETestFixtureTests.cs new file mode 100644 index 000000000..f7dee3ce0 --- /dev/null +++ b/dotnet/test/Unit/E2ETestFixtureTests.cs @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +using Xunit; + +namespace GitHub.Copilot.Test.Unit; + +public class E2ETestFixtureTests +{ + [Fact] + public void Shared_Client_Uses_InProcess_Connection_For_InProcess_Tests() + { + var connection = E2ETestFixture.CreateSharedConnection(useInProcessTransport: true); + + Assert.IsType(connection); + } + + [Fact] + public void Shared_Client_Preserves_Tcp_Connection_For_OutOfProcess_Tests() + { + var connection = Assert.IsType( + E2ETestFixture.CreateSharedConnection(useInProcessTransport: false)); + + Assert.Equal(E2ETestFixture.SharedTcpConnectionToken, connection.ConnectionToken); + } +} diff --git a/dotnet/test/Unit/GitHubTelemetryTests.cs b/dotnet/test/Unit/GitHubTelemetryTests.cs index f82e0db6e..a4a241e38 100644 --- a/dotnet/test/Unit/GitHubTelemetryTests.cs +++ b/dotnet/test/Unit/GitHubTelemetryTests.cs @@ -17,6 +17,60 @@ namespace GitHub.Copilot.Test.Unit; public sealed class GitHubTelemetryTests { + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task BuiltinPluginDirectories_Default_Or_Empty_Does_Not_Call_Rpc(bool useEmpty) + { + await using var server = await FakeTelemetryServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions + { + Connection = RuntimeConnection.ForUri(server.Url), + BuiltinPluginDirectories = useEmpty ? [] : null, + }); + + await client.StartAsync(); + + Assert.Equal(0, server.BuiltinPluginSetCount); + } + + [Fact] + public async Task BuiltinPluginDirectories_Are_Set_Once_Before_Start_Completes() + { + var paths = new[] + { + Path.GetFullPath(Path.Join("plugins", "core")), + Path.GetFullPath(Path.Join("plugins", "github")), + }; + await using var server = await FakeTelemetryServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions + { + Connection = RuntimeConnection.ForUri(server.Url), + BuiltinPluginDirectories = paths, + }); + + await client.StartAsync(); + + Assert.Equal(1, server.BuiltinPluginSetCount); + var payload = server.LastBuiltinPluginParams + ?? throw new InvalidOperationException("plugins.builtin.set was not captured."); + Assert.Collection( + payload.GetProperty("paths").EnumerateArray(), + value => Assert.Equal(paths[0], value.GetString()), + value => Assert.Equal(paths[1], value.GetString())); + } + + [Fact] + public void BuiltinPluginDirectories_Reject_Relative_Paths() + { + var exception = Assert.Throws(() => new CopilotClient(new CopilotClientOptions + { + BuiltinPluginDirectories = ["plugins/core"], + })); + + Assert.Contains("absolute paths", exception.Message); + } + [Fact] public async Task CreateSession_Opts_Into_Forwarding_When_Handler_Provided() { @@ -193,6 +247,50 @@ await server.SendGitHubTelemetryEventAsync(new Dictionary Assert.Equal(false, clientInfo.IsStaff); } + [Fact] + public async Task CreateSession_EmptyMode_Sends_IsExperimentalMode_False_By_Default() + { + await using var server = await FakeTelemetryServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions + { + Connection = RuntimeConnection.ForUri(server.Url), + Mode = CopilotClientMode.Empty, + BaseDirectory = Path.GetTempPath(), + }); + await client.StartAsync(); + + await client.CreateSessionAsync(new SessionConfig + { + AvailableTools = new ToolSet().AddBuiltIn(BuiltInTools.Isolated).ToList(), + }); + + var createParams = server.LastCreateParams ?? throw new InvalidOperationException("session.create was not captured."); + Assert.True(createParams.TryGetProperty("isExperimentalMode", out var flag)); + Assert.False(flag.GetBoolean()); + } + + [Fact] + public async Task ResumeSession_EmptyMode_Sends_IsExperimentalMode_False_By_Default() + { + await using var server = await FakeTelemetryServer.StartAsync(); + await using var client = new CopilotClient(new CopilotClientOptions + { + Connection = RuntimeConnection.ForUri(server.Url), + Mode = CopilotClientMode.Empty, + BaseDirectory = Path.GetTempPath(), + }); + await client.StartAsync(); + + await client.ResumeSessionAsync("session-1", new ResumeSessionConfig + { + AvailableTools = new ToolSet().AddBuiltIn(BuiltInTools.Isolated).ToList(), + }); + + var resumeParams = server.LastResumeParams ?? throw new InvalidOperationException("session.resume was not captured."); + Assert.True(resumeParams.TryGetProperty("isExperimentalMode", out var flag)); + Assert.False(flag.GetBoolean()); + } + private sealed class FakeTelemetryServer : IAsyncDisposable { private readonly TcpListener _listener; @@ -222,6 +320,10 @@ public string Url public JsonElement? LastConnectParams { get; private set; } + public JsonElement? LastBuiltinPluginParams { get; private set; } + + public int BuiltinPluginSetCount { get; private set; } + public static Task StartAsync() { var listener = new TcpListener(IPAddress.Loopback, 0); @@ -303,10 +405,12 @@ private async Task HandleRequestAsync(Stream stream, JsonElement request, Cancel object? result = method switch { "connect" => CaptureConnect(request), + "plugins.builtin.set" => CaptureBuiltinPluginDirectories(request), "session.create" => CaptureCreate(request), "session.resume" => CaptureResume(request), "session.send" => new Dictionary { ["messageId"] = "message-1" }, "session.destroy" => new Dictionary(), + "session.options.update" => new Dictionary { ["success"] = true }, "runtime.shutdown" => new Dictionary(), _ => throw new InvalidOperationException($"Unexpected RPC method '{method}'."), }; @@ -330,6 +434,13 @@ private async Task HandleRequestAsync(Stream stream, JsonElement request, Cancel }; } + private Dictionary CaptureBuiltinPluginDirectories(JsonElement request) + { + BuiltinPluginSetCount++; + LastBuiltinPluginParams = request.TryGetProperty("params", out var p) ? p.Clone() : null; + return new Dictionary(); + } + private Dictionary CaptureCreate(JsonElement request) { LastCreateParams = request.TryGetProperty("params", out var p) ? p.Clone() : null; diff --git a/dotnet/test/Unit/JsonRpcTests.cs b/dotnet/test/Unit/JsonRpcTests.cs index 9e8b19044..f4acfd355 100644 --- a/dotnet/test/Unit/JsonRpcTests.cs +++ b/dotnet/test/Unit/JsonRpcTests.cs @@ -3,6 +3,7 @@ *--------------------------------------------------------------------------------------------*/ using System.Reflection; +using System.Text; using System.Text.Json; using System.Text.Json.Serialization.Metadata; using Xunit; @@ -93,6 +94,57 @@ public async Task JsonRpc_Cancels_And_Disposes_Pending_Requests() await Assert.ThrowsAnyAsync(() => pending); } + [Fact] + public async Task JsonRpc_Does_Not_Retain_Oversized_Receive_Buffer() + { + var oversizedFrame = CreateResponseFrame( + long.MaxValue, + "ignored", + headerPaddingLength: 1024 * 1024); + var carriedFrame = CreateResponseFrame(1, "carried"); + using var receiveStream = new CoalescedFramesThenWaitStream(oversizedFrame, carriedFrame); + using var rpc = new JsonRpcReflection(Stream.Null, receiveStream); + + var carriedResponse = rpc.InvokeAsync("pending", args: null); + rpc.StartListening(); + + var responseCompleted = await Task.WhenAny( + carriedResponse, + Task.Delay(TimeSpan.FromSeconds(5))); + Assert.Same(carriedResponse, responseCompleted); + Assert.Equal("carried", await carriedResponse); + Assert.True(receiveStream.FramesWereCoalesced); + + var readCompleted = await Task.WhenAny( + receiveStream.PostFrameReadBufferSize, + Task.Delay(TimeSpan.FromSeconds(5))); + Assert.Same(receiveStream.PostFrameReadBufferSize, readCompleted); + Assert.InRange(await receiveStream.PostFrameReadBufferSize, 1, 1024 * 1024); + } + + private static byte[] CreateResponseFrame(long id, string result, int headerPaddingLength = 0) + { + using var bodyStream = new MemoryStream(); + using (var writer = new Utf8JsonWriter(bodyStream)) + { + writer.WriteStartObject(); + writer.WriteString("jsonrpc", "2.0"); + writer.WriteNumber("id", id); + writer.WriteString("result", result); + writer.WriteEndObject(); + } + + var body = bodyStream.ToArray(); + var paddingHeader = headerPaddingLength > 0 + ? $"X-Padding: {new string('x', headerPaddingLength)}\r\n" + : string.Empty; + var header = Encoding.ASCII.GetBytes($"{paddingHeader}Content-Length: {body.Length}\r\n\r\n"); + var frame = new byte[header.Length + body.Length]; + header.CopyTo(frame, 0); + body.CopyTo(frame, header.Length); + return frame; + } + private static int GetRemoteErrorCode(Exception exception) { var property = exception.GetType().GetProperty("ErrorCode", BindingFlags.Instance | BindingFlags.Public); @@ -170,12 +222,17 @@ private sealed class JsonRpcReflection : IDisposable private readonly object _instance; public JsonRpcReflection(Stream stream) + : this(stream, stream) + { + } + + public JsonRpcReflection(Stream sendStream, Stream receiveStream) { _instance = Activator.CreateInstance( JsonRpcType, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, binder: null, - args: [stream, stream, SerializerOptions, null], + args: [sendStream, receiveStream, SerializerOptions, null], culture: null)!; } @@ -198,6 +255,82 @@ public async Task InvokeAsync(string methodName, object?[]? args, Cancella public void Dispose() => ((IDisposable)_instance).Dispose(); } + private sealed class CoalescedFramesThenWaitStream : Stream + { + private readonly TaskCompletionSource _postFrameReadBufferSize = + new(TaskCreationOptions.RunContinuationsAsynchronously); + private readonly byte[] _frames; + private readonly int _firstFrameLength; + private int _offset; + + public CoalescedFramesThenWaitStream(byte[] firstFrame, byte[] secondFrame) + { + _firstFrameLength = firstFrame.Length; + _frames = new byte[firstFrame.Length + secondFrame.Length]; + firstFrame.CopyTo(_frames, 0); + secondFrame.CopyTo(_frames, firstFrame.Length); + } + + public bool FramesWereCoalesced { get; private set; } + + public Task PostFrameReadBufferSize => _postFrameReadBufferSize.Task; + + public override bool CanRead => true; + + public override bool CanSeek => false; + + public override bool CanWrite => false; + + public override long Length => throw new NotSupportedException(); + + public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } + + public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => + ReadCoreAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask(); + +#if NET8_0_OR_GREATER + public override +#else + internal +#endif + ValueTask ReadAsync(Memory destination, CancellationToken cancellationToken = default) => + ReadCoreAsync(destination, cancellationToken); + + public override void Flush() + { + } + + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + + public override void SetLength(long value) => throw new NotSupportedException(); + + public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + + private ValueTask ReadCoreAsync(Memory destination, CancellationToken cancellationToken) + { + if (_offset >= _frames.Length) + { + _postFrameReadBufferSize.TrySetResult(destination.Length); + return new ValueTask(WaitForCancellationAsync(cancellationToken)); + } + + var startingOffset = _offset; + var bytesRead = Math.Min(destination.Length, _frames.Length - _offset); + _frames.AsMemory(_offset, bytesRead).CopyTo(destination); + _offset += bytesRead; + FramesWereCoalesced |= startingOffset < _firstFrameLength && _offset == _frames.Length; + return new ValueTask(bytesRead); + } + + private static async Task WaitForCancellationAsync(CancellationToken cancellationToken) + { + await Task.Delay(Timeout.InfiniteTimeSpan, cancellationToken).ConfigureAwait(false); + return 0; + } + } + private sealed class InMemoryDuplexStream : Stream { private readonly Queue _buffer = new(); diff --git a/dotnet/test/Unit/PermissionHandlerTests.cs b/dotnet/test/Unit/PermissionHandlerTests.cs new file mode 100644 index 000000000..675ea1282 --- /dev/null +++ b/dotnet/test/Unit/PermissionHandlerTests.cs @@ -0,0 +1,127 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +using System.Text.Json; +using System.Text.Json.Serialization.Metadata; +using GitHub.Copilot.Rpc; +using Xunit; + +namespace GitHub.Copilot.Test.Unit; + +public class PermissionHandlerTests +{ + private static readonly JsonSerializerOptions SerializerOptions = new() + { + TypeInfoResolver = new DefaultJsonTypeInfoResolver(), + }; + + [Fact] + public void PermissionEventExposesManagedApprovalRequired() + { + const string json = """ + { + "permissionRequest": { + "kind": "read", + "intention": "Read managed content", + "path": "/workspace/file.txt", + "managedApprovalRequired": true + }, + "requestId": "permission-1" + } + """; + + var data = JsonSerializer.Deserialize( + json, + SerializerOptions); + + Assert.NotNull(data); + var request = Assert.IsType(data.PermissionRequest); + Assert.True(request.ManagedApprovalRequired); + PermissionRequest genericRequest = request; + Assert.True(genericRequest.ManagedApprovalRequired); + } + + [Fact] + public async Task ApproveAllThrowsWhenManagedSettingsEnabled() + { + var request = new PermissionRequest + { + Kind = "read", + ManagedApprovalRequired = true, + }; + + await Assert.ThrowsAsync(() => + PermissionHandler.ApproveAll(request, new PermissionInvocation + { + ManagedSettingsEnabled = true, + })); + } + + [Fact] + public async Task ApproveAllApprovesOrdinaryRequest() + { + var request = new PermissionRequest { Kind = "read" }; + + var decision = await PermissionHandler.ApproveAll(request, new PermissionInvocation()); + + Assert.IsType(decision); + } + + [Fact] + public async Task ApproveAllLeavesManagedRequestPendingWhenSessionFlagIsAbsent() + { + var request = new PermissionRequestRead + { + Intention = "Read managed content", + ManagedApprovalRequired = true, + Path = "/workspace/file.txt", + }; + + var decision = await PermissionHandler.ApproveAll(request, new PermissionInvocation()); + + Assert.IsType(decision); + } + + [Fact] + public async Task ApproveAllLeavesManagedKnownVariantPendingThroughBaseType() + { + PermissionRequest request = new PermissionRequestRead + { + Intention = "Read managed content", + ManagedApprovalRequired = true, + Path = "/workspace/file.txt", + }; + + var decision = await PermissionHandler.ApproveAll(request, new PermissionInvocation()); + + Assert.IsType(decision); + } + + [Fact] + public void DerivedManagedApprovalAccessorForwardsToBaseStorage() + { + var request = new PermissionRequestRead + { + Intention = "Read managed content", + ManagedApprovalRequired = true, + Path = "/workspace/file.txt", + }; + + PermissionRequest genericRequest = request; + Assert.True(genericRequest.ManagedApprovalRequired); + + genericRequest.ManagedApprovalRequired = false; + Assert.False(request.ManagedApprovalRequired); + } + + [Fact] + public async Task ApproveAllLeavesUnknownRequestPending() + { + var request = new PermissionRequest { Kind = "future-managed-kind" }; + + var decision = await PermissionHandler.ApproveAll(request, new PermissionInvocation()); + + Assert.IsType(decision); + } +} diff --git a/dotnet/test/Unit/SerializationTests.cs b/dotnet/test/Unit/SerializationTests.cs index 717fefb19..6edf16809 100644 --- a/dotnet/test/Unit/SerializationTests.cs +++ b/dotnet/test/Unit/SerializationTests.cs @@ -299,6 +299,51 @@ public void SessionRequests_OmitCapiOptions_WhenUnset() Assert.False(resumeDocument.RootElement.TryGetProperty("capi", out _)); } + [Fact] + public void SessionRequests_CanSerializeGitHubMcpToolConfig_WithSdkOptions() + { + var options = GetSerializerOptions(); + var githubConfig = new GitHubMcpToolConfig + { + EnableAllTools = true, + AdditionalToolsets = ["repos"], + AdditionalTools = ["get_issue"], + EnableInsidersMode = true, + DisableFormDeferral = true, + }; + + var createRequestType = GetNestedType(typeof(CopilotClient), "CreateSessionRequest"); + var createRequest = CreateInternalRequest( + createRequestType, + ("GitHubMcpToolConfig", githubConfig)); + using var createDocument = JsonDocument.Parse(JsonSerializer.Serialize(createRequest, createRequestType, options)); + var createConfig = createDocument.RootElement.GetProperty("githubMcpToolConfig"); + Assert.True(createConfig.GetProperty("enableAllTools").GetBoolean()); + Assert.Equal("repos", createConfig.GetProperty("additionalToolsets")[0].GetString()); + Assert.True(createConfig.GetProperty("disableFormDeferral").GetBoolean()); + + var resumeRequestType = GetNestedType(typeof(CopilotClient), "ResumeSessionRequest"); + var resumeRequest = CreateInternalRequest( + resumeRequestType, + ("SessionId", "session-id"), + ("GitHubMcpToolConfig", githubConfig)); + using var resumeDocument = JsonDocument.Parse(JsonSerializer.Serialize(resumeRequest, resumeRequestType, options)); + Assert.True(resumeDocument.RootElement.TryGetProperty("githubMcpToolConfig", out _)); + } + + [Fact] + public void SessionRequests_OmitGitHubMcpToolConfig_WhenUnset() + { + var options = GetSerializerOptions(); + foreach (var requestName in new[] { "CreateSessionRequest", "ResumeSessionRequest" }) + { + var requestType = GetNestedType(typeof(CopilotClient), requestName); + var request = CreateInternalRequest(requestType, ("SessionId", "session-id")); + using var document = JsonDocument.Parse(JsonSerializer.Serialize(request, requestType, options)); + Assert.False(document.RootElement.TryGetProperty("githubMcpToolConfig", out _)); + } + } + [Fact] public void SessionRequests_CanSerializeReasoningSummary_WithSdkOptions() { @@ -366,12 +411,14 @@ public void SessionRequests_CanSerializePluginDirectoriesAndLargeOutput_WithSdkO createRequestType, ("SessionId", "session-id"), ("PluginDirectories", pluginDirs), + ("DisabledMcpServers", new List { "local-files", "remote-github" }), ("LargeOutput", largeOutput)); var createJson = JsonSerializer.Serialize(createRequest, createRequestType, options); using var createDocument = JsonDocument.Parse(createJson); var createRoot = createDocument.RootElement; Assert.Equal("/tmp/plugins/a", createRoot.GetProperty("pluginDirectories")[0].GetString()); + Assert.Equal("local-files", createRoot.GetProperty("disabledMcpServers")[0].GetString()); Assert.Equal("/tmp/plugins/b", createRoot.GetProperty("pluginDirectories")[1].GetString()); var createLargeOutput = createRoot.GetProperty("largeOutput"); Assert.True(createLargeOutput.GetProperty("enabled").GetBoolean()); @@ -383,12 +430,14 @@ public void SessionRequests_CanSerializePluginDirectoriesAndLargeOutput_WithSdkO resumeRequestType, ("SessionId", "session-id"), ("PluginDirectories", pluginDirs), + ("DisabledMcpServers", new List { "local-files", "remote-github" }), ("LargeOutput", largeOutput)); var resumeJson = JsonSerializer.Serialize(resumeRequest, resumeRequestType, options); using var resumeDocument = JsonDocument.Parse(resumeJson); var resumeRoot = resumeDocument.RootElement; Assert.Equal("/tmp/plugins/a", resumeRoot.GetProperty("pluginDirectories")[0].GetString()); + Assert.Equal("local-files", resumeRoot.GetProperty("disabledMcpServers")[0].GetString()); var resumeLargeOutput = resumeRoot.GetProperty("largeOutput"); Assert.True(resumeLargeOutput.GetProperty("enabled").GetBoolean()); Assert.Equal(1024, resumeLargeOutput.GetProperty("maxSizeBytes").GetInt64()); @@ -434,6 +483,7 @@ public void SessionRequests_CanSerializeCitationAgentExclusionAndLimits_WithSdkO createRequestType, ("SessionId", "session-id"), ("EnableCitations", true), + ("EnableFileChangeTracking", true), ("ExcludedBuiltInAgents", excludedAgents), ("SessionLimits", new SessionLimitsConfig { MaxAiCredits = 12.5 })); @@ -441,6 +491,7 @@ public void SessionRequests_CanSerializeCitationAgentExclusionAndLimits_WithSdkO using var createDocument = JsonDocument.Parse(createJson); var createRoot = createDocument.RootElement; Assert.True(createRoot.GetProperty("enableCitations").GetBoolean()); + Assert.True(createRoot.GetProperty("enableFileChangeTracking").GetBoolean()); Assert.Equal("explore", createRoot.GetProperty("excludedBuiltinAgents")[0].GetString()); Assert.Equal(12.5, createRoot.GetProperty("sessionLimits").GetProperty("maxAiCredits").GetDouble()); @@ -449,6 +500,7 @@ public void SessionRequests_CanSerializeCitationAgentExclusionAndLimits_WithSdkO resumeRequestType, ("SessionId", "session-id"), ("EnableCitations", true), + ("EnableFileChangeTracking", true), ("ExcludedBuiltInAgents", excludedAgents), ("SessionLimits", new SessionLimitsConfig { MaxAiCredits = 7.25 })); @@ -456,6 +508,7 @@ public void SessionRequests_CanSerializeCitationAgentExclusionAndLimits_WithSdkO using var resumeDocument = JsonDocument.Parse(resumeJson); var resumeRoot = resumeDocument.RootElement; Assert.True(resumeRoot.GetProperty("enableCitations").GetBoolean()); + Assert.True(resumeRoot.GetProperty("enableFileChangeTracking").GetBoolean()); Assert.Equal("task", resumeRoot.GetProperty("excludedBuiltinAgents")[1].GetString()); Assert.Equal(7.25, resumeRoot.GetProperty("sessionLimits").GetProperty("maxAiCredits").GetDouble()); } @@ -593,6 +646,58 @@ public void CreateSessionRequest_CanSerializeEnableSessionTelemetry_WithSdkOptio Assert.False(root.GetProperty("enableSessionTelemetry").GetBoolean()); } + [Fact] + public void CreateSessionRequest_CanSerializeCustomAgentsLocalOnly_WithSdkOptions() + { + var options = GetSerializerOptions(); + var requestType = GetNestedType(typeof(CopilotClient), "CreateSessionRequest"); + var request = CreateInternalRequest( + requestType, + ("SessionId", "session-id"), + ("CustomAgentsLocalOnly", true)); + + var json = JsonSerializer.Serialize(request, requestType, options); + using var document = JsonDocument.Parse(json); + Assert.True(document.RootElement.GetProperty("customAgentsLocalOnly").GetBoolean()); + } + + [Fact] + public void ResumeSessionRequest_CanSerializeCustomAgentsLocalOnly_WithSdkOptions() + { + var options = GetSerializerOptions(); + var requestType = GetNestedType(typeof(CopilotClient), "ResumeSessionRequest"); + var request = CreateInternalRequest( + requestType, + ("SessionId", "session-id"), + ("CustomAgentsLocalOnly", true)); + + var json = JsonSerializer.Serialize(request, requestType, options); + using var document = JsonDocument.Parse(json); + Assert.True(document.RootElement.GetProperty("customAgentsLocalOnly").GetBoolean()); + } + + [Fact] + public void SessionRequests_OmitCustomAgentsLocalOnly_WhenUnset() + { + var options = GetSerializerOptions(); + + var createRequestType = GetNestedType(typeof(CopilotClient), "CreateSessionRequest"); + var createRequest = CreateInternalRequest( + createRequestType, + ("SessionId", "session-id")); + var createJson = JsonSerializer.Serialize(createRequest, createRequestType, options); + using var createDocument = JsonDocument.Parse(createJson); + Assert.False(createDocument.RootElement.TryGetProperty("customAgentsLocalOnly", out _)); + + var resumeRequestType = GetNestedType(typeof(CopilotClient), "ResumeSessionRequest"); + var resumeRequest = CreateInternalRequest( + resumeRequestType, + ("SessionId", "session-id")); + var resumeJson = JsonSerializer.Serialize(resumeRequest, resumeRequestType, options); + using var resumeDocument = JsonDocument.Parse(resumeJson); + Assert.False(resumeDocument.RootElement.TryGetProperty("customAgentsLocalOnly", out _)); + } + [Fact] public void ResumeSessionRequest_CanSerializeEnableSessionTelemetry_WithSdkOptions() { @@ -609,6 +714,40 @@ public void ResumeSessionRequest_CanSerializeEnableSessionTelemetry_WithSdkOptio Assert.False(root.GetProperty("enableSessionTelemetry").GetBoolean()); } + [Fact] + public void SessionRequests_CanSerializeEnableExperimentalMode_WithSdkOptions() + { + var options = GetSerializerOptions(); + + var createRequestType = GetNestedType(typeof(CopilotClient), "CreateSessionRequest"); + var createRequest = CreateInternalRequest( + createRequestType, + ("SessionId", "session-id"), + ("IsExperimentalMode", false)); + var createRoot = JsonDocument.Parse(JsonSerializer.Serialize(createRequest, createRequestType, options)).RootElement; + Assert.False(createRoot.GetProperty("isExperimentalMode").GetBoolean()); + + var createRequestOmitted = CreateInternalRequest( + createRequestType, + ("SessionId", "session-id")); + var createOmittedRoot = JsonDocument.Parse(JsonSerializer.Serialize(createRequestOmitted, createRequestType, options)).RootElement; + Assert.False(createOmittedRoot.TryGetProperty("isExperimentalMode", out _)); + + var resumeRequestType = GetNestedType(typeof(CopilotClient), "ResumeSessionRequest"); + var resumeRequest = CreateInternalRequest( + resumeRequestType, + ("SessionId", "session-id"), + ("IsExperimentalMode", true)); + var resumeRoot = JsonDocument.Parse(JsonSerializer.Serialize(resumeRequest, resumeRequestType, options)).RootElement; + Assert.True(resumeRoot.GetProperty("isExperimentalMode").GetBoolean()); + + var resumeRequestOmitted = CreateInternalRequest( + resumeRequestType, + ("SessionId", "session-id")); + var resumeOmittedRoot = JsonDocument.Parse(JsonSerializer.Serialize(resumeRequestOmitted, resumeRequestType, options)).RootElement; + Assert.False(resumeOmittedRoot.TryGetProperty("isExperimentalMode", out _)); + } + [Fact] public void CreateSessionRequest_CanSerializeEnableOnDemandInstructionDiscovery_WithSdkOptions() { @@ -790,6 +929,48 @@ public void PermissionDecision_SerializesBaseDiscriminator_WithSdkOptions() Assert.Equal("approve-once", document.RootElement.GetProperty("kind").GetString()); } + [Fact] + public void AgentStopHookInput_DeserializesWireFields_WithSdkOptions() + { + var options = GetSerializerOptions(); + var input = JsonSerializer.Deserialize( + """ + { + "sessionId": "session-1", + "timestamp": 1700000000000, + "cwd": "/repo", + "stopReason": "end_turn", + "transcriptPath": "/tmp/transcript.jsonl", + "stop_hook_active": true + } + """, + options); + + Assert.NotNull(input); + Assert.Equal("session-1", input.SessionId); + Assert.Equal("/repo", input.WorkingDirectory); + Assert.Equal("end_turn", input.StopReason); + Assert.Equal("/tmp/transcript.jsonl", input.TranscriptPath); + Assert.True(input.StopHookActive); + Assert.Equal(DateTimeOffset.FromUnixTimeMilliseconds(1700000000000), input.Timestamp); + } + + [Fact] + public void AgentStopHookOutput_SerializesBlockDecision_WithSdkOptions() + { + var options = GetSerializerOptions(); + var output = new AgentStopHookOutput + { + Decision = "block", + Reason = "finish the remaining work" + }; + + var json = JsonSerializer.SerializeToElement(output, options); + + Assert.Equal("block", json.GetProperty("decision").GetString()); + Assert.Equal("finish the remaining work", json.GetProperty("reason").GetString()); + } + [Fact] public void HooksInvokeResponse_SerializesPreMcpToolCallHookOutput_WithMetaToUse() { diff --git a/dotnet/test/Unit/SessionEventSerializationTests.cs b/dotnet/test/Unit/SessionEventSerializationTests.cs index 64e28a5ae..326ac3f3c 100644 --- a/dotnet/test/Unit/SessionEventSerializationTests.cs +++ b/dotnet/test/Unit/SessionEventSerializationTests.cs @@ -368,4 +368,65 @@ public void McpOauthRequiredData_Preserves_Static_Client_Secret() Assert.NotNull(authEvent.Data.StaticClientConfig); Assert.Equal("static-secret", authEvent.Data.StaticClientConfig.ClientSecret); } + + [Fact] + public void ManagedSettingsResolvedData_Preserves_Client_Provenance() + { + Assert.Equal("server", ManagedSettingsResolvedSource.Server.Value); + Assert.Equal("device", ManagedSettingsResolvedSource.Device.Value); + Assert.Equal("client", ManagedSettingsResolvedSource.Client.Value); + Assert.Equal("mixed", ManagedSettingsResolvedSource.Mixed.Value); + Assert.Equal("none", ManagedSettingsResolvedSource.None.Value); + + const string clientJson = """ + { + "id": "11111111-1111-1111-1111-111111111111", + "timestamp": "2026-03-15T21:26:54.987Z", + "parentId": null, + "type": "session.managed_settings_resolved", + "data": { + "source": "client", + "serverManaged": false, + "deviceManaged": false, + "clientManaged": true, + "failClosed": false, + "bypassPermissionsDisabled": true, + "managedKeys": ["permissions"] + } + } + """; + + var clientEvent = Assert.IsType( + SessionEvent.FromJson(clientJson)); + Assert.Equal(ManagedSettingsResolvedSource.Client, clientEvent.Data.Source); + Assert.True(clientEvent.Data.ClientManaged); + using (var document = JsonDocument.Parse(clientEvent.ToJson())) + { + Assert.True(document.RootElement.GetProperty("data").GetProperty("clientManaged").GetBoolean()); + } + + const string mixedJson = """ + { + "id": "22222222-2222-2222-2222-222222222222", + "timestamp": "2026-03-15T21:26:54.987Z", + "parentId": null, + "type": "session.managed_settings_resolved", + "data": { + "source": "mixed", + "serverManaged": true, + "deviceManaged": true, + "failClosed": false, + "bypassPermissionsDisabled": true, + "managedKeys": ["permissions"] + } + } + """; + + var mixedEvent = Assert.IsType( + SessionEvent.FromJson(mixedJson)); + Assert.Equal(ManagedSettingsResolvedSource.Mixed, mixedEvent.Data.Source); + Assert.Null(mixedEvent.Data.ClientManaged); + using var mixedDocument = JsonDocument.Parse(mixedEvent.ToJson()); + Assert.False(mixedDocument.RootElement.GetProperty("data").TryGetProperty("clientManaged", out _)); + } } diff --git a/go/README.md b/go/README.md index 44ba01d66..d8588699c 100644 --- a/go/README.md +++ b/go/README.md @@ -55,7 +55,6 @@ func main() { } defer client.Stop() - // Create a session (OnPermissionRequest is optional; ApproveAll allows every tool) session, err := client.CreateSession(context.Background(), &copilot.SessionConfig{ Model: "gpt-5", OnPermissionRequest: copilot.PermissionHandler.ApproveAll, @@ -89,6 +88,12 @@ func main() { } ``` +When targeting MCP tools configured through `MCPServers`, remember the runtime +tool name is `-`. For `AvailableTools` and +`ExcludedTools`, prefer the source-qualified form +`mcp:-`. For `CustomAgents[].Tools` and +`DefaultAgent.ExcludedTools`, use `-` directly. + ## Distributing your application with an embedded GitHub Copilot CLI The SDK supports bundling, using Go's `embed` package, the Copilot CLI binary within your application's distribution. @@ -193,7 +198,7 @@ Event types: `SessionLifecycleCreated`, `SessionLifecycleDeleted`, `SessionLifec When `Path` is empty for stdio/tcp, the SDK uses the bundled CLI (or `COPILOT_CLI_PATH` env var). `StdioConnection` and `TCPConnection` accept an optional connection-level `Env`. Set environment variables via **either** the client-level `Env` option or the connection's `Env`, not both (setting both panics); prefer the connection-level `Env`. -- `WorkingDirectory` (string): Working directory for the runtime process +- `WorkingDirectory` (string): Working directory for the runtime process (default: current process working directory) - `BaseDirectory` (string): Base directory for Copilot data (session state, config, etc.). Sets `COPILOT_HOME` on the spawned runtime. When empty, the runtime defaults to `~/.copilot`. Ignored with `URIConnection`. This does **not** affect where the Go SDK extracts the embedded CLI binary; use `embeddedcli.Config.Dir` for the extraction/cache location. - `LogLevel` (string): Log level. When empty (default), the runtime uses its own default level (the SDK does not pass `--log-level`). - `Env` ([]string): Environment variables for the runtime process (default: inherits from current process) @@ -205,7 +210,7 @@ Event types: `SessionLifecycleCreated`, `SessionLifecycleDeleted`, `SessionLifec **SessionConfig:** - `Model` (string): Model to use ("gpt-5", "claude-sonnet-4.5", etc.). **Required when using custom provider.** -- `ReasoningEffort` (string): Reasoning effort level for models that support it ("low", "medium", "high", "xhigh"). Use `ListModels()` to check which models support this option. +- `ReasoningEffort` (string): Reasoning effort level for models that support it ("low", "medium", "high", "xhigh", "max"). Use `ListModels()` to check which models support this option. - `SessionID` (string): Custom session ID - `Tools` ([]Tool): Custom tools exposed to the CLI - `SystemMessage` (\*SystemMessageConfig): System message configuration. Supports three modes: @@ -215,7 +220,9 @@ Event types: `SessionLifecycleCreated`, `SessionLifecycleDeleted`, `SessionLifec - `Provider` (\*ProviderConfig): Custom API provider configuration (BYOK). See [Custom Providers](#custom-providers) section. - `Streaming` (*bool): Enable streaming delta events (nil = runtime default) - `InfiniteSessions` (\*InfiniteSessionConfig): Automatic context compaction configuration -- `OnPermissionRequest` (PermissionHandlerFunc): Optional handler called before each tool execution to approve or deny it. When nil, permission requests are emitted as events and left pending for manual resolution. Use `copilot.PermissionHandler.ApproveAll` to allow everything, or provide a custom function for fine-grained control. See [Permission Handling](#permission-handling) section. +- `WorkingDirectory` (string): Working directory for the session (default: runtime process working directory) +- `EnableSessionStore` (\*bool): Enables the cross-session store for search and retrieval across sessions. When unset in `ModeCopilotCli`, the runtime default applies (enabled). In `ModeEmpty`, defaults to disabled. +- `OnPermissionRequest` (PermissionHandlerFunc): Optional handler called before each tool execution to approve or deny it. When nil, permission requests are emitted as events and left pending for manual resolution. `copilot.PermissionHandler.ApproveAll` approves requests when managed settings are disabled and returns an error when `EnableManagedSettings` is true. Custom handlers can inspect `RequiresManagedApproval()` for human-facing confirmation logic. See [Permission Handling](#permission-handling) section. - `OnUserInputRequest` (UserInputHandler): Handler for user input requests from the agent (enables ask_user tool). See [User Input Requests](#user-input-requests) section. - `Hooks` (\*SessionHooks): Hook handlers for session lifecycle events. See [Session Hooks](#session-hooks) section. - `Commands` ([]CommandDefinition): Slash-commands registered for this session. See [Commands](#commands) section. @@ -591,7 +598,7 @@ The SDK supports custom OpenAI-compatible API providers (BYOK - Bring Your Own K - `APIKey` (string): API key (optional for local providers like Ollama) - `BearerToken` (string): Bearer token for authentication (takes precedence over APIKey) - `WireAPI` (string): API format for OpenAI/Azure - "completions" or "responses" (default: "completions") -- `Azure.APIVersion` (string): Azure API version (default: "2024-10-21") +- `Azure.APIVersion` (string): Azure API version; when empty, the runtime uses the GA versionless `v1` route **Example with Ollama:** @@ -646,7 +653,7 @@ session, err := client.CreateSession(context.Background(), &copilot.SessionConfi The SDK supports OpenTelemetry for distributed tracing. Provide a `Telemetry` config to enable trace export and automatic W3C Trace Context propagation. ```go -client, err := copilot.NewClient(copilot.ClientOptions{ +client := copilot.NewClient(&copilot.ClientOptions{ Telemetry: &copilot.TelemetryConfig{ OTLPEndpoint: "http://localhost:4318", }, @@ -674,7 +681,7 @@ An `OnPermissionRequest` handler is optional when you create or resume a session ### Approve All (simplest) -Use the built-in `PermissionHandler.ApproveAll` helper to allow every tool call without any checks: +Use the built-in `PermissionHandler.ApproveAll` helper when managed settings are disabled: ```go session, err := client.CreateSession(context.Background(), &copilot.SessionConfig{ @@ -683,9 +690,11 @@ session, err := client.CreateSession(context.Background(), &copilot.SessionConfi }) ``` +When `EnableManagedSettings` is true for the session, `ApproveAll` returns an error. Use a custom handler for managed sessions; request-level `RequiresManagedApproval()` remains available for human-facing confirmation logic. + ### Custom Permission Handler -Provide your own `PermissionHandlerFunc` to inspect each request and apply custom logic: +Provide your own `PermissionHandlerFunc` to inspect each request and apply custom logic. Check `RequiresManagedApproval()` before any automatic approval: ```go import ( @@ -698,6 +707,10 @@ import ( session, err := client.CreateSession(context.Background(), &copilot.SessionConfig{ Model: "gpt-5", OnPermissionRequest: func(request copilot.PermissionRequest, invocation copilot.PermissionInvocation) (rpc.PermissionDecision, error) { + if request.RequiresManagedApproval() { + return &rpc.PermissionDecisionNoResult{}, nil + } + // Type-switch on the discriminated PermissionRequest variants to // access per-kind fields: if shell, ok := request.(*copilot.PermissionRequestShell); ok { @@ -965,6 +978,25 @@ Communicates with CLI via TCP socket. Useful for distributed scenarios. - `COPILOT_CLI_PATH` - Path to the Copilot CLI executable +## Development + +Tests require a supported [Node.js version](../nodejs/README.md#prerequisites). From the repository root: + +```bash +cd nodejs +npm ci +``` + +```bash +cd test/harness +npm ci +``` + +```bash +cd go +./test.sh +``` + ## License MIT diff --git a/go/client.go b/go/client.go index f243268aa..fb02897f9 100644 --- a/go/client.go +++ b/go/client.go @@ -38,6 +38,7 @@ import ( "net" "os" "os/exec" + "path/filepath" "regexp" "strconv" "strings" @@ -225,6 +226,12 @@ func NewClient(options *ClientOptions) *Client { if options != nil { opts = *options } + for _, path := range opts.BuiltinPluginDirectories { + if !filepath.IsAbs(path) { + panic(fmt.Sprintf("BuiltinPluginDirectories must contain only absolute paths: %s", path)) + } + } + opts.BuiltinPluginDirectories = append([]string(nil), opts.BuiltinPluginDirectories...) // Resolve the connection. An explicit connection always wins; otherwise // honor the same process/environment override as the other SDKs. @@ -453,6 +460,21 @@ func (c *Client) Start(ctx context.Context) error { return errors.Join(err, killErr) } + if len(c.options.BuiltinPluginDirectories) > 0 { + if _, err := c.client.Request(ctx, "plugins.builtin.set", map[string]any{ + "paths": c.options.BuiltinPluginDirectories, + }); err != nil { + c.client.Stop() + c.client = nil + c.conn = nil + c.RPC = nil + c.internalRPC = nil + killErr := c.killProcess() + c.state = stateError + return errors.Join(err, killErr) + } + } + // If a session filesystem provider was configured, register it. if c.options.SessionFS != nil { req := &rpc.SessionFSSetProviderRequest{ @@ -750,6 +772,10 @@ func extractTransformCallbacks(config *SystemMessageConfig) (*SystemMessageConfi return wireConfig, callbacks } +func hasManagedSettings(enableManagedSettings *bool, managedSettings *ManagedSettings) bool { + return (enableManagedSettings != nil && *enableManagedSettings) || managedSettings != nil +} + func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Session, error) { if config == nil { config = &SessionConfig{} @@ -795,13 +821,16 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses req.Models = config.Models req.EnableSessionTelemetry = config.EnableSessionTelemetry req.EnableCitations = config.EnableCitations + req.EnableFileChangeTracking = config.EnableFileChangeTracking req.SessionLimits = config.SessionLimits + req.IsExperimentalMode = config.EnableExperimentalMode req.SkipCustomInstructions = config.SkipCustomInstructions req.CustomAgentsLocalOnly = config.CustomAgentsLocalOnly req.CoauthorEnabled = config.CoauthorEnabled req.ManageScheduleEnabled = config.ManageScheduleEnabled req.ModelCapabilities = config.ModelCapabilities req.WorkingDirectory = config.WorkingDirectory + req.AdditionalDirectories = config.AdditionalDirectories req.MCPServers = config.MCPServers req.MCPOAuthTokenStorage = config.MCPOAuthTokenStorage req.EnvValueMode = "direct" @@ -812,6 +841,9 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses req.PluginDirectories = config.PluginDirectories req.InstructionDirectories = config.InstructionDirectories req.DisabledSkills = config.DisabledSkills + if config.DisabledMCPServers != nil { + req.DisabledMCPServers = &config.DisabledMCPServers + } req.InfiniteSessions = config.InfiniteSessions req.LargeOutput = config.LargeOutput req.ToolSearch = config.ToolSearch @@ -828,6 +860,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses req.ExtensionInfo = config.ExtensionInfo req.ExpAssignments = config.ExpAssignments req.EnableManagedSettings = config.EnableManagedSettings + req.ManagedSettings = config.ManagedSettings if len(config.Commands) > 0 { cmds := make([]wireCommand, 0, len(config.Commands)) @@ -848,6 +881,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses if config.EnableMCPApps { req.RequestMCPApps = Bool(true) } + req.GitHubMCPToolConfig = config.GitHubMCPToolConfig if config.Streaming != nil { req.Streaming = config.Streaming @@ -868,9 +902,11 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses config.Hooks.OnPostToolUse != nil || config.Hooks.OnPostToolUseFailure != nil || config.Hooks.OnUserPromptSubmitted != nil || + config.Hooks.OnUserPromptTransformed != nil || config.Hooks.OnSessionStart != nil || config.Hooks.OnSessionEnd != nil || - config.Hooks.OnErrorOccurred != nil) { + config.Hooks.OnErrorOccurred != nil || + config.Hooks.OnAgentStop != nil) { req.Hooks = Bool(true) } if config.OnPermissionRequest != nil { @@ -905,7 +941,12 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses // message is dispatched) so notifications for the new session id are // routed to a registered session. initializeSession := func(sessionID string) (*Session, error) { - s := newSession(sessionID, c.client, "") + s := newSession( + sessionID, + c.client, + "", + hasManagedSettings(config.EnableManagedSettings, config.ManagedSettings), + ) s.registerTools(config.Tools) s.registerPermissionHandler(config.OnPermissionRequest) @@ -1115,6 +1156,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, req.Providers = config.Providers req.Models = config.Models req.EnableSessionTelemetry = config.EnableSessionTelemetry + req.IsExperimentalMode = config.EnableExperimentalMode req.SkipCustomInstructions = config.SkipCustomInstructions req.CustomAgentsLocalOnly = config.CustomAgentsLocalOnly req.CoauthorEnabled = config.CoauthorEnabled @@ -1129,6 +1171,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, req.ToolFilterPrecedence = precedence req.ExcludedBuiltInAgents = config.ExcludedBuiltInAgents req.EnableCitations = config.EnableCitations + req.EnableFileChangeTracking = config.EnableFileChangeTracking req.SessionLimits = config.SessionLimits if config.Streaming != nil { req.Streaming = config.Streaming @@ -1149,12 +1192,15 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, config.Hooks.OnPostToolUse != nil || config.Hooks.OnPostToolUseFailure != nil || config.Hooks.OnUserPromptSubmitted != nil || + config.Hooks.OnUserPromptTransformed != nil || config.Hooks.OnSessionStart != nil || config.Hooks.OnSessionEnd != nil || - config.Hooks.OnErrorOccurred != nil) { + config.Hooks.OnErrorOccurred != nil || + config.Hooks.OnAgentStop != nil) { req.Hooks = Bool(true) } req.WorkingDirectory = config.WorkingDirectory + req.AdditionalDirectories = config.AdditionalDirectories req.ConfigDir = config.ConfigDirectory req.EnableConfigDiscovery = config.EnableConfigDiscovery req.SkipEmbeddingRetrieval = config.SkipEmbeddingRetrieval @@ -1179,6 +1225,9 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, req.PluginDirectories = config.PluginDirectories req.InstructionDirectories = config.InstructionDirectories req.DisabledSkills = config.DisabledSkills + if config.DisabledMCPServers != nil { + req.DisabledMCPServers = &config.DisabledMCPServers + } req.InfiniteSessions = config.InfiniteSessions req.LargeOutput = config.LargeOutput req.ToolSearch = config.ToolSearch @@ -1195,6 +1244,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, req.ExtensionInfo = config.ExtensionInfo req.ExpAssignments = config.ExpAssignments req.EnableManagedSettings = config.EnableManagedSettings + req.ManagedSettings = config.ManagedSettings if config.OnPermissionRequest != nil { req.RequestPermission = Bool(true) } @@ -1218,6 +1268,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, if config.EnableMCPApps { req.RequestMCPApps = Bool(true) } + req.GitHubMCPToolConfig = config.GitHubMCPToolConfig traceparent, tracestate := getTraceContext(ctx) req.Traceparent = traceparent @@ -1225,7 +1276,12 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, // Create and register the session before issuing the RPC so that // events emitted by the CLI (e.g. session.start) are not dropped. - session := newSession(sessionID, c.client, "") + session := newSession( + sessionID, + c.client, + "", + hasManagedSettings(config.EnableManagedSettings, config.ManagedSettings), + ) session.registerTools(config.Tools) session.registerPermissionHandler(config.OnPermissionRequest) diff --git a/go/client_test.go b/go/client_test.go index 3e4675d0b..f21442679 100644 --- a/go/client_test.go +++ b/go/client_test.go @@ -139,6 +139,200 @@ func TestClient_URLParsing(t *testing.T) { }) } +func TestClient_BuiltinPluginDirectories(t *testing.T) { + t.Run("default and empty do not call RPC", func(t *testing.T) { + for _, paths := range [][]string{nil, []string{}} { + t.Run(fmt.Sprintf("len=%d", len(paths)), func(t *testing.T) { + url, requests, cleanup := newStartupRPCServer(t) + defer cleanup() + + client := NewClient(&ClientOptions{ + Connection: URIConnection{URL: url}, + BuiltinPluginDirectories: paths, + }) + if err := client.Start(t.Context()); err != nil { + t.Fatalf("Start failed: %v", err) + } + defer client.ForceStop() + + if got := countMethod(requests(), "plugins.builtin.set"); got != 0 { + t.Fatalf("plugins.builtin.set call count = %d, want 0", got) + } + }) + } + }) + + t.Run("configured paths call RPC once", func(t *testing.T) { + url, requests, cleanup := newStartupRPCServer(t) + defer cleanup() + cwd, err := os.Getwd() + if err != nil { + t.Fatalf("Getwd failed: %v", err) + } + paths := []string{ + filepath.Join(cwd, "plugins", "core"), + filepath.Join(cwd, "plugins", "github"), + } + + client := NewClient(&ClientOptions{ + Connection: URIConnection{URL: url}, + BuiltinPluginDirectories: paths, + }) + if err := client.Start(t.Context()); err != nil { + t.Fatalf("Start failed: %v", err) + } + defer client.ForceStop() + + var calls []startupRPCRequest + for _, request := range requests() { + if request.Method == "plugins.builtin.set" { + calls = append(calls, request) + } + } + if len(calls) != 1 { + t.Fatalf("plugins.builtin.set call count = %d, want 1", len(calls)) + } + var payload struct { + Paths []string `json:"paths"` + } + if err := json.Unmarshal(calls[0].Params, &payload); err != nil { + t.Fatalf("decode plugins.builtin.set params: %v", err) + } + if !reflect.DeepEqual(payload.Paths, paths) { + t.Fatalf("paths = %v, want %v", payload.Paths, paths) + } + }) + + t.Run("relative path panics", func(t *testing.T) { + defer func() { + if recovered := recover(); recovered == nil { + t.Fatal("expected NewClient to panic") + } + }() + NewClient(&ClientOptions{BuiltinPluginDirectories: []string{"plugins/core"}}) + }) + + t.Run("startup RPC failure clears transport for reconnect", func(t *testing.T) { + url, _, cleanup := newStartupRPCServerWithBuiltinFailure(t, true) + defer cleanup() + cwd, err := os.Getwd() + if err != nil { + t.Fatalf("Getwd failed: %v", err) + } + client := NewClient(&ClientOptions{ + Connection: URIConnection{URL: url}, + BuiltinPluginDirectories: []string{filepath.Join(cwd, "plugins", "core")}, + }) + + if err := client.Start(t.Context()); err == nil { + t.Fatal("Start unexpectedly succeeded") + } + if client.client != nil { + t.Fatal("client transport was not cleared after startup RPC failure") + } + if client.conn != nil { + t.Fatal("connection was not cleared after startup RPC failure") + } + if client.RPC != nil { + t.Fatal("typed RPC client was not cleared after startup RPC failure") + } + if client.internalRPC != nil { + t.Fatal("internal RPC client was not cleared after startup RPC failure") + } + + if err := client.Start(t.Context()); err != nil { + t.Fatalf("second Start failed: %v", err) + } + defer client.ForceStop() + }) +} + +type startupRPCRequest struct { + Method string + Params json.RawMessage +} + +func newStartupRPCServer(t *testing.T) (string, func() []startupRPCRequest, func()) { + return newStartupRPCServerWithBuiltinFailure(t, false) +} + +func newStartupRPCServerWithBuiltinFailure(t *testing.T, failFirstBuiltin bool) (string, func() []startupRPCRequest, func()) { + t.Helper() + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("listen: %v", err) + } + + var mux sync.Mutex + var requests []startupRPCRequest + serverReady := make(chan *jsonrpc2.Client, 8) + var builtinSetCount int + go func() { + for { + conn, acceptErr := listener.Accept() + if acceptErr != nil { + return + } + server := jsonrpc2.NewClient(conn, conn) + record := func(method string, params json.RawMessage) { + mux.Lock() + requests = append(requests, startupRPCRequest{ + Method: method, + Params: append(json.RawMessage(nil), params...), + }) + mux.Unlock() + } + server.SetRequestHandler("connect", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { + record("connect", params) + return []byte(`{"ok":true,"protocolVersion":3,"version":"test"}`), nil + }) + server.SetRequestHandler("plugins.builtin.set", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { + record("plugins.builtin.set", params) + mux.Lock() + builtinSetCount++ + shouldFail := failFirstBuiltin && builtinSetCount == 1 + mux.Unlock() + if shouldFail { + return nil, &jsonrpc2.Error{Code: -32000, Message: "builtin registration failed"} + } + return []byte(`{}`), nil + }) + server.Start() + serverReady <- server + } + }() + + snapshot := func() []startupRPCRequest { + mux.Lock() + defer mux.Unlock() + return append([]startupRPCRequest(nil), requests...) + } + cleanup := func() { + listener.Close() + for { + select { + case server := <-serverReady: + server.Stop() + case <-time.After(time.Second): + return + default: + return + } + } + } + return listener.Addr().String(), snapshot, cleanup +} + +func countMethod(requests []startupRPCRequest, method string) int { + count := 0 + for _, request := range requests { + if request.Method == method { + count++ + } + } + return count +} + func TestClient_StopRequestsRuntimeShutdownForOwnedProcess(t *testing.T) { rpcClient, server, shutdownCalled := newRuntimeShutdownRpcPair(t) client := &Client{ @@ -251,6 +445,60 @@ func TestClient_ForwardsCapiOptionsToSessionRequests(t *testing.T) { assertCapiEnableWebSocketResponses(t, <-resumeParams) } +func TestClient_ForwardsAdditionalDirectoriesToSessionRequests(t *testing.T) { + rpcClient, server, _ := newRuntimeShutdownRpcPair(t) + t.Cleanup(server.Stop) + client := &Client{ + client: rpcClient, + RPC: rpc.NewServerRPC(rpcClient), + sessions: make(map[string]*Session), + } + + createParams := make(chan json.RawMessage, 1) + server.SetRequestHandler("session.create", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { + createParams <- append(json.RawMessage(nil), params...) + sessionID := sessionIDFromParams(t, params) + return []byte(`{"sessionId":"` + sessionID + `","workspacePath":"/workspace"}`), nil + }) + + _, err := client.CreateSession(t.Context(), &SessionConfig{ + AdditionalDirectories: []string{"/repo/shared", "/repo/generated"}, + }) + if err != nil { + t.Fatalf("CreateSession failed: %v", err) + } + assertAdditionalDirectories(t, <-createParams, []string{"/repo/shared", "/repo/generated"}) + + resumeParams := make(chan json.RawMessage, 1) + server.SetRequestHandler("session.resume", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { + resumeParams <- append(json.RawMessage(nil), params...) + return []byte(`{"sessionId":"resumed-additional-directories","workspacePath":"/workspace"}`), nil + }) + + _, err = client.ResumeSessionWithOptions( + t.Context(), + "resumed-additional-directories", + &ResumeSessionConfig{AdditionalDirectories: []string{"/repo/resumed"}}, + ) + if err != nil { + t.Fatalf("ResumeSessionWithOptions failed: %v", err) + } + assertAdditionalDirectories(t, <-resumeParams, []string{"/repo/resumed"}) +} + +func assertAdditionalDirectories(t *testing.T, params json.RawMessage, want []string) { + t.Helper() + var payload struct { + AdditionalDirectories []string `json:"additionalDirectories"` + } + if err := json.Unmarshal(params, &payload); err != nil { + t.Fatalf("failed to decode request params: %v", err) + } + if !reflect.DeepEqual(payload.AdditionalDirectories, want) { + t.Fatalf("additionalDirectories = %v, want %v", payload.AdditionalDirectories, want) + } +} + func TestClient_ForwardsCanvasProviderToSessionRequests(t *testing.T) { rpcClient, server, _ := newRuntimeShutdownRpcPair(t) t.Cleanup(server.Stop) @@ -344,14 +592,15 @@ func TestClient_ForwardsNewSessionOptionsToSessionRequests(t *testing.T) { }) _, err := client.CreateSession(t.Context(), &SessionConfig{ - ExcludedBuiltInAgents: []string{"explore"}, - EnableCitations: Bool(true), - SessionLimits: &rpc.SessionLimitsConfig{MaxAiCredits: float64Ptr(30)}, + ExcludedBuiltInAgents: []string{"explore"}, + EnableCitations: Bool(true), + EnableFileChangeTracking: Bool(true), + SessionLimits: &rpc.SessionLimitsConfig{MaxAiCredits: float64Ptr(30)}, }) if err != nil { t.Fatalf("CreateSession failed: %v", err) } - assertNewSessionOptions(t, <-createParams, true, "explore", 30) + assertNewSessionOptions(t, <-createParams, true, true, "explore", 30) resumeParams := make(chan json.RawMessage, 1) server.SetRequestHandler("session.resume", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { @@ -360,14 +609,15 @@ func TestClient_ForwardsNewSessionOptionsToSessionRequests(t *testing.T) { }) _, err = client.ResumeSessionWithOptions(t.Context(), "resumed-options", &ResumeSessionConfig{ - ExcludedBuiltInAgents: []string{"task"}, - EnableCitations: Bool(false), - SessionLimits: &rpc.SessionLimitsConfig{MaxAiCredits: float64Ptr(15)}, + ExcludedBuiltInAgents: []string{"task"}, + EnableCitations: Bool(false), + EnableFileChangeTracking: Bool(false), + SessionLimits: &rpc.SessionLimitsConfig{MaxAiCredits: float64Ptr(15)}, }) if err != nil { t.Fatalf("ResumeSessionWithOptions failed: %v", err) } - assertNewSessionOptions(t, <-resumeParams, false, "task", 15) + assertNewSessionOptions(t, <-resumeParams, false, false, "task", 15) } func assertCapiEnableWebSocketResponses(t *testing.T, params json.RawMessage) { @@ -391,6 +641,7 @@ func assertNewSessionOptions( t *testing.T, params json.RawMessage, expectedCitations bool, + expectedFileChangeTracking bool, expectedAgent string, expectedCredits float64, ) { @@ -403,6 +654,9 @@ func assertNewSessionOptions( if decoded["enableCitations"] != expectedCitations { t.Fatalf("expected enableCitations=%v, got %v", expectedCitations, decoded["enableCitations"]) } + if decoded["enableFileChangeTracking"] != expectedFileChangeTracking { + t.Fatalf("expected enableFileChangeTracking=%v, got %v", expectedFileChangeTracking, decoded["enableFileChangeTracking"]) + } agents, ok := decoded["excludedBuiltinAgents"].([]any) if !ok || len(agents) != 1 || agents[0] != expectedAgent { t.Fatalf("expected excludedBuiltinAgents=[%q], got %#v", expectedAgent, decoded["excludedBuiltinAgents"]) @@ -1049,9 +1303,11 @@ func TestSessionRequests_PluginDirectoriesAndLargeOutput(t *testing.T) { "outputDir": "/tmp/large-output", } expectedPluginDirs := []any{"/tmp/plugins/a", "/tmp/plugins/b"} + expectedDisabledMCPServers := []any{"local-files", "remote-github"} + disabledMCPServers := []string{"local-files", "remote-github"} t.Run("create includes pluginDirectories and largeOutput in JSON when set", func(t *testing.T) { - req := createSessionRequest{PluginDirectories: pluginDirs, LargeOutput: largeOutput} + req := createSessionRequest{PluginDirectories: pluginDirs, DisabledMCPServers: &disabledMCPServers, LargeOutput: largeOutput} data, err := json.Marshal(req) if err != nil { t.Fatalf("Failed to marshal: %v", err) @@ -1063,13 +1319,16 @@ func TestSessionRequests_PluginDirectoriesAndLargeOutput(t *testing.T) { if !reflect.DeepEqual(m["pluginDirectories"], expectedPluginDirs) { t.Errorf("Expected pluginDirectories %v, got %v", expectedPluginDirs, m["pluginDirectories"]) } + if !reflect.DeepEqual(m["disabledMcpServers"], expectedDisabledMCPServers) { + t.Errorf("Expected disabledMcpServers %v, got %v", expectedDisabledMCPServers, m["disabledMcpServers"]) + } if !reflect.DeepEqual(m["largeOutput"], expectedLargeOutput) { t.Errorf("Expected largeOutput %v, got %v", expectedLargeOutput, m["largeOutput"]) } }) t.Run("resume includes pluginDirectories and largeOutput in JSON when set", func(t *testing.T) { - req := resumeSessionRequest{SessionID: "s1", PluginDirectories: pluginDirs, LargeOutput: largeOutput} + req := resumeSessionRequest{SessionID: "s1", PluginDirectories: pluginDirs, DisabledMCPServers: &disabledMCPServers, LargeOutput: largeOutput} data, err := json.Marshal(req) if err != nil { t.Fatalf("Failed to marshal: %v", err) @@ -1081,11 +1340,36 @@ func TestSessionRequests_PluginDirectoriesAndLargeOutput(t *testing.T) { if !reflect.DeepEqual(m["pluginDirectories"], expectedPluginDirs) { t.Errorf("Expected pluginDirectories %v, got %v", expectedPluginDirs, m["pluginDirectories"]) } + if !reflect.DeepEqual(m["disabledMcpServers"], expectedDisabledMCPServers) { + t.Errorf("Expected disabledMcpServers %v, got %v", expectedDisabledMCPServers, m["disabledMcpServers"]) + } if !reflect.DeepEqual(m["largeOutput"], expectedLargeOutput) { t.Errorf("Expected largeOutput %v, got %v", expectedLargeOutput, m["largeOutput"]) } }) + t.Run("create and resume include explicit empty disabledMcpServers", func(t *testing.T) { + emptyDisabledMCPServers := []string{} + requests := []any{ + createSessionRequest{DisabledMCPServers: &emptyDisabledMCPServers}, + resumeSessionRequest{SessionID: "s1", DisabledMCPServers: &emptyDisabledMCPServers}, + } + + for _, request := range requests { + data, err := json.Marshal(request) + if err != nil { + t.Fatalf("Failed to marshal: %v", err) + } + var m map[string]any + if err := json.Unmarshal(data, &m); err != nil { + t.Fatalf("Failed to unmarshal: %v", err) + } + if value, ok := m["disabledMcpServers"]; !ok || !reflect.DeepEqual(value, []any{}) { + t.Errorf("Expected explicit empty disabledMcpServers, got %v", value) + } + } + }) + t.Run("create omits pluginDirectories and largeOutput when nil", func(t *testing.T) { req := createSessionRequest{} data, err := json.Marshal(req) @@ -1099,10 +1383,28 @@ func TestSessionRequests_PluginDirectoriesAndLargeOutput(t *testing.T) { if _, ok := m["pluginDirectories"]; ok { t.Errorf("Expected pluginDirectories to be omitted") } + if _, ok := m["disabledMcpServers"]; ok { + t.Error("Expected disabledMcpServers to be omitted") + } if _, ok := m["largeOutput"]; ok { t.Errorf("Expected largeOutput to be omitted") } }) + + t.Run("resume omits disabledMcpServers when nil", func(t *testing.T) { + req := resumeSessionRequest{SessionID: "s1"} + data, err := json.Marshal(req) + if err != nil { + t.Fatalf("Failed to marshal: %v", err) + } + var m map[string]any + if err := json.Unmarshal(data, &m); err != nil { + t.Fatalf("Failed to unmarshal: %v", err) + } + if _, ok := m["disabledMcpServers"]; ok { + t.Error("Expected disabledMcpServers to be omitted") + } + }) } func TestSessionRequests_Memory(t *testing.T) { @@ -2333,6 +2635,63 @@ func TestCreateSessionRequest_RequestMCPApps(t *testing.T) { }) } +func TestSessionRequests_EnableExperimentalMode(t *testing.T) { + t.Run("create forwards enableExperimentalMode when explicitly false", func(t *testing.T) { + req := createSessionRequest{ + IsExperimentalMode: Bool(false), + } + data, err := json.Marshal(req) + if err != nil { + t.Fatalf("Failed to marshal: %v", err) + } + var m map[string]any + if err := json.Unmarshal(data, &m); err != nil { + t.Fatalf("Failed to unmarshal: %v", err) + } + if m["isExperimentalMode"] != false { + t.Errorf("Expected isExperimentalMode to be false, got %v", m["isExperimentalMode"]) + } + }) + + t.Run("create omits enableExperimentalMode when unset", func(t *testing.T) { + req := createSessionRequest{} + data, _ := json.Marshal(req) + var m map[string]any + json.Unmarshal(data, &m) + if _, ok := m["isExperimentalMode"]; ok { + t.Error("Expected isExperimentalMode to be omitted when not set") + } + }) + + t.Run("resume forwards enableExperimentalMode when explicitly true", func(t *testing.T) { + req := resumeSessionRequest{ + SessionID: "s1", + IsExperimentalMode: Bool(true), + } + data, err := json.Marshal(req) + if err != nil { + t.Fatalf("Failed to marshal: %v", err) + } + var m map[string]any + if err := json.Unmarshal(data, &m); err != nil { + t.Fatalf("Failed to unmarshal: %v", err) + } + if m["isExperimentalMode"] != true { + t.Errorf("Expected isExperimentalMode to be true, got %v", m["isExperimentalMode"]) + } + }) + + t.Run("resume omits enableExperimentalMode when unset", func(t *testing.T) { + req := resumeSessionRequest{SessionID: "s1"} + data, _ := json.Marshal(req) + var m map[string]any + json.Unmarshal(data, &m) + if _, ok := m["isExperimentalMode"]; ok { + t.Error("Expected isExperimentalMode to be omitted when not set") + } + }) +} + func TestResumeSessionRequest_RequestMCPApps(t *testing.T) { t.Run("sends requestMcpApps flag when EnableMCPApps is set", func(t *testing.T) { req := resumeSessionRequest{ @@ -2363,6 +2722,68 @@ func TestResumeSessionRequest_RequestMCPApps(t *testing.T) { }) } +func TestSessionRequests_GitHubMCPToolConfig(t *testing.T) { + config := &GitHubMCPToolConfig{ + EnableAllTools: Bool(true), + AdditionalToolsets: []string{"repos"}, + AdditionalTools: []string{"get_issue"}, + EnableInsidersMode: Bool(true), + DisableFormDeferral: Bool(true), + } + expected := map[string]any{ + "enableAllTools": true, + "additionalToolsets": []any{"repos"}, + "additionalTools": []any{"get_issue"}, + "enableInsidersMode": true, + "disableFormDeferral": true, + } + + t.Run("create", func(t *testing.T) { + data, err := json.Marshal(createSessionRequest{GitHubMCPToolConfig: config}) + if err != nil { + t.Fatalf("Failed to marshal: %v", err) + } + var payload map[string]any + if err := json.Unmarshal(data, &payload); err != nil { + t.Fatalf("Failed to unmarshal: %v", err) + } + if !reflect.DeepEqual(payload["githubMcpToolConfig"], expected) { + t.Fatalf("Unexpected githubMcpToolConfig: %#v", payload["githubMcpToolConfig"]) + } + }) + + t.Run("resume", func(t *testing.T) { + data, err := json.Marshal(resumeSessionRequest{ + SessionID: "s1", + GitHubMCPToolConfig: config, + }) + if err != nil { + t.Fatalf("Failed to marshal: %v", err) + } + var payload map[string]any + if err := json.Unmarshal(data, &payload); err != nil { + t.Fatalf("Failed to unmarshal: %v", err) + } + if !reflect.DeepEqual(payload["githubMcpToolConfig"], expected) { + t.Fatalf("Unexpected githubMcpToolConfig: %#v", payload["githubMcpToolConfig"]) + } + }) + + t.Run("unset is omitted", func(t *testing.T) { + data, err := json.Marshal(createSessionRequest{}) + if err != nil { + t.Fatalf("Failed to marshal: %v", err) + } + var payload map[string]any + if err := json.Unmarshal(data, &payload); err != nil { + t.Fatalf("Failed to unmarshal: %v", err) + } + if _, ok := payload["githubMcpToolConfig"]; ok { + t.Fatal("Expected githubMcpToolConfig to be omitted") + } + }) +} + func TestResumeSessionRequest_ModeCallbackFlags(t *testing.T) { req := resumeSessionRequest{ SessionID: "s1", @@ -3305,3 +3726,199 @@ func TestResumeSessionRequest_ExpAssignments(t *testing.T) { } }) } + +func TestIsTerminal(t *testing.T) { + t.Run("IsTerminal is serialized in tool definition", func(t *testing.T) { + tool := Tool{ + Name: "clear_context", + Description: "Clear the conversation", + IsTerminal: true, + Handler: func(_ ToolInvocation) (ToolResult, error) { return ToolResult{}, nil }, + } + data, err := json.Marshal(tool) + if err != nil { + t.Fatalf("Failed to marshal: %v", err) + } + var m map[string]any + if err := json.Unmarshal(data, &m); err != nil { + t.Fatalf("Failed to unmarshal: %v", err) + } + if m["isTerminal"] != true { + t.Errorf("Expected isTerminal to be true, got %v", m["isTerminal"]) + } + }) + + t.Run("IsTerminal is omitted when false", func(t *testing.T) { + tool := Tool{Name: "plain", Description: "A plain tool"} + data, err := json.Marshal(tool) + if err != nil { + t.Fatalf("Failed to marshal: %v", err) + } + var m map[string]any + if err := json.Unmarshal(data, &m); err != nil { + t.Fatalf("Failed to unmarshal: %v", err) + } + if _, ok := m["isTerminal"]; ok { + t.Error("Expected isTerminal to be omitted when false") + } + }) +} + +func TestSessionRequests_ManagedSettings(t *testing.T) { + settings := &ManagedSettings{ + Permissions: &ManagedSettingsPermissions{ + DisableBypassPermissionsMode: DisableBypassPermissionsModeDisable, + Deny: []string{"Shell(git push)"}, + Ask: []string{"Domain(publish.example)"}, + Allow: []string{"Read(**)"}, + }, + } + + expectedPermissions := map[string]any{ + "disableBypassPermissionsMode": "disable", + "deny": []any{"Shell(git push)"}, + "ask": []any{"Domain(publish.example)"}, + "allow": []any{"Read(**)"}, + } + + t.Run("direct injection enables managed safeguards", func(t *testing.T) { + if !hasManagedSettings(nil, settings) { + t.Fatal("expected injected managed settings to enable managed safeguards") + } + if hasManagedSettings(nil, nil) { + t.Fatal("expected an ordinary session to remain unmanaged") + } + }) + + t.Run("includes managedSettings on create when set", func(t *testing.T) { + req := createSessionRequest{EnableManagedSettings: Bool(true), ManagedSettings: settings} + data, err := json.Marshal(req) + if err != nil { + t.Fatalf("Failed to marshal: %v", err) + } + var m map[string]any + if err := json.Unmarshal(data, &m); err != nil { + t.Fatalf("Failed to unmarshal: %v", err) + } + if m["enableManagedSettings"] != true { + t.Errorf("Expected enableManagedSettings true, got %v", m["enableManagedSettings"]) + } + ms, ok := m["managedSettings"].(map[string]any) + if !ok { + t.Fatalf("Expected managedSettings object, got %v", m["managedSettings"]) + } + perms, ok := ms["permissions"].(map[string]any) + if !ok { + t.Fatalf("Expected permissions object, got %v", ms["permissions"]) + } + if !reflect.DeepEqual(perms, expectedPermissions) { + t.Errorf("permissions mismatch:\n got: %#v\nwant: %#v", perms, expectedPermissions) + } + }) + + t.Run("includes managedSettings on resume when set", func(t *testing.T) { + req := resumeSessionRequest{SessionID: "s1", ManagedSettings: settings} + data, err := json.Marshal(req) + if err != nil { + t.Fatalf("Failed to marshal: %v", err) + } + var m map[string]any + if err := json.Unmarshal(data, &m); err != nil { + t.Fatalf("Failed to unmarshal: %v", err) + } + if _, ok := m["managedSettings"].(map[string]any); !ok { + t.Fatalf("Expected managedSettings object, got %v", m["managedSettings"]) + } + }) + + t.Run("omits managedSettings when nil", func(t *testing.T) { + req := createSessionRequest{} + data, _ := json.Marshal(req) + var m map[string]any + json.Unmarshal(data, &m) + if _, ok := m["managedSettings"]; ok { + t.Error("Expected managedSettings to be omitted when nil") + } + }) + + t.Run("preserves explicit empty permission arrays", func(t *testing.T) { + // A non-nil empty allow list is restrictive: it admits no operations. + // Preserve field presence while still omitting nil slices. + req := createSessionRequest{ManagedSettings: &ManagedSettings{ + Permissions: &ManagedSettingsPermissions{ + DisableBypassPermissionsMode: DisableBypassPermissionsModeDisable, + Deny: []string{}, + Ask: []string{}, + Allow: []string{}, + }, + }} + data, err := json.Marshal(req) + if err != nil { + t.Fatalf("Failed to marshal: %v", err) + } + var m map[string]any + json.Unmarshal(data, &m) + perms := m["managedSettings"].(map[string]any)["permissions"].(map[string]any) + if perms["disableBypassPermissionsMode"] != "disable" { + t.Errorf("Expected disableBypassPermissionsMode preserved, got %v", perms["disableBypassPermissionsMode"]) + } + for _, key := range []string{"deny", "ask", "allow"} { + if value, ok := perms[key].([]any); !ok || len(value) != 0 { + t.Errorf("Expected %s to be an explicit empty array, got %v", key, perms[key]) + } + } + }) + + t.Run("distinguishes explicit empty allow from an absent allow", func(t *testing.T) { + // Security-critical: a present empty allow list admits nothing, while an + // absent allow list imposes no allow restriction. The wire output must + // tell these apart per-field, so an explicit empty slice serializes as + // `[]` while a nil slice is omitted entirely. + req := createSessionRequest{ManagedSettings: &ManagedSettings{ + Permissions: &ManagedSettingsPermissions{ + Allow: []string{}, // present but empty: admit nothing + // Deny and Ask left nil: no such restriction supplied. + }, + }} + data, err := json.Marshal(req) + if err != nil { + t.Fatalf("Failed to marshal: %v", err) + } + var m map[string]any + json.Unmarshal(data, &m) + perms := m["managedSettings"].(map[string]any)["permissions"].(map[string]any) + + allow, ok := perms["allow"].([]any) + if !ok || len(allow) != 0 { + t.Errorf("Expected allow to be an explicit empty array, got %v", perms["allow"]) + } + if _, present := perms["deny"]; present { + t.Errorf("Expected deny to be omitted when nil, got %v", perms["deny"]) + } + if _, present := perms["ask"]; present { + t.Errorf("Expected ask to be omitted when nil, got %v", perms["ask"]) + } + }) + + t.Run("distinguishes explicit empty arrays on resume", func(t *testing.T) { + req := resumeSessionRequest{SessionID: "s1", ManagedSettings: &ManagedSettings{ + Permissions: &ManagedSettingsPermissions{ + Deny: []string{}, + Ask: []string{}, + Allow: []string{}, + }, + }} + data, err := json.Marshal(req) + if err != nil { + t.Fatalf("Failed to marshal: %v", err) + } + var m map[string]any + json.Unmarshal(data, &m) + perms := m["managedSettings"].(map[string]any)["permissions"].(map[string]any) + for _, key := range []string{"deny", "ask", "allow"} { + if value, ok := perms[key].([]any); !ok || len(value) != 0 { + t.Errorf("Expected %s to be an explicit empty array on resume, got %v", key, perms[key]) + } + } + }) +} diff --git a/go/internal/e2e/client_options_e2e_test.go b/go/internal/e2e/client_options_e2e_test.go index 1a4879cc5..86332eb6f 100644 --- a/go/internal/e2e/client_options_e2e_test.go +++ b/go/internal/e2e/client_options_e2e_test.go @@ -165,12 +165,12 @@ func TestClientOptionsE2E(t *testing.T) { EnableConfigDiscovery: copilot.Bool(true), EnableOnDemandInstructionDiscovery: copilot.Bool(true), IncludeSubAgentStreamingEvents: copilot.Bool(false), + CustomAgentsLocalOnly: copilot.Bool(false), OnPermissionRequest: copilot.PermissionHandler.ApproveAll, }) if err != nil { t.Fatalf("CreateSession failed: %v", err) } - t.Cleanup(func() { session.Disconnect() }) updated := readCapture(t, capturePath) var createReq *capturedRequest @@ -197,6 +197,107 @@ func TestClientOptionsE2E(t *testing.T) { if v, ok := params["includeSubAgentStreamingEvents"].(bool); !ok || v != false { t.Errorf("Expected session.create.params.includeSubAgentStreamingEvents=false, got %v", params["includeSubAgentStreamingEvents"]) } + if v, ok := params["customAgentsLocalOnly"].(bool); !ok || v != false { + t.Errorf("Expected session.create.params.customAgentsLocalOnly=false, got %v", params["customAgentsLocalOnly"]) + } + + sessionID := session.SessionID + if err := session.Disconnect(); err != nil { + t.Fatalf("Disconnect failed: %v", err) + } + resumed, err := client.ResumeSession(t.Context(), sessionID, &copilot.ResumeSessionConfig{ + CustomAgentsLocalOnly: copilot.Bool(false), + OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + }) + if err != nil { + t.Fatalf("ResumeSession failed: %v", err) + } + t.Cleanup(func() { _ = resumed.Disconnect() }) + + resumedCapture := readCapture(t, capturePath) + for _, req := range resumedCapture.Requests { + if req.Method != "session.resume" { + continue + } + resumeParams, ok := req.Params.(map[string]any) + if !ok { + t.Fatalf("Expected session.resume params to be an object, got %T", req.Params) + } + if v, ok := resumeParams["customAgentsLocalOnly"].(bool); !ok || v != false { + t.Errorf("Expected session.resume.params.customAgentsLocalOnly=false, got %v", + resumeParams["customAgentsLocalOnly"]) + } + return + } + t.Fatalf("session.resume request was not captured. Captured requests: %+v", resumedCapture.Requests) + }) + + t.Run("should send empty-mode custom agent locality defaults in initial requests", func(t *testing.T) { + ctx := testharness.NewTestContext(t) + cliPath := filepath.Join(ctx.WorkDir, "fake-cli-empty-"+randomHex(t)+".js") + capturePath := filepath.Join(ctx.WorkDir, "fake-cli-empty-capture-"+randomHex(t)+".json") + if err := os.WriteFile(cliPath, []byte(fakeStdioCliScript), 0644); err != nil { + t.Fatalf("Failed to write fake CLI script: %v", err) + } + + client := ctx.NewClient(func(opts *copilot.ClientOptions) { + opts.Connection = copilot.StdioConnection{ + Path: cliPath, + Args: []string{"--capture-file", capturePath}, + } + opts.Mode = copilot.ModeEmpty + opts.BaseDirectory = ctx.WorkDir + opts.UseLoggedInUser = copilot.Bool(false) + }) + t.Cleanup(func() { client.ForceStop() }) + + session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ + AvailableTools: []string{"builtin:ask_user"}, + OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + }) + if err != nil { + t.Fatalf("CreateSession failed: %v", err) + } + sessionID := session.SessionID + if err := session.Disconnect(); err != nil { + t.Fatalf("Disconnect failed: %v", err) + } + + resumed, err := client.ResumeSession(t.Context(), sessionID, &copilot.ResumeSessionConfig{ + AvailableTools: []string{"builtin:ask_user"}, + OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + }) + if err != nil { + t.Fatalf("ResumeSession failed: %v", err) + } + t.Cleanup(func() { _ = resumed.Disconnect() }) + + capture := readCapture(t, capturePath) + foundCreate := false + foundResume := false + for _, req := range capture.Requests { + params, ok := req.Params.(map[string]any) + if !ok { + continue + } + switch req.Method { + case "session.create": + foundCreate = true + if v, ok := params["customAgentsLocalOnly"].(bool); !ok || !v { + t.Errorf("Expected session.create.params.customAgentsLocalOnly=true, got %v", + params["customAgentsLocalOnly"]) + } + case "session.resume": + foundResume = true + if v, ok := params["customAgentsLocalOnly"].(bool); !ok || !v { + t.Errorf("Expected session.resume.params.customAgentsLocalOnly=true, got %v", + params["customAgentsLocalOnly"]) + } + } + } + if !foundCreate || !foundResume { + t.Fatalf("Expected create and resume requests, got %+v", capture.Requests) + } }) t.Run("should forward advanced session creation options to the CLI", func(t *testing.T) { @@ -763,7 +864,7 @@ function handleMessage(message) { writeResponse(message.id, { message: "pong", protocolVersion: 3, timestamp: Date.now() }); return; } - if (message.method === "session.create") { + if (message.method === "session.create" || message.method === "session.resume") { const sessionId = (message.params && message.params.sessionId) || "fake-session"; writeResponse(message.id, { sessionId, workspacePath: null, capabilities: null }); return; diff --git a/go/internal/e2e/commands_and_elicitation_e2e_test.go b/go/internal/e2e/commands_and_elicitation_e2e_test.go index 8d2d40f2f..af7520a4c 100644 --- a/go/internal/e2e/commands_and_elicitation_e2e_test.go +++ b/go/internal/e2e/commands_and_elicitation_e2e_test.go @@ -53,7 +53,7 @@ func TestCommandsE2E(t *testing.T) { var clientCommands *rpc.CommandList waitForRPCCondition(t, 30*time.Second, "client commands to be listed", func() (bool, error) { var err error - clientCommands, err = session.RPC.Commands.List(t.Context(), &rpc.CommandsListRequest{ + clientCommands, err = session.RPC.Commands.List(t.Context(), &rpc.SessionCommandsListRequest{ IncludeBuiltins: rpcPtr(false), IncludeClientCommands: rpcPtr(true), IncludeSkills: rpcPtr(false), @@ -68,7 +68,7 @@ func TestCommandsE2E(t *testing.T) { t.Fatalf("Expected client-command-only list to exclude builtins, got %+v", clientCommands.Commands) } - builtinCommands, err := session.RPC.Commands.List(t.Context(), &rpc.CommandsListRequest{ + builtinCommands, err := session.RPC.Commands.List(t.Context(), &rpc.SessionCommandsListRequest{ IncludeBuiltins: rpcPtr(true), IncludeClientCommands: rpcPtr(false), IncludeSkills: rpcPtr(false), @@ -93,7 +93,7 @@ func TestCommandsE2E(t *testing.T) { } defer session.Disconnect() - builtinCommands, err := session.RPC.Commands.List(t.Context(), &rpc.CommandsListRequest{ + builtinCommands, err := session.RPC.Commands.List(t.Context(), &rpc.SessionCommandsListRequest{ IncludeBuiltins: rpcPtr(true), IncludeClientCommands: rpcPtr(false), IncludeSkills: rpcPtr(false), @@ -152,7 +152,7 @@ func TestCommandsE2E(t *testing.T) { defer session.Disconnect() waitForRPCCondition(t, 30*time.Second, "registered deploy command", func() (bool, error) { - commands, err := session.RPC.Commands.List(t.Context(), &rpc.CommandsListRequest{ + commands, err := session.RPC.Commands.List(t.Context(), &rpc.SessionCommandsListRequest{ IncludeBuiltins: rpcPtr(false), IncludeClientCommands: rpcPtr(true), IncludeSkills: rpcPtr(false), diff --git a/go/internal/e2e/hooks_extended_e2e_test.go b/go/internal/e2e/hooks_extended_e2e_test.go index f53dd13f6..5cbba3856 100644 --- a/go/internal/e2e/hooks_extended_e2e_test.go +++ b/go/internal/e2e/hooks_extended_e2e_test.go @@ -14,8 +14,9 @@ import ( // Mirrors dotnet/test/HookLifecycleAndOutputTests.cs (snapshot category "hooks_extended"). // // Covers each handler exposed on copilot.SessionHooks: OnPreToolUse, -// OnPostToolUse, OnPostToolUseFailure, OnUserPromptSubmitted, OnSessionStart, -// OnSessionEnd, OnErrorOccurred. Output-shape behavior (modifiedPrompt / +// OnPostToolUse, OnPostToolUseFailure, OnUserPromptSubmitted, +// OnUserPromptTransformed, OnSessionStart, OnSessionEnd, OnErrorOccurred, +// OnAgentStop. Output-shape behavior (modifiedPrompt / modifiedTransformedPrompt / // additionalContext / errorHandling / modifiedArgs / modifiedResult / // sessionSummary) is asserted alongside hook invocation. If a new handler is // added to SessionHooks, add a corresponding test here. @@ -72,6 +73,59 @@ func TestHooksExtendedE2E(t *testing.T) { } }) + t.Run("should invoke userPromptTransformed hook and modify transformed prompt", func(t *testing.T) { + ctx.ConfigureForTest(t) + + var ( + mu sync.Mutex + inputs []copilot.UserPromptTransformedHookInput + ) + session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ + OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + Hooks: &copilot.SessionHooks{ + OnUserPromptTransformed: func(input copilot.UserPromptTransformedHookInput, invocation copilot.HookInvocation) (*copilot.UserPromptTransformedHookOutput, error) { + mu.Lock() + inputs = append(inputs, input) + mu.Unlock() + if invocation.SessionID == "" { + t.Error("Expected non-empty session ID in invocation") + } + return &copilot.UserPromptTransformedHookOutput{ + ModifiedTransformedPrompt: copilot.String("Reply with exactly: HOOKED_TRANSFORMED_PROMPT"), + }, nil + }, + }, + }) + if err != nil { + t.Fatalf("Failed to create session: %v", err) + } + + response, err := session.SendAndWait(t.Context(), copilot.MessageOptions{Prompt: "Answer the request above."}) + if err != nil { + t.Fatalf("Failed to send message: %v", err) + } + + mu.Lock() + defer mu.Unlock() + if len(inputs) == 0 { + t.Fatal("Expected at least one userPromptTransformed hook invocation") + } + if !strings.Contains(inputs[0].Prompt, "Answer the request above.") { + t.Errorf("Expected original prompt in hook input, got %q", inputs[0].Prompt) + } + if !strings.Contains(inputs[0].TransformedPrompt, "Answer the request above.") || + !strings.Contains(inputs[0].TransformedPrompt, "") { + t.Errorf("Expected runtime-transformed prompt in hook input, got %q", inputs[0].TransformedPrompt) + } + if !inputs[0].Timestamp.After(time.UnixMilli(0)) || inputs[0].WorkingDirectory == "" { + t.Error("Expected timestamp and working directory in hook input") + } + assistantMessage, ok := response.Data.(*copilot.AssistantMessageData) + if !ok || !strings.Contains(assistantMessage.Content, "HOOKED_TRANSFORMED_PROMPT") { + t.Errorf("Expected transformed prompt response, got %v", response.Data) + } + }) + t.Run("should invoke sessionStart hook", func(t *testing.T) { ctx.ConfigureForTest(t) @@ -215,6 +269,66 @@ func TestHooksExtendedE2E(t *testing.T) { } }) + t.Run("should invoke agentStop hook and apply block response", func(t *testing.T) { + ctx.ConfigureForTest(t) + + var ( + mu sync.Mutex + inputs []copilot.AgentStopHookInput + ) + + session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ + OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + Hooks: &copilot.SessionHooks{ + OnAgentStop: func(input copilot.AgentStopHookInput, invocation copilot.HookInvocation) (*copilot.AgentStopHookOutput, error) { + mu.Lock() + inputs = append(inputs, input) + callCount := len(inputs) + mu.Unlock() + if invocation.SessionID == "" { + t.Error("Expected non-empty session ID in invocation") + } + if callCount == 1 { + return &copilot.AgentStopHookOutput{ + Decision: "block", + Reason: "Reply with exactly: AGENT_STOP_CONTINUED", + }, nil + } + return nil, nil + }, + }, + }) + if err != nil { + t.Fatalf("Failed to create session: %v", err) + } + + response, err := session.SendAndWait(t.Context(), copilot.MessageOptions{ + Prompt: "Reply with exactly: AGENT_STOP_INITIAL", + }) + if err != nil { + t.Fatalf("Failed to send message: %v", err) + } + + mu.Lock() + defer mu.Unlock() + if len(inputs) != 2 { + t.Fatalf("Expected two agentStop hook invocations, got %+v", inputs) + } + if inputs[0].StopHookActive { + t.Error("Expected first agentStop invocation to not be a continuation") + } + if !inputs[1].StopHookActive { + t.Error("Expected second agentStop invocation to be a continuation") + } + if inputs[0].StopReason != "end_turn" || inputs[0].TranscriptPath == "" { + t.Errorf("Unexpected first agentStop input: %+v", inputs[0]) + } + assistantMessage, ok := response.Data.(*copilot.AssistantMessageData) + if !ok || !strings.Contains(assistantMessage.Content, "AGENT_STOP_CONTINUED") { + t.Errorf("Expected final response to contain AGENT_STOP_CONTINUED, got %v", response.Data) + } + }) + t.Run("should allow preToolUse to return modifiedArgs and suppressOutput", func(t *testing.T) { ctx.ConfigureForTest(t) diff --git a/go/internal/e2e/rewind_e2e_test.go b/go/internal/e2e/rewind_e2e_test.go new file mode 100644 index 000000000..b15e546eb --- /dev/null +++ b/go/internal/e2e/rewind_e2e_test.go @@ -0,0 +1,152 @@ +package e2e + +import ( + "os" + "path/filepath" + "runtime" + "strings" + "testing" + "time" + + copilot "github.com/github/copilot-sdk/go" + "github.com/github/copilot-sdk/go/internal/e2e/testharness" + "github.com/github/copilot-sdk/go/rpc" +) + +const ( + rewindFileName = "rewind-sdk.txt" + rewindFileContent = "SDK rewind content" +) + +func TestRewindE2E(t *testing.T) { + ctx := testharness.NewTestContext(t) + client := ctx.NewClient() + t.Cleanup(func() { client.ForceStop() }) + + t.Run("should restore tracked file and conversation", func(t *testing.T) { + ctx.ConfigureForTest(t) + filePath := filepath.Join(ctx.WorkDir, rewindFileName) + session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ + Model: "claude-sonnet-4.5", + EnableFileChangeTracking: copilot.Bool(true), + OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + }) + if err != nil { + t.Fatalf("CreateSession failed: %v", err) + } + defer session.Disconnect() + + response, err := session.SendAndWait(t.Context(), copilot.MessageOptions{ + Prompt: "Use the create tool to create " + rewindFileName + " containing exactly " + + rewindFileContent + ". After the tool succeeds, reply with exactly SDK_REWIND_DONE.", + }) + if err != nil { + t.Fatalf("SendAndWait failed: %v", err) + } + responseData, ok := response.Data.(*copilot.AssistantMessageData) + if !ok || responseData.Content != "SDK_REWIND_DONE" { + t.Fatalf("Expected SDK_REWIND_DONE response, got %+v", response) + } + content, err := os.ReadFile(filePath) + if err != nil { + t.Fatalf("Failed to read created file: %v", err) + } + if string(content) != rewindFileContent { + t.Fatalf("Expected file content %q, got %q", rewindFileContent, content) + } + + rewindPoints := waitForRewindPoints(t, session) + if !rewindPoints.FileChangeTrackingEnabled { + t.Fatal("Expected file change tracking to be enabled") + } + if len(rewindPoints.Points) != 1 { + t.Fatalf("Expected one rewind point, got %+v", rewindPoints.Points) + } + rewindPoint := rewindPoints.Points[0] + if !rewindPoint.CanRestoreFiles || rewindPoint.FileCount != 1 { + t.Fatalf("Expected one restorable file, got %+v", rewindPoint) + } + + preview, err := session.RPC.History.PreviewRewind(t.Context(), &rpc.HistoryPreviewRewindRequest{ + EventID: rewindPoint.EventID, + }) + if err != nil { + t.Fatalf("PreviewRewind failed: %v", err) + } + if !preview.Available || len(preview.Files) != 1 { + t.Fatalf("Expected one available preview file, got %+v", preview) + } + assertSameRewindPath(t, filePath, preview.Files[0].Path) + + rewind, err := session.RPC.History.Rewind(t.Context(), &rpc.HistoryRewindRequest{ + EventID: rewindPoint.EventID, + Mode: rpc.HistoryRewindModeConversationAndFiles, + }) + if err != nil { + t.Fatalf("Rewind failed: %v", err) + } + if rewind.Outcome != rpc.HistoryRewindOutcomeSuccess { + t.Fatalf("Expected successful rewind, got %+v", rewind) + } + if rewind.EventsRemoved == nil || *rewind.EventsRemoved < 1 { + t.Fatalf("Expected rewind to remove events, got %+v", rewind) + } + if len(rewind.RestoredFiles) != 1 { + t.Fatalf("Expected one restored file, got %+v", rewind.RestoredFiles) + } + assertSameRewindPath(t, filePath, rewind.RestoredFiles[0]) + if _, err := os.Stat(filePath); !os.IsNotExist(err) { + t.Fatalf("Expected rewound file to be removed, stat error: %v", err) + } + + events, err := session.GetEvents(t.Context()) + if err != nil { + t.Fatalf("GetEvents failed: %v", err) + } + for _, event := range events { + if event.ID == rewindPoint.EventID { + t.Fatalf("Expected rewound event %q to be removed", rewindPoint.EventID) + } + } + }) +} + +func waitForRewindPoints(t *testing.T, session *copilot.Session) *rpc.HistoryListRewindPointsResult { + t.Helper() + deadline := time.Now().Add(10 * time.Second) + for { + result, err := session.RPC.History.ListRewindPoints(t.Context()) + if err != nil { + t.Fatalf("ListRewindPoints failed: %v", err) + } + if result.UnavailableReason == nil { + return result + } + if time.Now().After(deadline) { + t.Fatalf("Timed out waiting for rewind points: %s", *result.UnavailableReason) + } + time.Sleep(100 * time.Millisecond) + } +} + +func assertSameRewindPath(t *testing.T, expected, actual string) { + t.Helper() + expectedPath, err := filepath.Abs(expected) + if err != nil { + t.Fatalf("Failed to resolve expected path: %v", err) + } + actualPath, err := filepath.Abs(actual) + if err != nil { + t.Fatalf("Failed to resolve actual path: %v", err) + } + + expectedPath = filepath.Clean(expectedPath) + actualPath = filepath.Clean(actualPath) + if runtime.GOOS == "windows" { + if !strings.EqualFold(expectedPath, actualPath) { + t.Fatalf("Expected path %q, got %q", expectedPath, actualPath) + } + } else if expectedPath != actualPath { + t.Fatalf("Expected path %q, got %q", expectedPath, actualPath) + } +} diff --git a/go/internal/e2e/rpc_session_state_e2e_test.go b/go/internal/e2e/rpc_session_state_e2e_test.go index 00c2e9ef6..4046ab97f 100644 --- a/go/internal/e2e/rpc_session_state_e2e_test.go +++ b/go/internal/e2e/rpc_session_state_e2e_test.go @@ -1083,7 +1083,7 @@ func TestRPCSessionStateE2E(t *testing.T) { t.Errorf("Expected SetApproveAll(true) to succeed, got %+v", approve) } - reset, err := session.RPC.Permissions.ResetSessionApprovals(t.Context()) + reset, err := session.RPC.Permissions.ResetSessionApprovals(t.Context(), &rpc.PermissionsResetSessionApprovalsRequest{}) if err != nil { t.Fatalf("Failed to call ResetSessionApprovals: %v", err) } diff --git a/go/internal/e2e/rpc_shell_and_fleet_e2e_test.go b/go/internal/e2e/rpc_shell_and_fleet_e2e_test.go index d7ffd725d..81b8471da 100644 --- a/go/internal/e2e/rpc_shell_and_fleet_e2e_test.go +++ b/go/internal/e2e/rpc_shell_and_fleet_e2e_test.go @@ -55,6 +55,7 @@ func TestRPCShellAndFleetE2E(t *testing.T) { if err != nil { t.Fatalf("Failed to create session: %v", err) } + t.Cleanup(func() { _ = session.Disconnect() }) var command string if runtime.GOOS == "windows" { diff --git a/go/internal/e2e/session_config_e2e_test.go b/go/internal/e2e/session_config_e2e_test.go index 672a905dc..2ce48e3b3 100644 --- a/go/internal/e2e/session_config_e2e_test.go +++ b/go/internal/e2e/session_config_e2e_test.go @@ -5,10 +5,12 @@ import ( "encoding/base64" "encoding/json" "fmt" + "net/http" "os" "path/filepath" "strings" "testing" + "time" copilot "github.com/github/copilot-sdk/go" "github.com/github/copilot-sdk/go/internal/e2e/testharness" @@ -988,6 +990,79 @@ func TestSessionConfigExtrasE2E(t *testing.T) { t.Errorf("Expected toolNames=[view], got %v", toolNames) } }) + + t.Run("should apply GitHub MCP tool config on create", func(t *testing.T) { + ctx.ConfigureForTest(t) + enableAllTools := true + enableInsidersMode := true + disableFormDeferral := true + + session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ + OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + EnableConfigDiscovery: copilot.Bool(true), + EnableMCPApps: true, + GitHubMCPToolConfig: &copilot.GitHubMCPToolConfig{ + EnableAllTools: &enableAllTools, + AdditionalToolsets: []string{"actions"}, + AdditionalTools: []string{"get_me"}, + EnableInsidersMode: &enableInsidersMode, + DisableFormDeferral: &disableFormDeferral, + }, + }) + if err != nil { + t.Fatalf("CreateSession failed: %v", err) + } + t.Cleanup(func() { _ = session.Disconnect() }) + + assertGitHubMCPConfigApplied(t, ctx, session) + }) +} + +func assertGitHubMCPConfigApplied(t *testing.T, ctx *testharness.TestContext, session *copilot.Session) { + t.Helper() + if _, err := session.RPC.MCP.List(t.Context()); err != nil { + t.Fatalf("MCP.List failed: %v", err) + } + deadline := time.Now().Add(60 * time.Second) + var lastRequests []testharness.CapturedRequest + for time.Now().Before(deadline) { + requests, err := ctx.GetRequests() + if err == nil { + lastRequests = requests + var writableRequest *testharness.CapturedRequest + hasReadonlyRequest := false + for i := range requests { + request := &requests[i] + if request.URL == "/mcp/readonly" { + hasReadonlyRequest = true + } + if request.Method == http.MethodPost && request.URL == "/mcp" { + writableRequest = request + } + } + if writableRequest != nil { + if hasReadonlyRequest { + t.Fatalf("Expected writable GitHub MCP endpoint, got requests: %+v", requests) + } + assertCapturedHeader(t, writableRequest.Headers, "x-mcp-toolsets", "all") + assertCapturedHeader(t, writableRequest.Headers, "x-mcp-insiders", "true") + return + } + } + time.Sleep(200 * time.Millisecond) + } + t.Fatalf("Timed out waiting for configured GitHub MCP request; captured: %+v", lastRequests) +} + +func assertCapturedHeader(t *testing.T, headers map[string]json.RawMessage, name, expected string) { + t.Helper() + var actual string + if err := json.Unmarshal(headers[name], &actual); err != nil { + t.Fatalf("Failed to decode %s header: %v", name, err) + } + if actual != expected { + t.Fatalf("Expected %s=%q, got %q", name, expected, actual) + } } // createProxyProvider returns a ProviderConfig that points at the test proxy and diff --git a/go/internal/e2e/session_e2e_test.go b/go/internal/e2e/session_e2e_test.go index 13ed75750..440a30348 100644 --- a/go/internal/e2e/session_e2e_test.go +++ b/go/internal/e2e/session_e2e_test.go @@ -1047,7 +1047,12 @@ func getSystemMessage(exchange testharness.ParsedHttpExchange) string { } func TestSetModelWithReasoningEffortE2E(t *testing.T) { + t.Run("should set model with reasoningeffort", runSetModelWithReasoningEffortE2E) +} + +func runSetModelWithReasoningEffortE2E(t *testing.T) { ctx := testharness.NewTestContext(t) + ctx.ConfigureForTest(t) client := ctx.NewClient() t.Cleanup(func() { client.ForceStop() }) @@ -1072,15 +1077,15 @@ func TestSetModelWithReasoningEffortE2E(t *testing.T) { } }) - if err := session.SetModel(t.Context(), "gpt-4.1", &copilot.SetModelOptions{ReasoningEffort: copilot.String("high")}); err != nil { + if err := session.SetModel(t.Context(), "gpt-5.4", &copilot.SetModelOptions{ReasoningEffort: copilot.String("high")}); err != nil { t.Fatalf("SetModel returned error: %v", err) } select { case evt := <-modelChanged: md, mdOk := evt.Data.(*copilot.SessionModelChangeData) - if !mdOk || md.NewModel != "gpt-4.1" { - t.Errorf("Expected newModel 'gpt-4.1', got %v", evt.Data) + if !mdOk || md.NewModel != "gpt-5.4" { + t.Errorf("Expected newModel 'gpt-5.4', got %v", evt.Data) } if !mdOk || md.ReasoningEffort == nil || *md.ReasoningEffort != "high" { t.Errorf("Expected reasoningEffort 'high', got %v", evt.Data) diff --git a/go/internal/e2e/session_fs_sqlite_e2e_test.go b/go/internal/e2e/session_fs_sqlite_e2e_test.go index fa3b49e48..20cd77783 100644 --- a/go/internal/e2e/session_fs_sqlite_e2e_test.go +++ b/go/internal/e2e/session_fs_sqlite_e2e_test.go @@ -198,6 +198,26 @@ func (p *inMemorySqliteProvider) Rename(src string, dest string) error { func (p *inMemorySqliteProvider) SqliteQuery(queryType rpc.SessionFSSqliteQueryType, query string, params map[string]any) (*copilot.SessionFSSqliteQueryResult, error) { p.mu.Lock() defer p.mu.Unlock() + return p.runQueryLocked(queryType, query), nil +} + +func (p *inMemorySqliteProvider) SqliteTransaction(statements []rpc.SessionFSSqliteTransactionStatement) ([]copilot.SessionFSSqliteQueryResult, error) { + p.mu.Lock() + defer p.mu.Unlock() + results := make([]copilot.SessionFSSqliteQueryResult, 0, len(statements)) + for _, statement := range statements { + results = append(results, *p.runQueryLocked(statement.QueryType, statement.Query)) + } + return results, nil +} + +// runQueryLocked returns canned results based on query type. The agent doesn't +// know or care whether a real SQLite database is behind this — it just receives +// SQL tool results. These stubs return plausible responses so the agent can +// proceed normally without pulling in a real SQLite dependency. +// +// Callers must hold p.mu. +func (p *inMemorySqliteProvider) runQueryLocked(queryType rpc.SessionFSSqliteQueryType, query string) *copilot.SessionFSSqliteQueryResult { p.hadQuery = true *p.sqliteCalls = append(*p.sqliteCalls, sqliteCall{ SessionID: p.sessionID, @@ -205,14 +225,10 @@ func (p *inMemorySqliteProvider) SqliteQuery(queryType rpc.SessionFSSqliteQueryT Query: query, }) - // Return canned results based on query type. The agent doesn't know or care - // whether a real SQLite database is behind this — it just receives SQL tool - // results. These stubs return plausible responses so the agent can proceed - // normally without pulling in a real SQLite dependency. upper := strings.ToUpper(strings.TrimSpace(query)) switch queryType { case rpc.SessionFSSqliteQueryTypeExec: - return &copilot.SessionFSSqliteQueryResult{Columns: []string{}, Rows: []map[string]any{}}, nil + return &copilot.SessionFSSqliteQueryResult{Columns: []string{}, Rows: []map[string]any{}} case rpc.SessionFSSqliteQueryTypeRun: lastID := int64(1) return &copilot.SessionFSSqliteQueryResult{ @@ -220,17 +236,34 @@ func (p *inMemorySqliteProvider) SqliteQuery(queryType rpc.SessionFSSqliteQueryT Rows: []map[string]any{}, RowsAffected: 1, LastInsertRowid: &lastID, - }, nil + } case rpc.SessionFSSqliteQueryTypeQuery: - if strings.Contains(upper, "SELECT") { + // Only the "items" table the test asks the agent to create is modelled + // here. The runtime also reads its own bookkeeping tables (for example + // inbox_entries) through this provider and deserializes those rows into + // typed structs, so returning the canned item row for every SELECT would + // make the runtime reject rows it cannot parse. + if strings.Contains(upper, "SELECT") && readsTable(upper, "ITEMS") { return &copilot.SessionFSSqliteQueryResult{ Columns: []string{"id", "name"}, Rows: []map[string]any{{"id": "a1", "name": "Widget"}}, - }, nil + } + } + return &copilot.SessionFSSqliteQueryResult{Columns: []string{}, Rows: []map[string]any{}} + } + return &copilot.SessionFSSqliteQueryResult{Columns: []string{}, Rows: []map[string]any{}} +} + +// readsTable reports whether an upper-cased SQL statement selects from the given +// table, tolerating the quoting styles the agent may emit. +func readsTable(upperQuery string, table string) bool { + names := []string{table, `"` + table + `"`, "`" + table + "`", "[" + table + "]", "MAIN." + table} + for _, name := range names { + if strings.Contains(upperQuery, "FROM "+name) { + return true } - return &copilot.SessionFSSqliteQueryResult{Columns: []string{}, Rows: []map[string]any{}}, nil } - return &copilot.SessionFSSqliteQueryResult{Columns: []string{}, Rows: []map[string]any{}}, nil + return false } func (p *inMemorySqliteProvider) SqliteExists() (bool, error) { diff --git a/go/internal/e2e/streaming_fidelity_e2e_test.go b/go/internal/e2e/streaming_fidelity_e2e_test.go index 189b61bf2..7f6d4fba8 100644 --- a/go/internal/e2e/streaming_fidelity_e2e_test.go +++ b/go/internal/e2e/streaming_fidelity_e2e_test.go @@ -285,12 +285,16 @@ func TestStreamingFidelityE2E(t *testing.T) { }) t.Run("should emit streaming deltas with reasoning effort configured", func(t *testing.T) { - ctx.ConfigureForTest(t) + reasoningCtx := testharness.NewTestContext(t) + reasoningCtx.ConfigureForTest(t) + reasoningClient := reasoningCtx.NewClient() + t.Cleanup(func() { reasoningClient.ForceStop() }) // Verifies that setting ReasoningEffort alongside Streaming=true does not break // the streaming pipeline — deltas still arrive and complete successfully. - session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ + session, err := reasoningClient.CreateSession(t.Context(), &copilot.SessionConfig{ OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + Model: "gpt-5.4", Streaming: copilot.Bool(true), ReasoningEffort: "high", }) diff --git a/go/internal/e2e/testharness/context.go b/go/internal/e2e/testharness/context.go index b73153dff..03ebf24cb 100644 --- a/go/internal/e2e/testharness/context.go +++ b/go/internal/e2e/testharness/context.go @@ -312,6 +312,11 @@ func (c *TestContext) GetExchanges() ([]ParsedHttpExchange, error) { return c.proxy.GetExchanges() } +// GetRequests retrieves all captured outbound HTTP requests from the proxy. +func (c *TestContext) GetRequests() ([]CapturedRequest, error) { + return c.proxy.GetRequests() +} + // WaitForExchanges waits until the proxy has captured at least the requested exchanges. func (c *TestContext) WaitForExchanges(t *testing.T, minimumCount int) []ParsedHttpExchange { t.Helper() diff --git a/go/internal/e2e/testharness/proxy.go b/go/internal/e2e/testharness/proxy.go index ec16124bc..2545882bc 100644 --- a/go/internal/e2e/testharness/proxy.go +++ b/go/internal/e2e/testharness/proxy.go @@ -188,6 +188,38 @@ func (p *CapiProxy) GetExchanges() ([]ParsedHttpExchange, error) { return exchanges, nil } +// GetRequests retrieves all captured outbound HTTP requests from the proxy. +func (p *CapiProxy) GetRequests() ([]CapturedRequest, error) { + p.mu.Lock() + url := p.proxyURL + p.mu.Unlock() + + if url == "" { + return nil, fmt.Errorf("proxy not started") + } + + resp, err := http.Get(url + "/requests") + if err != nil { + return nil, fmt.Errorf("failed to get requests: %w", err) + } + defer resp.Body.Close() + + var requests []CapturedRequest + if err := json.NewDecoder(resp.Body).Decode(&requests); err != nil { + return nil, fmt.Errorf("failed to decode requests: %w", err) + } + + return requests, nil +} + +// CapturedRequest represents an outbound HTTP request captured by the proxy. +type CapturedRequest struct { + Method string `json:"method"` + URL string `json:"url"` + Headers map[string]json.RawMessage `json:"headers"` + Body string `json:"body"` +} + // ParsedHttpExchange represents a captured HTTP exchange. type ParsedHttpExchange struct { Request ChatCompletionRequest `json:"request"` diff --git a/go/mode_empty.go b/go/mode_empty.go index 51fc34a4a..6057b2661 100644 --- a/go/mode_empty.go +++ b/go/mode_empty.go @@ -122,6 +122,10 @@ func (c *Client) applyConfigDefaultsForMode(config *SessionConfig) { if c.options.Mode != ModeEmpty { return } + if config.EnableExperimentalMode == nil { + f := false + config.EnableExperimentalMode = &f + } if config.EnableSessionTelemetry == nil { f := false config.EnableSessionTelemetry = &f @@ -160,12 +164,20 @@ func (c *Client) applyConfigDefaultsForMode(config *SessionConfig) { if config.MCPOAuthTokenStorage == "" { config.MCPOAuthTokenStorage = "in-memory" } + if config.CustomAgentsLocalOnly == nil { + localOnly := true + config.CustomAgentsLocalOnly = &localOnly + } } func (c *Client) applyResumeDefaultsForMode(config *ResumeSessionConfig) { if c.options.Mode != ModeEmpty { return } + if config.EnableExperimentalMode == nil { + f := false + config.EnableExperimentalMode = &f + } if config.EnableSessionTelemetry == nil { f := false config.EnableSessionTelemetry = &f @@ -204,6 +216,10 @@ func (c *Client) applyResumeDefaultsForMode(config *ResumeSessionConfig) { if config.MCPOAuthTokenStorage == "" { config.MCPOAuthTokenStorage = "in-memory" } + if config.CustomAgentsLocalOnly == nil { + localOnly := true + config.CustomAgentsLocalOnly = &localOnly + } } // updateSessionOptionsForMode applies the per-mode safe-defaults patch via diff --git a/go/permission_context_test.go b/go/permission_context_test.go new file mode 100644 index 000000000..16c6d2d59 --- /dev/null +++ b/go/permission_context_test.go @@ -0,0 +1,260 @@ +package copilot + +import ( + "encoding/json" + "fmt" + "io" + "testing" + "time" + + "github.com/github/copilot-sdk/go/internal/jsonrpc2" + "github.com/github/copilot-sdk/go/rpc" +) + +// runPermissionExchange drives executePermissionAndRespond with the supplied +// handler and captures the raw JSON-RPC request frame the SDK emits (if any). +// The second return value reports whether a request was sent at all, so tests +// can assert that no-result decisions suppress the response entirely. +func runPermissionExchange(t *testing.T, handler PermissionHandlerFunc) (frame []byte, sent bool) { + t.Helper() + + stdinR, stdinW := io.Pipe() + stdoutR, stdoutW := io.Pipe() + t.Cleanup(func() { + stdinR.Close() + stdinW.Close() + stdoutR.Close() + stdoutW.Close() + }) + + client := jsonrpc2.NewClient(stdinW, stdoutR) + client.Start() + t.Cleanup(client.Stop) + + session := &Session{ + SessionID: "session-1", + client: client, + RPC: rpc.NewSessionRPC(client, "session-1"), + } + + frameCh := make(chan []byte, 1) + go func() { + captured, err := readTestJSONRPCFrame(stdinR) + if err != nil { + return + } + var request struct { + ID json.RawMessage `json:"id"` + } + _ = json.Unmarshal(captured, &request) + // Publish the captured frame before unblocking the RPC round trip so a + // sent response is always observable before executePermissionAndRespond + // returns. + frameCh <- captured + response := map[string]any{ + "jsonrpc": "2.0", + "id": json.RawMessage(request.ID), + "result": map[string]any{"applied": true}, + } + data, _ := json.Marshal(response) + _, _ = fmt.Fprintf(stdoutW, "Content-Length: %d\r\n\r\n%s", len(data), data) + }() + + done := make(chan struct{}) + go func() { + session.executePermissionAndRespond("permission-1", nil, handler) + close(done) + }() + + select { + case captured := <-frameCh: + return captured, true + case <-done: + select { + case captured := <-frameCh: + return captured, true + default: + return nil, false + } + case <-time.After(2 * time.Second): + t.Fatal("timed out waiting for permission response") + return nil, false + } +} + +// paramsOf extracts the top-level params object from a JSON-RPC request frame. +func paramsOf(t *testing.T, frame []byte) map[string]json.RawMessage { + t.Helper() + var request struct { + Method string `json:"method"` + Params map[string]json.RawMessage `json:"params"` + } + if err := json.Unmarshal(frame, &request); err != nil { + t.Fatalf("failed to unmarshal request frame: %v", err) + } + if request.Method != "session.permissions.handlePendingPermissionRequest" { + t.Fatalf("unexpected method %q", request.Method) + } + return request.Params +} + +func sampleDecisionContext() *rpc.PermissionDecisionContext { + return &rpc.PermissionDecisionContext{ + Outcome: PermissionDecisionOutcomeAutoApproved, + Source: PermissionDecisionSourceHostPolicy, + Surface: PermissionDecisionSurfaceSDK, + } +} + +func TestPermissionDecisionContextForwardedAsSiblingOfResult(t *testing.T) { + frame, sent := runPermissionExchange(t, func(PermissionRequest, PermissionInvocation) (rpc.PermissionDecision, error) { + return NewAttributedPermissionResult(&rpc.PermissionDecisionApproveOnce{}, sampleDecisionContext()), nil + }) + if !sent { + t.Fatal("expected a permission response to be sent") + } + + params := paramsOf(t, frame) + + // decisionContext must be a top-level sibling of result. + rawContext, ok := params["decisionContext"] + if !ok { + t.Fatal("expected decisionContext to be present as a top-level sibling of result") + } + var context rpc.PermissionDecisionContext + if err := json.Unmarshal(rawContext, &context); err != nil { + t.Fatalf("failed to unmarshal decisionContext: %v", err) + } + if context.Outcome != PermissionDecisionOutcomeAutoApproved || + context.Source != PermissionDecisionSourceHostPolicy || + context.Surface != PermissionDecisionSurfaceSDK { + t.Fatalf("unexpected decisionContext contents: %#v", context) + } + + // result must exist and must NOT contain a nested decisionContext. + rawResult, ok := params["result"] + if !ok { + t.Fatal("expected result to be present") + } + var result map[string]json.RawMessage + if err := json.Unmarshal(rawResult, &result); err != nil { + t.Fatalf("failed to unmarshal result: %v", err) + } + if _, nested := result["decisionContext"]; nested { + t.Fatal("decisionContext must not be nested inside result") + } +} + +func TestPermissionDecisionContextOmittedWithoutAttribution(t *testing.T) { + frame, sent := runPermissionExchange(t, func(PermissionRequest, PermissionInvocation) (rpc.PermissionDecision, error) { + return &rpc.PermissionDecisionApproveOnce{}, nil + }) + if !sent { + t.Fatal("expected a permission response to be sent") + } + + params := paramsOf(t, frame) + if _, ok := params["decisionContext"]; ok { + t.Fatal("expected decisionContext to be absent when no context is supplied") + } + if _, ok := params["result"]; !ok { + t.Fatal("expected result to be present") + } +} + +func TestAttributedResultReplacesRatherThanNests(t *testing.T) { + first := &rpc.PermissionDecisionContext{ + Outcome: PermissionDecisionOutcomePromptedUser, + Source: PermissionDecisionSourceHumanResponse, + Surface: PermissionDecisionSurfaceTui, + } + second := sampleDecisionContext() + + wrapped := NewAttributedPermissionResult(NewAttributedPermissionResult(&rpc.PermissionDecisionApproveOnce{}, first), second) + + if wrapped.DecisionContext != second { + t.Fatalf("expected the second context to replace the first, got %#v", wrapped.DecisionContext) + } + // The underlying decision must be the plain approve-once, not another wrapper. + if _, ok := wrapped.PermissionDecision.(*rpc.PermissionDecisionApproveOnce); !ok { + t.Fatalf("expected unwrapped decision to be *rpc.PermissionDecisionApproveOnce, got %T", wrapped.PermissionDecision) + } + + frame, sent := runPermissionExchange(t, func(PermissionRequest, PermissionInvocation) (rpc.PermissionDecision, error) { + return wrapped, nil + }) + if !sent { + t.Fatal("expected a permission response to be sent") + } + params := paramsOf(t, frame) + rawContext, ok := params["decisionContext"] + if !ok { + t.Fatal("expected decisionContext to be present") + } + var context rpc.PermissionDecisionContext + if err := json.Unmarshal(rawContext, &context); err != nil { + t.Fatalf("failed to unmarshal decisionContext: %v", err) + } + if context.Surface != PermissionDecisionSurfaceSDK { + t.Fatalf("expected replaced surface %q, got %q", PermissionDecisionSurfaceSDK, context.Surface) + } +} + +func TestAttributedNoResultStillSuppressesResponse(t *testing.T) { + frame, sent := runPermissionExchange(t, func(PermissionRequest, PermissionInvocation) (rpc.PermissionDecision, error) { + return NewAttributedPermissionResult(&rpc.PermissionDecisionNoResult{}, sampleDecisionContext()), nil + }) + if sent { + t.Fatalf("expected no response to be sent for an attributed no-result decision, got frame: %s", frame) + } +} + +// A handler may dereference the wrapper and return it by value. The embedded +// interface promotes its methods to the value type, so the value form also +// satisfies rpc.PermissionDecision and must be unwrapped identically to the +// pointer form -- otherwise the wrapper itself is sent as result and the +// context is silently dropped. +func TestValueFormAttributedResultIsUnwrapped(t *testing.T) { + frame, sent := runPermissionExchange(t, func(PermissionRequest, PermissionInvocation) (rpc.PermissionDecision, error) { + return *NewAttributedPermissionResult(&rpc.PermissionDecisionApproveOnce{}, sampleDecisionContext()), nil + }) + if !sent { + t.Fatal("expected a permission response to be sent") + } + + params := paramsOf(t, frame) + + if _, ok := params["decisionContext"]; !ok { + t.Fatal("expected decisionContext to be forwarded for a value-form attributed result") + } + + var result map[string]json.RawMessage + if err := json.Unmarshal(params["result"], &result); err != nil { + t.Fatalf("failed to unmarshal result: %v", err) + } + if _, nested := result["decisionContext"]; nested { + t.Fatal("decisionContext must not be nested inside result") + } + if _, leaked := result["PermissionDecision"]; leaked { + t.Fatal("the wrapper leaked into result instead of being unwrapped") + } +} + +func TestAttributedResultReplacesContextOnValueForm(t *testing.T) { + first := sampleDecisionContext() + second := &rpc.PermissionDecisionContext{ + Outcome: PermissionDecisionOutcomePromptedUser, + Source: PermissionDecisionSourceHumanResponse, + Surface: PermissionDecisionSurfaceTui, + } + + valueForm := *NewAttributedPermissionResult(&rpc.PermissionDecisionApproveOnce{}, first) + replaced := NewAttributedPermissionResult(valueForm, second) + + if replaced.DecisionContext != second { + t.Fatal("expected the second context to replace the first") + } + if _, nested := replaced.PermissionDecision.(AttributedPermissionResult); nested { + t.Fatal("value-form attribution must be replaced, not nested") + } +} diff --git a/go/permissions.go b/go/permissions.go index f86a72683..f27f9b6e6 100644 --- a/go/permissions.go +++ b/go/permissions.go @@ -1,15 +1,79 @@ package copilot import ( + "errors" + "github.com/github/copilot-sdk/go/rpc" ) +// AttributedPermissionResult pairs a permission decision with the context +// describing how it was reached, so the runtime can attribute auto-approval +// telemetry to the responding surface. +// +// The embedded [rpc.PermissionDecision] carries the actual decision, while +// DecisionContext is informational only and never changes permission behavior. +// It satisfies [rpc.PermissionDecision] itself, so a [PermissionHandlerFunc] +// can return it wherever a plain decision is expected. Prefer constructing it +// through [NewAttributedPermissionResult] rather than by hand. +// +// Experimental: AttributedPermissionResult is part of an experimental API and +// may change or be removed. +type AttributedPermissionResult struct { + rpc.PermissionDecision + // DecisionContext describes how and where the decision was reached. When nil + // the SDK omits it from the wire, preserving legacy behavior. + DecisionContext *rpc.PermissionDecisionContext +} + +// NewAttributedPermissionResult pairs a permission decision with the context +// describing how it was reached, so the runtime can attribute auto-approval +// telemetry to the responding surface. +// +// The returned value satisfies [rpc.PermissionDecision], so a +// [PermissionHandlerFunc] can return it directly. Passing an already-attributed +// result replaces the previous context rather than nesting it. If result is a +// [rpc.PermissionDecisionNoResult] (attributed or not), the SDK still +// suppresses the response. +// +// Experimental: NewAttributedPermissionResult is part of an experimental API +// and may change or be removed. +func NewAttributedPermissionResult(result rpc.PermissionDecision, decisionContext *rpc.PermissionDecisionContext) *AttributedPermissionResult { + decision, _ := splitAttribution(result) + return &AttributedPermissionResult{ + PermissionDecision: decision, + DecisionContext: decisionContext, + } +} + +// splitAttribution separates an optionally attributed result into the bare +// decision and its context, returning a nil context when there is none. +// +// Both the pointer and value forms are matched: embedding an interface promotes +// its methods to the value type too, so an AttributedPermissionResult passed by +// value also satisfies [rpc.PermissionDecision] and must not slip through +// unwrapped. +func splitAttribution(result rpc.PermissionDecision) (rpc.PermissionDecision, *rpc.PermissionDecisionContext) { + switch attributed := result.(type) { + case *AttributedPermissionResult: + return attributed.PermissionDecision, attributed.DecisionContext + case AttributedPermissionResult: + return attributed.PermissionDecision, attributed.DecisionContext + } + return result, nil +} + // PermissionHandler provides pre-built OnPermissionRequest implementations. var PermissionHandler = struct { - // ApproveAll approves all permission requests. + // ApproveAll approves permission requests when managed settings are disabled. ApproveAll PermissionHandlerFunc }{ - ApproveAll: func(_ PermissionRequest, _ PermissionInvocation) (rpc.PermissionDecision, error) { + ApproveAll: func(request PermissionRequest, invocation PermissionInvocation) (rpc.PermissionDecision, error) { + if invocation.ManagedSettingsEnabled { + return nil, errors.New("approveAll cannot be used when managed settings are enabled") + } + if request.RequiresManagedApproval() { + return &rpc.PermissionDecisionNoResult{}, nil + } return &rpc.PermissionDecisionApproveOnce{}, nil }, } diff --git a/go/permissions_test.go b/go/permissions_test.go new file mode 100644 index 000000000..517450dbf --- /dev/null +++ b/go/permissions_test.go @@ -0,0 +1,82 @@ +package copilot_test + +import ( + "encoding/json" + "testing" + + copilot "github.com/github/copilot-sdk/go" + "github.com/github/copilot-sdk/go/rpc" +) + +func TestPermissionEventExposesManagedApprovalRequired(t *testing.T) { + var data copilot.PermissionRequestedData + err := json.Unmarshal([]byte(`{ + "permissionRequest": { + "kind": "read", + "intention": "Read managed content", + "path": "/workspace/file.txt", + "managedApprovalRequired": true + }, + "requestId": "permission-1" + }`), &data) + if err != nil { + t.Fatal(err) + } + + if !data.PermissionRequest.RequiresManagedApproval() { + t.Fatal("expected managed approval to be required") + } +} + +func TestApproveAllApprovesOrdinaryRequest(t *testing.T) { + decision, err := copilot.PermissionHandler.ApproveAll( + &copilot.PermissionRequestRead{}, + copilot.PermissionInvocation{SessionID: "session-1"}, + ) + if err != nil { + t.Fatal(err) + } + if _, ok := decision.(*rpc.PermissionDecisionApproveOnce); !ok { + t.Fatalf("expected PermissionDecisionApproveOnce, got %T", decision) + } +} + +func TestApproveAllRejectsManagedSettingsSession(t *testing.T) { + decision, err := copilot.PermissionHandler.ApproveAll( + &copilot.PermissionRequestRead{}, + copilot.PermissionInvocation{ + SessionID: "session-1", + ManagedSettingsEnabled: true, + }, + ) + if err == nil { + t.Fatal("expected managed settings error") + } + if decision != nil { + t.Fatalf("expected no decision, got %T", decision) + } +} + +func TestApproveAllLeavesManagedRequestPending(t *testing.T) { + decision, err := copilot.PermissionHandler.ApproveAll( + &copilot.PermissionRequestRead{ManagedApprovalRequired: ptrTo(true)}, + copilot.PermissionInvocation{SessionID: "session-1"}, + ) + if err != nil { + t.Fatal(err) + } + if _, ok := decision.(*rpc.PermissionDecisionNoResult); !ok { + t.Fatalf("expected PermissionDecisionNoResult, got %T", decision) + } +} + +func TestRawPermissionRequestWithMalformedJSONRequiresManagedApproval(t *testing.T) { + request := rpc.RawPermissionRequest{Raw: json.RawMessage(`{"managedApprovalRequired":`)} + if !request.RequiresManagedApproval() { + t.Fatal("expected malformed raw request to fail closed") + } +} + +func ptrTo[T any](value T) *T { + return &value +} diff --git a/go/rpc/permission_request_managed_approval.go b/go/rpc/permission_request_managed_approval.go new file mode 100644 index 000000000..020626893 --- /dev/null +++ b/go/rpc/permission_request_managed_approval.go @@ -0,0 +1,87 @@ +// Copyright (c) GitHub. All rights reserved. + +package rpc + +import "encoding/json" + +func managedApprovalRequired(value *bool) bool { + return value != nil && *value +} + +// RequiresManagedApproval reports whether managed policy requires an explicit +// human decision for this request. +func (r PermissionRequestCustomTool) RequiresManagedApproval() bool { + return managedApprovalRequired(r.ManagedApprovalRequired) +} + +// RequiresManagedApproval reports whether managed policy requires an explicit +// human decision for this request. +func (r PermissionRequestExtensionManagement) RequiresManagedApproval() bool { + return managedApprovalRequired(r.ManagedApprovalRequired) +} + +// RequiresManagedApproval reports whether managed policy requires an explicit +// human decision for this request. +func (r PermissionRequestExtensionPermissionAccess) RequiresManagedApproval() bool { + return managedApprovalRequired(r.ManagedApprovalRequired) +} + +// RequiresManagedApproval reports whether managed policy requires an explicit +// human decision for this request. +func (r PermissionRequestFactory) RequiresManagedApproval() bool { + return managedApprovalRequired(r.ManagedApprovalRequired) +} + +// RequiresManagedApproval reports whether managed policy requires an explicit +// human decision for this request. +func (r PermissionRequestHook) RequiresManagedApproval() bool { + return managedApprovalRequired(r.ManagedApprovalRequired) +} + +// RequiresManagedApproval reports whether managed policy requires an explicit +// human decision for this request. +func (r PermissionRequestMCP) RequiresManagedApproval() bool { + return managedApprovalRequired(r.ManagedApprovalRequired) +} + +// RequiresManagedApproval reports whether managed policy requires an explicit +// human decision for this request. +func (r PermissionRequestMemory) RequiresManagedApproval() bool { + return managedApprovalRequired(r.ManagedApprovalRequired) +} + +// RequiresManagedApproval reports whether managed policy requires an explicit +// human decision for this request. +func (r PermissionRequestRead) RequiresManagedApproval() bool { + return managedApprovalRequired(r.ManagedApprovalRequired) +} + +// RequiresManagedApproval reports whether managed policy requires an explicit +// human decision for this request. +func (r PermissionRequestShell) RequiresManagedApproval() bool { + return managedApprovalRequired(r.ManagedApprovalRequired) +} + +// RequiresManagedApproval reports whether managed policy requires an explicit +// human decision for this request. +func (r PermissionRequestURL) RequiresManagedApproval() bool { + return managedApprovalRequired(r.ManagedApprovalRequired) +} + +// RequiresManagedApproval reports whether managed policy requires an explicit +// human decision for this request. +func (r PermissionRequestWrite) RequiresManagedApproval() bool { + return managedApprovalRequired(r.ManagedApprovalRequired) +} + +// RequiresManagedApproval reports whether an unknown request carries managed +// approval metadata. +func (r RawPermissionRequest) RequiresManagedApproval() bool { + var metadata struct { + ManagedApprovalRequired *bool `json:"managedApprovalRequired"` + } + if json.Unmarshal(r.Raw, &metadata) != nil { + return true + } + return managedApprovalRequired(metadata.ManagedApprovalRequired) +} diff --git a/go/rpc/zrpc.go b/go/rpc/zrpc.go index 634efbca2..622c7a0cb 100644 --- a/go/rpc/zrpc.go +++ b/go/rpc/zrpc.go @@ -162,7 +162,7 @@ type AgentGetCurrentResult struct { Agent *AgentInfo `json:"agent,omitempty"` } -// Custom agent metadata, including identifiers, display details, source, tools, model, MCP +// Agent metadata, including identifiers, display details, source, tools, model, MCP // servers, skills, and file path. // Experimental: AgentInfo is part of an experimental API and may change or be removed. type AgentInfo struct { @@ -178,13 +178,17 @@ type AgentInfo struct { // shape mirrors the MCP `mcpServers` schema. // Experimental: MCPServers is part of an experimental API and may change or be removed. MCPServers map[string]any `json:"mcpServers,omitzero"` - // Preferred model id for this agent. When omitted, inherits the outer agent's model. + // Authored preferred model id for this agent. Runtime model selection may choose a + // different model; omitted means no authored preference. Model *string `json:"model,omitempty"` - // Unique identifier of the custom agent + // Name of the agent. Use `id` as the stable selection identifier. Name string `json:"name"` // Absolute local file path of the agent definition. Only set for file-based agents loaded // from disk; remote agents do not have a path. Path *string `json:"path,omitempty"` + // Authored base prompt for the agent. Runtime prompt assembly may add dynamic context at + // invocation time. Omitted from `session.agent.list` unless `includePrompt` is true. + Prompt *string `json:"prompt,omitempty"` // Skill names preloaded into this agent's context. Omitted means none. Skills []string `json:"skills,omitzero"` // Where the agent definition was loaded from @@ -196,13 +200,25 @@ type AgentInfo struct { UserInvocable *bool `json:"userInvocable,omitempty"` } -// Custom agents available to the session. +// Agents available to the session. // Experimental: AgentList is part of an experimental API and may change or be removed. type AgentList struct { - // Available custom agents + // Available agents Agents []AgentInfo `json:"agents"` } +type AgentListRequest struct { + // When true, request the session's configured built-in agents alongside custom agents. + // Listing applies feature, context, inclusion, exclusion, and user-disabled-agent policy, + // but does not evaluate transient invocation requirements such as model availability. + // Built-in metadata may be omitted when the session cannot project it, such as a relay + // session. + IncludeBuiltInAgents *bool `json:"includeBuiltInAgents,omitempty"` + // When true, request authored base prompt text on each AgentInfo. Prompt text may be + // omitted when unavailable, such as for agents projected through a relay session. + IncludePrompt *bool `json:"includePrompt,omitempty"` +} + // Full registry entry for the spawned child. Lets the controller call // `handleLiveTargetSelected(entry)` directly without re-reading the registry (avoids a // TOCTOU window). @@ -417,6 +433,16 @@ type AgentSelectResult struct { Agent AgentInfo `json:"agent"` } +// An in-memory authored prompt override for an available agent. +// Experimental: AgentSetPromptRequest is part of an experimental API and may change or be +// removed. +type AgentSetPromptRequest struct { + // Stable effective agent id. Plugin namespace separators are normalized. + ID string `json:"id"` + // Replacement authored prompt. Empty text is valid. + Prompt string `json:"prompt"` +} + // Optional project paths to include when enumerating agent discovery directories. // Experimental: AgentsGetDiscoveryPathsRequest is part of an experimental API and may // change or be removed. @@ -999,6 +1025,25 @@ func (UserAuthInfo) Type() AuthInfoType { return AuthInfoTypeUser } +// The running runtime's complete catalog of well-known built-in model IDs, including +// supported models and additional IDs with built-in metadata. +// Experimental: BuiltInModelCatalog is part of an experimental API and may change or be +// removed. +type BuiltInModelCatalog struct { + // Built-in model entries. + Models []BuiltInModelCatalogEntry `json:"models"` +} + +// A well-known model in the runtime's built-in catalog. +// Experimental: BuiltInModelCatalogEntry is part of an experimental API and may change or +// be removed. +type BuiltInModelCatalogEntry struct { + // Well-known runtime model ID suitable for `ProviderConfig.modelId` or + // `ProviderModelConfig.modelId`. This is not necessarily the provider-facing deployment or + // model name and does not indicate CAPI entitlement or provider availability. + ID string `json:"id"` +} + // Cancellation result for a user-requested shell command. // Experimental: CancelUserRequestedShellCommandResult is part of an experimental API and // may change or be removed. @@ -1230,7 +1275,6 @@ type CommandsInvokeRequest struct { Name string `json:"name"` } -// Optional filters controlling which command sources to include in the listing. // Experimental: CommandsListRequest is part of an experimental API and may change or be // removed. type CommandsListRequest struct { @@ -1363,11 +1407,14 @@ type ConnectRemoteSessionParams struct { type ConnectRequest struct { // Opt this connection in to GitHub telemetry forwarding for its lifetime. When set, the // runtime forwards every internal telemetry event it emits — across all sessions, plus - // sessionless events — to this connection over the `gitHubTelemetry.event` notification, in - // addition to the runtime's normal GitHub/CTS emission (dual-write). Intended for - // first-party hosts that re-emit the events into their own telemetry stores. Both - // unrestricted and restricted events are forwarded, each tagged with a `restricted` - // discriminator; a backstop drops restricted events when restricted telemetry is disabled. + // sessionless events — to this connection over the `gitHubTelemetry.event` notification. + // Regular events are also written to the runtime's normal GitHub/CTS path (dual-write); + // host-only compatibility events are forward-only and intentionally skip that path. + // Intended for first-party hosts that re-emit the events into their own telemetry stores. + // Both unrestricted and restricted events are forwarded, each tagged with a `restricted` + // discriminator; a backstop drops restricted events when restricted telemetry is disabled — + // using the process-global gate for ordinary events and an explicit session-scoped decision + // for host-only events. EnableGitHubTelemetryForwarding *bool `json:"enableGitHubTelemetryForwarding,omitempty"` // Connection token; required when the server was started with COPILOT_CONNECTION_TOKEN Token *string `json:"token,omitempty"` @@ -1385,6 +1432,38 @@ type ConnectResult struct { Version string `json:"version"` } +// Local file system absolute paths within the session working directory to check against +// its content-exclusion policy. +// Experimental: ContentExclusionCheckPathsRequest is part of an experimental API and may +// change or be removed. +type ContentExclusionCheckPathsRequest struct { + // Local file system absolute paths within the session working directory to check. Results + // are returned in the same order, including duplicates. + Paths []string `json:"paths"` +} + +// Batch content-exclusion result. Callers must fail closed when policy evaluation is +// unavailable. +// Experimental: ContentExclusionCheckPathsResult is part of an experimental API and may +// change or be removed. +type ContentExclusionCheckPathsResult struct { + // Whether the session's policy service was available for the complete batch. When false, + // checks is empty and callers must treat every requested path as excluded. + Available bool `json:"available"` + // Per-path decisions in request order. Empty when available is false. + Checks []ContentExclusionPathCheck `json:"checks"` +} + +// Content-exclusion decision for one requested path. +// Experimental: ContentExclusionPathCheck is part of an experimental API and may change or +// be removed. +type ContentExclusionPathCheck struct { + // Whether the session's complete content-exclusion policy excludes the path. + Excluded bool `json:"excluded"` + // The path supplied by the caller. + Path string `json:"path"` +} + // A single large message currently in context. // Experimental: ContextHeaviestMessage is part of an experimental API and may change or be // removed. @@ -1469,6 +1548,7 @@ type CopilotUserResponse struct { // or be removed. type CopilotUserResponseEndpoints struct { API *string `json:"api,omitempty"` + Exp *string `json:"exp,omitempty"` OriginTracker *string `json:"origin-tracker,omitempty"` Proxy *string `json:"proxy,omitempty"` Telemetry *string `json:"telemetry,omitempty"` @@ -1790,6 +1870,59 @@ type DiscoveredCanvas struct { InputSchema any `json:"inputSchema,omitempty"` } +// Discovered extension metadata and persistent enablement state. +// Experimental: DiscoveredExtension is part of an experimental API and may change or be +// removed. +type DiscoveredExtension struct { + // Whether this extension's persistent per-ID preference is enabled + Enabled bool `json:"enabled"` + // Source-qualified ID accepted by both server and session extension enablement methods + ID string `json:"id"` + // Human-readable extension name + Name string `json:"name"` + // Absolute path to the extension entry module, suitable for revealing it in a file manager + Path string `json:"path"` + // Containing plugin metadata for plugin-contributed extensions + Plugin *DiscoveredExtensionPlugin `json:"plugin,omitempty"` + // Discovery source + Source DiscoveredExtensionSource `json:"source"` +} + +// Installed plugin that contributes a discovered extension. +// Experimental: DiscoveredExtensionPlugin is part of an experimental API and may change or +// be removed. +type DiscoveredExtensionPlugin struct { + // Installed plugin name + Name string `json:"name"` +} + +// Extensions discovered from persisted Copilot home state and their effective loading mode. +// Launch-scoped additional plugins are not included. +// Experimental: DiscoveredExtensions is part of an experimental API and may change or be +// removed. +type DiscoveredExtensions struct { + // Discovered user and enabled installed-plugin extensions from persisted Copilot home state + Extensions []DiscoveredExtension `json:"extensions"` + // Effective extension loading mode. Defaults to load_and_augment when unset. + Mode DiscoveredExtensionMode `json:"mode"` +} + +// Source-qualified extension identifiers to persistently disable for future sessions. +// Experimental: DiscoveredExtensionsDisableRequest is part of an experimental API and may +// change or be removed. +type DiscoveredExtensionsDisableRequest struct { + // Source-qualified user or plugin extension IDs to disable + IDs []string `json:"ids"` +} + +// Source-qualified extension identifiers to persistently enable for future sessions. +// Experimental: DiscoveredExtensionsEnableRequest is part of an experimental API and may +// change or be removed. +type DiscoveredExtensionsEnableRequest struct { + // Source-qualified user or plugin extension IDs to enable + IDs []string `json:"ids"` +} + // MCP server discovered by `mcp.discover`, with config source, optional plugin source, // transport type, and enabled state. // Experimental: DiscoveredMCPServer is part of an experimental API and may change or be @@ -1831,6 +1964,11 @@ type EnqueueCommandResult struct { // Experimental: EventLogReadRequest is part of an experimental API and may change or be // removed. type EventLogReadRequest struct { + // Optional non-empty list of subagent identifiers. When provided, only events owned by one + // of these agents are returned; ownership recognizes the event envelope's agentId plus + // legacy data.agentId and data.parentToolCallId markers. This filter takes precedence over + // agentScope. + AgentIDs []string `json:"agentIds,omitzero"` // Agent-scope filter: 'primary' returns only main-agent events plus events whose type // starts with 'subagent.' (matching the typed-subscription default behavior); 'all' returns // events from all agents (matching wildcard-subscription behavior). Default is 'all' to @@ -1839,6 +1977,24 @@ type EventLogReadRequest struct { // Opaque cursor returned by a previous read. Omit on the first call to start from the // beginning of the session's persisted history. Cursor *string `json:"cursor,omitempty"` + // Direction to page through the session's persisted event history. 'forward' (default) + // pages from the cursor toward newer events (or from the start of history when no cursor is + // given). 'backward' enables tail-first reads: with no cursor it returns the NEWEST `max` + // events, and the returned cursor pages toward OLDER events on subsequent backward reads. + // Events within a returned batch are always in chronological (oldest-to-newest) order, even + // for a backward read. Backward reads cover PERSISTED history only; ephemeral events are + // never returned by a backward read. `direction` selects the INITIAL read only: the + // returned cursor is self-describing, so a continuation read pages in the cursor's own + // direction regardless of the `direction` passed alongside it — a forward cursor always + // pages forward and a backward cursor always pages backward. Pass the direction that + // matches the cursor to avoid confusion. + Direction *EventsReadDirection `json:"direction,omitempty"` + // When false, skip ephemeral events entirely and return only durable (persisted) events. + // History-backfill callers that discard ephemerals anyway should set this so the read is + // bounded by the durable log length instead of racing the ephemeral ring on a busy session. + // Defaults to true (ephemerals are interleaved with durable events in creation order). + // Ignored by backward reads, which always cover persisted history only. + IncludeEphemeral *bool `json:"includeEphemeral,omitempty"` // Maximum number of events to return in this batch (1–1000, default 200). Max *int64 `json:"max,omitempty"` // Either '*' to receive all event types, or a non-empty list of event types to receive @@ -1847,7 +2003,10 @@ type EventLogReadRequest struct { // (default) returns immediately even if no events are available. Capped at 30000ms. // Ephemeral events that arrive during the wait are delivered in this batch but are NOT // replayable on a subsequent read (use a non-zero waitMs in your next call to capture - // future ephemerals as they happen). + // future ephemerals as they happen). This applies to forward reads only: a backward read + // always returns immediately and ignores `waitMs`, because backward paging covers persisted + // history only while new events append at the tail (the opposite end from a backward page), + // so no blocking or ephemeral delivery can occur. WaitMs *int32 `json:"waitMs,omitempty"` } @@ -1885,20 +2044,31 @@ type EventLogTypes struct { // removed. type EventsReadResult struct { // Opaque cursor for the next read. Pass back unchanged in the next read.cursor to continue - // from where this read left off. Always present, even when no events were returned. + // from where this read left off. Always present, even when no events were returned. For a + // backward read this cursor pages toward OLDER events; keep passing `direction: backward` + // with it (the cursor is also self-describing, so backward paging continues correctly). Cursor string `json:"cursor"` // Cursor status: 'ok' means the cursor was applied successfully; 'expired' means the cursor // referred to an event that no longer exists in history (e.g. truncated or compacted away) - // and the read started from the beginning of the remaining history. + // and the read fell back to a boundary of the remaining history. For a forward read the + // fallback starts from the beginning of the remaining history; for a backward read it falls + // back to the tail (the newest window). Because the fallback page is a fresh boundary + // snapshot rather than a continuation of the requested cursor, it may overlap events the + // consumer has already rendered — a backward fallback to the tail in particular can repeat + // the newest window. On 'expired', consumers should reset or rebase their local pagination + // state (or deduplicate by event id) before continuing from the returned cursor rather than + // blindly appending/prepending the fallback page. CursorStatus EventsCursorStatus `json:"cursorStatus"` - // Events are delivered in two batches per read: persisted events first (in append order), - // then ephemeral events (in seq order). When `waitMs > 0` and the catch-up batches were - // empty, post-wait events follow the same two-batch ordering. Persisted and ephemeral - // events do not interleave within a single read. + // Session events for this batch, merged into a single stream in creation order: durable + // (persisted) events and ephemeral events interleave exactly as they were emitted. Set + // `includeEphemeral: false` to receive only durable events. Ephemeral events are never + // replayable once pruned from the in-memory ring, so a consumer that needs them should keep + // reading with a non-zero `waitMs`. For a backward (tail-first) read, the returned window + // contains persisted events only, still in chronological (oldest-to-newest) append order. Events []SessionEvent `json:"events"` - // True when the read returned `max` events and more events are available immediately. When - // false, the next read with a non-zero `waitMs` will block until a new event arrives or the - // wait expires. + // True when more events are available in the read's direction. For a forward read, true + // means the batch returned `max` events and more are available immediately. For a backward + // read, true means older persisted events remain before the returned window. HasMore bool `json:"hasMore"` } @@ -1939,6 +2109,44 @@ type Extension struct { Status ExtensionStatus `json:"status"` } +// Opaque integrator-owned process launch profile for one extension entrypoint. +// Experimental: ExtensionLaunchProfile is part of an experimental API and may change or be +// removed. +type ExtensionLaunchProfile struct { + // Opaque integrator-defined arguments passed to the executable. The runtime does not append + // the extension entrypoint. + Args []string `json:"args"` + // Opaque integrator-defined environment variables. Runtime-owned COPILOT_SDK_PATH, + // SESSION_ID, and COPILOT_EXTENSION_PARENT_PID values take precedence. + Env map[string]string `json:"env"` + // Executable used to launch the extension entrypoint. + Executable string `json:"executable"` +} + +// A discovered extension entrypoint that the registered integrator may classify and resolve +// to an opaque launch profile. +// Experimental: ExtensionLaunchProviderResolveRequest is part of an experimental API and +// may change or be removed. +type ExtensionLaunchProviderResolveRequest struct { + // Source-qualified extension identifier. + ID string `json:"id"` + // Absolute path to the discovered extension entrypoint. + ModulePath string `json:"modulePath"` + // Human-readable extension name. + Name string `json:"name"` + // Discovery source for the extension entrypoint. + Source ExtensionSource `json:"source"` +} + +// The launch profile for a supported entrypoint. Omit launch when the provider does not +// support the entrypoint. +// Experimental: ExtensionLaunchProviderResolveResult is part of an experimental API and may +// change or be removed. +type ExtensionLaunchProviderResolveResult struct { + // Opaque launch profile, omitted when this provider does not support the entrypoint. + Launch *ExtensionLaunchProfile `json:"launch,omitempty"` +} + // Extensions discovered for the session, with their current status. // Experimental: ExtensionList is part of an experimental API and may change or be removed. type ExtensionList struct { @@ -1954,6 +2162,11 @@ type ExtensionsDisableRequest struct { ID string `json:"id"` } +// Experimental: ExtensionsDisableResult is part of an experimental API and may change or be +// removed. +type ExtensionsDisableResult struct { +} + // Source-qualified extension identifier to enable for the session. // Experimental: ExtensionsEnableRequest is part of an experimental API and may change or be // removed. @@ -1962,6 +2175,11 @@ type ExtensionsEnableRequest struct { ID string `json:"id"` } +// Experimental: ExtensionsEnableResult is part of an experimental API and may change or be +// removed. +type ExtensionsEnableResult struct { +} + // Tool call result (string or expanded result object) // Experimental: ExternalToolResult is part of an experimental API and may change or be // removed. @@ -2234,10 +2452,16 @@ type FactoryAckResult struct { // Experimental: FactoryAgentOptions is part of an experimental API and may change or be // removed. type FactoryAgentOptions struct { + // Optional custom agent name for the subagent. This field is accepted but not yet honored. + Agent *string `json:"agent,omitempty"` + // Optional context tier for the subagent. This field is accepted but not yet honored. + ContextTier *ContextTier `json:"contextTier,omitempty"` // Optional label distinguishing otherwise identical memoized agent calls. Label *string `json:"label,omitempty"` // Optional model identifier for the subagent. Model *string `json:"model,omitempty"` + // Optional reasoning effort for the subagent. This field is accepted but not yet honored. + ReasoningEffort *string `json:"reasoningEffort,omitempty"` // Optional JSON Schema for structured agent output. Schema any `json:"schema,omitempty"` } @@ -2246,6 +2470,8 @@ type FactoryAgentOptions struct { // Experimental: FactoryAgentRequest is part of an experimental API and may change or be // removed. type FactoryAgentRequest struct { + // Opaque token identifying the current factory execution attempt. + ExecutionToken string `json:"executionToken"` // Factory run identifier that owns the subagent. FactoryRunID string `json:"factoryRunId"` // Subagent execution options. @@ -2262,6 +2488,25 @@ type FactoryAgentResult struct { Result any `json:"result,omitempty"` } +// Prompt-safe durable identity and live status for a direct factory agent. +// Experimental: FactoryAgentSummary is part of an experimental API and may change or be +// removed. +type FactoryAgentSummary struct { + ActiveMs int64 `json:"activeMs"` + Activity *string `json:"activity,omitempty"` + AgentID string `json:"agentId"` + AgentType string `json:"agentType"` + CompletedAt *int64 `json:"completedAt,omitempty"` + Label string `json:"label"` + PhaseID *string `json:"phaseId"` + RequestedModel *string `json:"requestedModel,omitempty"` + ResolvedModel *string `json:"resolvedModel,omitempty"` + RunID string `json:"runId"` + StartedAt *int64 `json:"startedAt,omitempty"` + Status string `json:"status"` + ToolCallID string `json:"toolCallId"` +} + // Parameters for cancelling a factory run. // Experimental: FactoryCancelRequest is part of an experimental API and may change or be // removed. @@ -2270,12 +2515,32 @@ type FactoryCancelRequest struct { RunID string `json:"runId"` } +// Current factory phase identity. +// Experimental: FactoryCurrentPhase is part of an experimental API and may change or be +// removed. +type FactoryCurrentPhase struct { + ID string `json:"id"` + Ordinal *int64 `json:"ordinal"` +} + +// Declared or approved factory resource ceilings. +// Experimental: FactoryDeclaredLimits is part of an experimental API and may change or be +// removed. +type FactoryDeclaredLimits struct { + MaxAiCredits *float64 `json:"maxAiCredits,omitempty"` + MaxConcurrentSubagents *int64 `json:"maxConcurrentSubagents,omitempty"` + MaxTotalSubagents *int64 `json:"maxTotalSubagents,omitempty"` + TimeoutSeconds *float64 `json:"timeoutSeconds,omitempty"` +} + // Parameters sent to the owning extension to execute a factory closure. // Experimental: FactoryExecuteRequest is part of an experimental API and may change or be // removed. type FactoryExecuteRequest struct { // Factory input value. Args any `json:"args"` + // Opaque token identifying this factory execution attempt. + ExecutionToken string `json:"executionToken"` // Registered factory name. Name string `json:"name"` // Factory run identifier. @@ -2289,7 +2554,23 @@ type FactoryExecuteRequest struct { // removed. type FactoryExecuteResult struct { // Factory result value. - Result any `json:"result"` + Result any `json:"result,omitempty"` +} + +// Parameters for paging factory progress. +// Experimental: FactoryGetRunProgressRequest is part of an experimental API and may change +// or be removed. +type FactoryGetRunProgressRequest struct { + // Exclusive forward cursor. + AfterSeq *int64 `json:"afterSeq,omitempty"` + // Exclusive backward cursor. + BeforeSeq *int64 `json:"beforeSeq,omitempty"` + // Maximum records to return. Defaults to 200 and is capped at 500. + Limit *int32 `json:"limit,omitempty"` + // Optional phase identifier used to scope records and cursors. + PhaseID *string `json:"phaseId,omitempty"` + // Factory run identifier. + RunID string `json:"runId"` } // Parameters for retrieving a factory run. @@ -2304,6 +2585,8 @@ type FactoryGetRunRequest struct { // Experimental: FactoryJournalGetRequest is part of an experimental API and may change or // be removed. type FactoryJournalGetRequest struct { + // Opaque token identifying the current factory execution attempt. + ExecutionToken string `json:"executionToken"` // Namespaced journal key. Key string `json:"key"` // Factory run identifier. @@ -2324,6 +2607,8 @@ type FactoryJournalGetResult struct { // Experimental: FactoryJournalPutRequest is part of an experimental API and may change or // be removed. type FactoryJournalPutRequest struct { + // Opaque token identifying the current factory execution attempt. + ExecutionToken string `json:"executionToken"` // Namespaced journal key. Key string `json:"key"` // JSON result to memoize. @@ -2332,6 +2617,33 @@ type FactoryJournalPutRequest struct { RunID string `json:"runId"` } +// Parameters for paging factory runs. +// Experimental: FactoryListRunsRequest is part of an experimental API and may change or be +// removed. +type FactoryListRunsRequest struct { + // Exclusive forward cursor. + AfterSeq *int64 `json:"afterSeq,omitempty"` + // Exclusive backward cursor. + BeforeSeq *int64 `json:"beforeSeq,omitempty"` + // Maximum terminal runs to return. Defaults to 200 and is capped at 500. + Limit *int32 `json:"limit,omitempty"` +} + +// A page of factory runs in durable creation order. +// Experimental: FactoryListRunsResult is part of an experimental API and may change or be +// removed. +type FactoryListRunsResult struct { + // Whether terminal runs newer than this page exist. + HasMoreNewer *bool `json:"hasMoreNewer,omitempty"` + // Newest terminal-run cursor in this page, or null when the terminal window is empty. + NewestSeq *int64 `json:"newestSeq,omitempty"` + // Oldest terminal-run cursor in this page, or null when the terminal window is empty. + OldestSeq *int64 `json:"oldestSeq,omitempty"` + // Number of terminal runs older than this page. + OmittedOlder *int64 `json:"omittedOlder,omitempty"` + Runs []FactoryRunSummary `json:"runs"` +} + // One ordered factory progress line. // Experimental: FactoryLogLine is part of an experimental API and may change or be removed. type FactoryLogLine struct { @@ -2347,12 +2659,121 @@ type FactoryLogLine struct { // Experimental: FactoryLogRequest is part of an experimental API and may change or be // removed. type FactoryLogRequest struct { + // Opaque token identifying the current factory execution attempt. + ExecutionToken string `json:"executionToken"` // Ordered progress lines to append. Lines []FactoryLogLine `json:"lines"` // Factory run identifier. RunID string `json:"runId"` } +// Durable lifecycle and timing for one factory phase. +// Experimental: FactoryPhaseObservation is part of an experimental API and may change or be +// removed. +type FactoryPhaseObservation struct { + AccumulatedActiveMs int64 `json:"accumulatedActiveMs"` + CompletedAt *int64 `json:"completedAt,omitempty"` + CurrentActiveMs int64 `json:"currentActiveMs"` + Detail *string `json:"detail,omitempty"` + EntryCount int64 `json:"entryCount"` + ID string `json:"id"` + LastEnteredRunAttempt int64 `json:"lastEnteredRunAttempt"` + LiveAgentCount int64 `json:"liveAgentCount"` + Ordinal *int64 `json:"ordinal"` + StartedAt *int64 `json:"startedAt,omitempty"` + Status FactoryPhaseStatus `json:"status"` + Title string `json:"title"` + TotalAgentCount int64 `json:"totalAgentCount"` +} + +// One durable factory progress record. +// Experimental: FactoryProgressLine is part of an experimental API and may change or be +// removed. +type FactoryProgressLine struct { + // Resume attempt that emitted this record. + Attempt int64 `json:"attempt"` + // Progress record kind. + Kind FactoryLogLineKind `json:"kind"` + // Phase active when the record was emitted, or null before any phase. + PhaseID *string `json:"phaseId"` + // Epoch milliseconds when the record was persisted. + RecordedAt int64 `json:"recordedAt"` + // Global monotonic sequence number within the run. + Seq int64 `json:"seq"` + // Prompt-safe progress text. + Text string `json:"text"` +} + +// A bidirectional page of factory progress. +// Experimental: FactoryProgressPage is part of an experimental API and may change or be +// removed. +type FactoryProgressPage struct { + HasMoreNewer bool `json:"hasMoreNewer"` + HasMoreOlder bool `json:"hasMoreOlder"` + NewestSeq *int64 `json:"newestSeq"` + OldestSeq *int64 `json:"oldestSeq"` + Records []FactoryProgressLine `json:"records"` + // Run revision reflected by this page. + Revision int64 `json:"revision"` +} + +// Parameters for resuming a factory run from its persisted identity. +// Experimental: FactoryResumeRequest is part of an experimental API and may change or be +// removed. +type FactoryResumeRequest struct { + // Optional per-invocation resource ceiling overrides. + Limits *FactoryRunLimits `json:"limits,omitempty"` + // Factory run identifier. + RunID string `json:"runId"` +} + +// Resolved persisted factory identity and resumed run envelope. +// Experimental: FactoryResumeResult is part of an experimental API and may change or be +// removed. +type FactoryResumeResult struct { + // Persisted factory name resolved for the resumed run. + FactoryName string `json:"factoryName"` + // Terminal resumed run envelope. + Run FactoryRunResult `json:"run"` +} + +// Durable factory resource consumption. +// Experimental: FactoryRunConsumed is part of an experimental API and may change or be +// removed. +type FactoryRunConsumed struct { + ActiveMs int64 `json:"activeMs"` + NanoAiu int64 `json:"nanoAiu"` + Subagents int64 `json:"subagents"` +} + +// Full factory run observability detail. +// Experimental: FactoryRunDetail is part of an experimental API and may change or be +// removed. +type FactoryRunDetail struct { + ActiveSegmentStartedAt *int64 `json:"activeSegmentStartedAt"` + Agents []FactoryAgentSummary `json:"agents"` + Approved *FactoryDeclaredLimits `json:"approved"` + CompletedAt *int64 `json:"completedAt"` + Consumed FactoryRunConsumed `json:"consumed"` + CreatedAt int64 `json:"createdAt"` + CurrentPhase *FactoryCurrentPhase `json:"currentPhase"` + DeclaredLimits FactoryDeclaredLimits `json:"declaredLimits"` + DeclaredPhaseCount int64 `json:"declaredPhaseCount"` + Description string `json:"description"` + FactoryName string `json:"factoryName"` + LiveAgentCount int64 `json:"liveAgentCount"` + ObservedAt int64 `json:"observedAt"` + Phases []FactoryPhaseObservation `json:"phases"` + Progress FactoryProgressPage `json:"progress"` + Revision int64 `json:"revision"` + RunID string `json:"runId"` + StartedAt *int64 `json:"startedAt"` + Status FactoryRunStatus `json:"status"` + Terminal *FactoryRunTerminal `json:"terminal"` + TotalSpawnedAgentCount int64 `json:"totalSpawnedAgentCount"` + UpdatedAt int64 `json:"updatedAt"` +} + // Machine-readable factory run failure. // Experimental: FactoryRunFailure is part of an experimental API and may change or be // removed. @@ -2371,6 +2792,33 @@ func (r RawFactoryRunFailureData) Type() FactoryRunFailureType { return r.Discriminator } +// The run stopped because its usage accounting could not be completed. +type FactoryRunFailureFactoryAccountingIncomplete struct { + // Confirmed usage in nano-AIU, representing the floor of what the run spent. + DrainedNanoAiu int64 `json:"drainedNanoAiu"` + // Factory run identifier. + RunID string `json:"runId"` +} + +func (FactoryRunFailureFactoryAccountingIncomplete) factoryRunFailure() {} +func (FactoryRunFailureFactoryAccountingIncomplete) Type() FactoryRunFailureType { + return FactoryRunFailureTypeFactoryAccountingIncomplete +} + +type FactoryRunFailureFactoryDurableFailure struct { + // Stable failure code. + Code string `json:"code"` + // Execution-critical durable operation that failed. + Operation FactoryDurableOperation `json:"operation"` + // Factory run identifier. + RunID string `json:"runId"` +} + +func (FactoryRunFailureFactoryDurableFailure) factoryRunFailure() {} +func (FactoryRunFailureFactoryDurableFailure) Type() FactoryRunFailureType { + return FactoryRunFailureTypeFactoryDurableFailure +} + type FactoryRunFailureFactoryLimitReached struct { // Resource ceiling that stopped the run. Kind FactoryRunFailureKind `json:"kind"` @@ -2401,12 +2849,17 @@ func (FactoryRunFailureFactoryResumeDeclined) Type() FactoryRunFailureType { // Experimental: FactoryRunLimits is part of an experimental API and may change or be // removed. type FactoryRunLimits struct { + // Maximum AI credits consumed by factory subagents and their descendants. The post-paid + // ceiling is soft: parallel turns can settle beyond it before the run stops. + MaxAiCredits *float64 `json:"maxAiCredits,omitempty"` // Maximum number of factory subagents that may run concurrently. MaxConcurrentSubagents *int64 `json:"maxConcurrentSubagents,omitempty"` // Maximum total number of factory subagents that may be admitted. MaxTotalSubagents *int64 `json:"maxTotalSubagents,omitempty"` - // Factory active-run timeout in milliseconds. - Timeout *float64 `json:"timeout,omitempty"` + // Maximum accumulated active-execution time in seconds. Active execution includes the + // entire extension body, subprocess waits, queued-agent waits, and sleeps; time between + // resumed attempts is not counted. + TimeoutSeconds *float64 `json:"timeoutSeconds,omitempty"` } // Parameters for invoking a registered factory. @@ -2441,6 +2894,41 @@ type FactoryRunResult struct { Status FactoryRunStatus `json:"status"` } +// Durable factory run summary with read-time live overlays. +// Experimental: FactoryRunSummary is part of an experimental API and may change or be +// removed. +type FactoryRunSummary struct { + ActiveSegmentStartedAt *int64 `json:"activeSegmentStartedAt"` + Approved *FactoryDeclaredLimits `json:"approved"` + CompletedAt *int64 `json:"completedAt"` + Consumed FactoryRunConsumed `json:"consumed"` + CreatedAt int64 `json:"createdAt"` + CurrentPhase *FactoryCurrentPhase `json:"currentPhase"` + DeclaredLimits FactoryDeclaredLimits `json:"declaredLimits"` + DeclaredPhaseCount int64 `json:"declaredPhaseCount"` + Description string `json:"description"` + FactoryName string `json:"factoryName"` + LiveAgentCount int64 `json:"liveAgentCount"` + ObservedAt int64 `json:"observedAt"` + Revision int64 `json:"revision"` + RunID string `json:"runId"` + StartedAt *int64 `json:"startedAt"` + Status FactoryRunStatus `json:"status"` + Terminal *FactoryRunTerminal `json:"terminal"` + TotalSpawnedAgentCount int64 `json:"totalSpawnedAgentCount"` + UpdatedAt int64 `json:"updatedAt"` +} + +// Prompt-safe terminal factory outcome. +// Experimental: FactoryRunTerminal is part of an experimental API and may change or be +// removed. +type FactoryRunTerminal struct { + Error *string `json:"error,omitempty"` + Failure FactoryRunFailure `json:"failure,omitempty"` + Reason *string `json:"reason,omitempty"` + ResultPreview *string `json:"resultPreview,omitempty"` +} + // Content filtering mode to apply to all tools, or a map of tool name to content filtering // mode. // Experimental: FilterMapping is part of an experimental API and may change or be removed. @@ -2621,6 +3109,27 @@ type HistoryCancelBackgroundCompactionResult struct { Cancelled bool `json:"cancelled"` } +// Parameters for clearing the conversation and seeding the window that replaces it. +// Experimental: HistoryClearContextRequest is part of an experimental API and may change or +// be removed. +type HistoryClearContextRequest struct { + // First user message of the fresh context window. Required: a cleared window holding only + // system and developer messages is not a conversation a model can answer, so every clear + // seeds the window it creates. Delivered by the enclosing turn driver once the agentic loop + // exits, which is why the call must be made from inside a tool handler. + Prompt string `json:"prompt"` +} + +// What a successful clear removed. A clear that could not be applied rejects instead of +// reporting a count. +// Experimental: HistoryClearContextResult is part of an experimental API and may change or +// be removed. +type HistoryClearContextResult struct { + // Number of non-system, non-developer messages that were removed from the conversation. + // Zero only when the window already held no conversation. + MessagesCleared int64 `json:"messagesCleared"` +} + // Post-compaction context window usage breakdown // Experimental: HistoryCompactContextWindow is part of an experimental API and may change // or be removed. @@ -2639,12 +3148,21 @@ type HistoryCompactContextWindow struct { ToolDefinitionsTokens *int64 `json:"toolDefinitionsTokens,omitempty"` } -// Optional compaction parameters. -// Experimental: HistoryCompactRequest is part of an experimental API and may change or be -// removed. type HistoryCompactRequest struct { // Optional user-provided instructions to focus the compaction summary CustomInstructions *string `json:"customInstructions,omitempty"` + // Context window token limit this compaction is targeting, recorded as the `tokenLimit` on + // the persisted `session.compaction_start` / `session.compaction_complete` events. Set it + // when the compaction targets a window other than the compacting model's own, e.g. + // switching to a model with a smaller context window: the compaction still runs on the + // current model, so the limit that motivated it would otherwise be lost. When absent, the + // events record the compacting model's own resolved limit. Attribution metadata only - it + // does not change how much the compaction removes. + TokenLimit *int64 `json:"tokenLimit,omitempty"` + // What initiated this compaction request, recorded as the `trigger` on the persisted + // `session.compaction_start` / `session.compaction_complete` events. When absent, the + // compaction is persisted without trigger attribution (initiator unknown). + Trigger *HistoryCompactRequestTrigger `json:"trigger,omitempty"` } // Compaction outcome with the number of tokens and messages removed, summary text, and the @@ -2665,6 +3183,141 @@ type HistoryCompactResult struct { TokensRemoved int64 `json:"tokensRemoved"` } +// Rewind points and file-change-tracking availability for the session. +// Experimental: HistoryListRewindPointsResult is part of an experimental API and may change +// or be removed. +type HistoryListRewindPointsResult struct { + // Whether this session captured file changes from its first turn. + FileChangeTrackingEnabled bool `json:"fileChangeTrackingEnabled"` + // Root user turns in chronological order. Empty when `unavailableReason` is set. + Points []HistoryRewindPoint `json:"points"` + // Why the listed points could not be produced, when applicable; the points list is empty + // whenever it is set. `unsupported-remote-session` is permanent for the session and comes + // with `fileChangeTrackingEnabled: false`. `session-busy` is transient and only ever + // reported by a session that *is* tracking (`fileChangeTrackingEnabled: true`), because the + // file-change captures cannot be read while work that may still mutate them is in flight; + // the same request succeeds once the session settles, so a client that wants points should + // retry rather than treat it as a failure. It is never `file-change-tracking-disabled`: an + // untracked local session still lists conversation-only points and reports that through + // `fileChangeTrackingEnabled: false`. + UnavailableReason *HistoryRewindUnavailableReason `json:"unavailableReason,omitempty"` +} + +// Event boundary to preview for conversation-and-files rewind. +// Experimental: HistoryPreviewRewindRequest is part of an experimental API and may change +// or be removed. +type HistoryPreviewRewindRequest struct { + // ID of the user.message event that begins the discarded suffix. + EventID string `json:"eventId"` +} + +// Files and aggregate changes for a prospective rewind. +// Experimental: HistoryPreviewRewindResult is part of an experimental API and may change or +// be removed. +type HistoryPreviewRewindResult struct { + // Whether file restore is available for this session. This is authoritative: switch on it + // and read `reason` only when it is false. + Available bool `json:"available"` + // Number of unique files in the preview. + FileCount int64 `json:"fileCount"` + // Files ordered by path. + Files []HistoryRewindFilePreview `json:"files"` + // Why file restore is unavailable, when applicable. Populated only when `available` is + // false and never set when `available` is true. + Reason *HistoryRewindUnavailableReason `json:"reason,omitempty"` +} + +// A file that a conversation-and-files rewind would restore. +// Experimental: HistoryRewindFilePreview is part of an experimental API and may change or +// be removed. +type HistoryRewindFilePreview struct { + // Aggregate change made across the discarded turns. + ChangeType HistoryRewindChangeType `json:"changeType"` + // Lines added across the discarded turns. + LinesAdded int64 `json:"linesAdded"` + // Lines removed across the discarded turns. + LinesRemoved int64 `json:"linesRemoved"` + // Absolute path of the captured file. + Path string `json:"path"` +} + +// A root user turn that the session can rewind to. +// Experimental: HistoryRewindPoint is part of an experimental API and may change or be +// removed. +type HistoryRewindPoint struct { + // Whether at least one file in this turn or a later turn can be restored. + CanRestoreFiles bool `json:"canRestoreFiles"` + // ID of the user.message event that begins the discarded suffix. + EventID string `json:"eventId"` + // Number of unique files in this turn and all later turns that have captured changes. + FileCount int64 `json:"fileCount"` + // Whether this turn was an automatically injected autopilot continuation. + IsAutopilotContinuation bool `json:"isAutopilotContinuation"` + // Lines added by this turn's captured file changes. + LinesAdded int64 `json:"linesAdded"` + // Lines removed by this turn's captured file changes. + LinesRemoved int64 `json:"linesRemoved"` + // ISO timestamp of the user turn. + Timestamp string `json:"timestamp"` + // Whether this turn itself captured any file changes. + TurnChangedFiles bool `json:"turnChangedFiles"` + // User-visible message text for the turn. + UserMessage string `json:"userMessage"` +} + +// Boundary and mode for rewinding session history. +// Experimental: HistoryRewindRequest is part of an experimental API and may change or be +// removed. +type HistoryRewindRequest struct { + // ID of the user.message event that begins the discarded suffix. + EventID string `json:"eventId"` + // Whether to rewind only conversation history or also restore captured files. + Mode HistoryRewindMode `json:"mode"` +} + +// Structured outcome of a rewind request. +// Experimental: HistoryRewindResult is part of an experimental API and may change or be +// removed. +type HistoryRewindResult struct { + // Failure detail. Set only for the failure and partial-failure outcomes + // (`files-rolled-back`, `rollback-incomplete`, `truncation-failed`, + // `checkpoint-cleanup-failed`, `snapshot-prune-failed`); omitted for `success` and for the + // unavailable outcomes (`session-busy`, `file-change-tracking-disabled`, + // `unsupported-remote-session`). + Error *string `json:"error,omitempty"` + // Number of persisted events removed by conversation truncation. Present only when + // truncation succeeded (outcomes `success`, `checkpoint-cleanup-failed`, and + // `snapshot-prune-failed`); omitted for every unavailable outcome (`session-busy`, + // `file-change-tracking-disabled`, `unsupported-remote-session`) and for + // `truncation-failed`, `files-rolled-back`, and `rollback-incomplete`. + EventsRemoved *int64 `json:"eventsRemoved,omitempty"` + // Overall rewind outcome. This discriminates the result: it governs which of the remaining + // fields are populated, so consumers must switch on it before reading `eventsRemoved`, + // `restoredFiles`, `skippedFiles`, or `error`. See each field for the outcomes that + // populate it. + Outcome HistoryRewindOutcome `json:"outcome"` + // Absolute paths restored to their captured preimages. Always empty for conversation-only + // rewinds and for the unavailable outcomes (`session-busy`, + // `file-change-tracking-disabled`, `unsupported-remote-session`); only + // conversation-and-files outcomes that reached the file-restore stage populate it. + RestoredFiles []string `json:"restoredFiles"` + // Captured files intentionally left unchanged. Always empty for conversation-only rewinds + // and for the unavailable outcomes (`session-busy`, `file-change-tracking-disabled`, + // `unsupported-remote-session`); only conversation-and-files outcomes that reached the + // file-restore stage populate it. + SkippedFiles []HistorySkippedFileRestore `json:"skippedFiles"` +} + +// A captured file that rewind intentionally left unchanged. +// Experimental: HistorySkippedFileRestore is part of an experimental API and may change or +// be removed. +type HistorySkippedFileRestore struct { + // Absolute path of the skipped file. + Path string `json:"path"` + // Reason the file was not restored. + Reason HistoryFileRestoreSkipReason `json:"reason"` +} + // Markdown summary of the conversation context (empty when not available). // Experimental: HistorySummarizeForHandoffResult is part of an experimental API and may // change or be removed. @@ -2686,6 +3339,12 @@ type HistoryTruncateRequest struct { // Experimental: HistoryTruncateResult is part of an experimental API and may change or be // removed. type HistoryTruncateResult struct { + // Failure detail when checkpointCleanupFailed is true. + CheckpointCleanupError *string `json:"checkpointCleanupError,omitempty"` + // True when conversation truncation succeeded but post-truncation workspace checkpoint + // cleanup failed. History is already truncated; callers may still prune snapshots but + // should report a checkpoint-cleanup rather than a truncation failure. + CheckpointCleanupFailed *bool `json:"checkpointCleanupFailed,omitempty"` // Number of events that were removed EventsRemoved int64 `json:"eventsRemoved"` } @@ -2726,6 +3385,12 @@ type InstalledPlugin struct { Name string `json:"name"` // Source for direct repo installs (when marketplace is empty) Source *InstalledPluginSource `json:"source,omitempty"` + // Per-plugin source fingerprint (a SHA-256 hash of the plugin's catalog source spec plus + // its resolved source subtree — NOT a Git commit SHA) captured at marketplace + // install/update time. Auto-update compares it against the freshly recomputed fingerprint + // to detect a content change that does not bump the version. Absent for pre-existing + // installs and for direct (non-marketplace) installs. + SourceSha *string `json:"source_sha,omitempty"` // Version installed (if available) Version *string `json:"version,omitempty"` } @@ -2759,14 +3424,16 @@ type InstalledPluginSource struct { String *string } -// Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref, -// and optional subpath. +// Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref or +// full commit SHA, and optional subpath. // Experimental: InstalledPluginSourceGitHub is part of an experimental API and may change // or be removed. type InstalledPluginSourceGitHub struct { Path *string `json:"path,omitempty"` Ref *string `json:"ref,omitempty"` Repo string `json:"repo"` + // Optional full 40-character hexadecimal commit SHA. + Sha *string `json:"sha,omitempty"` // Constant value. Always "github". Source InstalledPluginSourceGitHubSource `json:"source"` } @@ -2780,13 +3447,15 @@ type InstalledPluginSourceLocal struct { Source InstalledPluginSourceLocalSource `json:"source"` } -// Source descriptor for a direct URL plugin install, with URL, optional ref, and optional -// subpath. +// Source descriptor for a direct URL plugin install, with URL, optional ref or full commit +// SHA, and optional subpath. // Experimental: InstalledPluginSourceURL is part of an experimental API and may change or // be removed. type InstalledPluginSourceURL struct { Path *string `json:"path,omitempty"` Ref *string `json:"ref,omitempty"` + // Optional full 40-character hexadecimal commit SHA. + Sha *string `json:"sha,omitempty"` // Constant value. Always "url". Source InstalledPluginSourceURLSource `json:"source"` URL string `json:"url"` @@ -2873,9 +3542,8 @@ type InstructionSource struct { // Where this source lives — used for UI grouping Location InstructionSourceLocation `json:"location"` // The project path this source was discovered from. Only set by sessionless discovery for - // repository/working-directory sources, where it disambiguates same-named files (e.g. - // .github/copilot-instructions.md) across multiple workspace roots. The session-scoped - // getSources leaves it unset. + // repository, working-directory, and project-scoped plugin sources, where it disambiguates + // sources across multiple workspace roots. The session-scoped getSources leaves it unset. ProjectPath *string `json:"projectPath,omitempty"` // File path relative to repo or absolute for home SourcePath string `json:"sourcePath"` @@ -2883,6 +3551,25 @@ type InstructionSource struct { Type InstructionSourceType `json:"type"` } +// Parameters for interrupting the main agent turn. +// Experimental: InterruptMainTurnRequest is part of an experimental API and may change or +// be removed. +type InterruptMainTurnRequest struct { + // When true, the user's queued prompts are preserved and run as the next turn once the + // interrupted turn unwinds; when false (the default), the queue is cleared like a plain + // abort. + FlushQueued *bool `json:"flushQueued,omitempty"` +} + +// Result of interrupting the main agent turn. +// Experimental: InterruptMainTurnResult is part of an experimental API and may change or be +// removed. +type InterruptMainTurnResult struct { + // Whether an in-flight main agent turn was interrupted. False when the main loop was not + // processing. + Interrupted bool `json:"interrupted"` +} + // HTTP headers as a map from lowercased header name to a list of values. Multi-valued // headers (e.g. Set-Cookie) preserve all values. // Experimental: LlmInferenceHeaders is part of an experimental API and may change or be @@ -3121,6 +3808,17 @@ type LspInitializeRequest struct { WorkingDirectory *string `json:"workingDirectory,omitempty"` } +// Validated device-managed settings discovered before a session exists. +// Experimental: ManagedSettingsReadResult is part of an experimental API and may change or +// be removed. +type ManagedSettingsReadResult struct { + // Discovery or validation error text when managed settings could not be read safely. + ErrorMessage *string `json:"errorMessage,omitempty"` + // Validated, canonical managed-settings JSON. Omitted when no managed settings were + // discovered or when discovered settings failed validation. + SettingsJSON any `json:"settingsJson,omitempty"` +} + // Result of registering a new marketplace. // Experimental: MarketplaceAddResult is part of an experimental API and may change or be // removed. @@ -3571,12 +4269,12 @@ type MCPExecuteSamplingRequest struct { type MCPExecuteSamplingResult struct { } -// MCP server filtered by policy, with name, reason, optional redacted reason, and -// enterprise login. +// MCP server filtered by policy, with name, reason, and optional redacted reason. // Experimental: MCPFilteredServer is part of an experimental API and may change or be // removed. type MCPFilteredServer struct { - // Enterprise login associated with an allowlist policy + // Deprecated. This field is no longer populated. + // Deprecated: EnterpriseName is deprecated. EnterpriseName *string `json:"enterpriseName,omitempty"` // Filtered server name Name string `json:"name"` @@ -3654,7 +4352,7 @@ type MCPHostState struct { DisabledServers []string `json:"disabledServers"` // Map of server name to recorded connection failure. FailedServers map[string]MCPServerFailureInfo `json:"failedServers"` - // Configured servers filtered out by enterprise allowlist policy. + // Configured servers filtered out by MCP server policy. FilteredServers []string `json:"filteredServers"` // Whether third-party MCP servers are policy-enabled for this session. Mcp3pEnabled bool `json:"mcp3pEnabled"` @@ -3696,6 +4394,18 @@ type MCPListToolsResult struct { Tools []MCPTools `json:"tools"` } +// Identifies the MCP server whose persisted OAuth credentials were updated. +// Experimental: MCPOauthAuthenticationStateChangedRequest is part of an experimental API +// and may change or be removed. +type MCPOauthAuthenticationStateChangedRequest struct { + // Whether the target session must mint a session-scoped access token instead of reusing a + // shared access token persisted by another session. + RefreshSessionToken *bool `json:"refreshSessionToken,omitempty"` + // Name of the MCP server whose OAuth credentials were updated. Omit only when the host + // cannot identify the server. + ServerName *string `json:"serverName,omitempty"` +} + // Pending MCP OAuth request ID and host-provided token or cancellation response. // Experimental: MCPOauthHandlePendingRequest is part of an experimental API and may change // or be removed. @@ -3805,6 +4515,23 @@ func (MCPOauthPendingRequestResponseToken) Kind() MCPOauthPendingRequestResponse return MCPOauthPendingRequestResponseKindToken } +// Pending MCP OAuth request id to respond to. +// Experimental: MCPOauthRespondRequest is part of an experimental API and may change or be +// removed. +type MCPOauthRespondRequest struct { + // OAuth request identifier from the mcp.oauth_required event + RequestID string `json:"requestId"` +} + +// Indicates whether the pending MCP OAuth response was accepted. +// Experimental: MCPOauthRespondResult is part of an experimental API and may change or be +// removed. +type MCPOauthRespondResult struct { + // Whether the response was accepted. False if the request was unknown, timed out, or + // already resolved. + Success bool `json:"success"` +} + // Registration parameters for an external MCP client. // Experimental: MCPRegisterExternalClientRequest is part of an experimental API and may // change or be removed. @@ -4049,7 +4776,8 @@ type MCPServer struct { SourcePlugin *string `json:"sourcePlugin,omitempty"` // Plugin version that provided this server, when source is plugin. SourcePluginVersion *string `json:"sourcePluginVersion,omitempty"` - // Connection status: connected, failed, needs-auth, pending, disabled, or not_configured + // Connection status: connected, failed, needs-auth, pending, disabled, stopped, or + // not_configured Status MCPServerStatus `json:"status"` } @@ -4095,6 +4823,9 @@ type MCPServerConfigHTTP struct { // Controls if tools provided by this server can be loaded on demand via tool search (auto) // or always included in the initial tool list (never) DeferTools *MCPServerConfigDeferTools `json:"deferTools,omitempty"` + // Set to true to disable persisted MCP tool snapshots for this server. Live tool discovery + // is unaffected. + DisableToolCache *bool `json:"disableToolCache,omitempty"` // Content filtering mode to apply to all tools, or a map of tool name to content filtering // mode. FilterMapping FilterMapping `json:"filterMapping,omitempty"` @@ -4138,6 +4869,9 @@ type MCPServerConfigStdio struct { // Controls if tools provided by this server can be loaded on demand via tool search (auto) // or always included in the initial tool list (never) DeferTools *MCPServerConfigDeferTools `json:"deferTools,omitempty"` + // Set to true to disable persisted MCP tool snapshots for this server. Live tool discovery + // is unaffected. + DisableToolCache *bool `json:"disableToolCache,omitempty"` // Environment variables to pass to the Stdio MCP server process. Env map[string]string `json:"env,omitzero"` // Content filtering mode to apply to all tools, or a map of tool name to content filtering @@ -4203,12 +4937,14 @@ type MCPSetEnvValueModeResult struct { Mode MCPSetEnvValueModeDetails `json:"mode"` } -// Server name and configuration for an individual MCP server start. +// Server name and optional configuration for an individual MCP server start. Omit `config` +// for a config-free start-by-name of an already-configured server. // Experimental: MCPStartServerRequest is part of an experimental API and may change or be // removed. type MCPStartServerRequest struct { - // MCP server configuration (stdio process or remote HTTP/SSE) - Config MCPServerConfig `json:"config"` + // MCP server configuration (stdio process or remote HTTP/SSE). Omit to start the server + // with its already-registered configuration (config-free start-by-name). + Config MCPServerConfig `json:"config,omitempty"` // Name of the MCP server to start ServerName string `json:"serverName"` } @@ -4443,8 +5179,6 @@ type Model struct { Billing *ModelBilling `json:"billing,omitempty"` // Model capabilities and limits Capabilities ModelCapabilities `json:"capabilities"` - // Default reasoning effort level (only present if model supports reasoning effort) - DefaultReasoningEffort *string `json:"defaultReasoningEffort,omitempty"` // Model identifier (e.g., "claude-sonnet-4.5") ID string `json:"id"` // Model capability category for grouping in the model picker @@ -4469,26 +5203,26 @@ type ModelBilling struct { // Billing cost multiplier relative to the base rate Multiplier *float64 `json:"multiplier,omitempty"` // Active server-driven promotion for this model, if any. Present when the model is being - // promoted with a time-boxed discount. + // promoted with a discount, which may be time-boxed or open-ended. Promo *ModelBillingPromo `json:"promo,omitempty"` // Token-level pricing information for this model TokenPrices *ModelBillingTokenPrices `json:"tokenPrices,omitempty"` } -// Active server-driven promotion for a model, including its discount and expiry. +// Active server-driven promotion for a model, including its discount and optional expiry. // Experimental: ModelBillingPromo is part of an experimental API and may change or be // removed. type ModelBillingPromo struct { // Percentage discount (0-100) applied while the promotion is active. May be fractional. DiscountPercent *float64 `json:"discountPercent,omitempty"` - // UTC ISO 8601 timestamp marking when the promotion ends. Always present: the API only - // surfaces a promo whose expiry parses and is in the future. Consumers should treat a past - // value as expired. - EndsAt string `json:"endsAt"` + // UTC ISO 8601 timestamp marking when the promotion ends. Optional: an open-ended promotion + // omits this field. When present, the API only surfaces a promo whose expiry parses and is + // in the future, so consumers should treat a past value as expired. + EndsAt *string `json:"endsAt,omitempty"` // Stable identifier for the promotion campaign. ID *string `json:"id,omitempty"` // Human-readable promotion message. Does not include the expiry timestamp; consumers may - // format endsAt and append it. + // format endsAt and append it when present. Message *string `json:"message,omitempty"` } @@ -4650,9 +5384,6 @@ type ModelList struct { Models []Model `json:"models"` } -// Optional listing options. -// Experimental: ModelListRequest is part of an experimental API and may change or be -// removed. type ModelListRequest struct { // If true, bypasses the per-session model list cache and re-fetches from CAPI. SkipCache *bool `json:"skipCache,omitempty"` @@ -4702,13 +5433,21 @@ type ModelSwitchToRequest struct { // Explicit context tier for the selected model. `"default"` / `"long_context"` apply the // requested tier; omit this field to use normal model behavior with no explicit tier. ContextTier *ContextTier `json:"contextTier,omitempty"` + // When true, defer this switch (enqueue it) if another model change is already queued, even + // when no turn is active — so it drains last (FIFO) and wins over the already-queued + // change. Intended for genuine user-initiated model selections; internal restore/reapply + // switches omit it and apply immediately when no turn is active. When no other model change + // is queued this has no effect (a switch still applies immediately unless a turn is active). + DeferIfModelChangeQueued *bool `json:"deferIfModelChangeQueued,omitempty"` // Override individual model capabilities resolved by the runtime ModelCapabilities *ModelCapabilitiesOverride `json:"modelCapabilities,omitempty"` // Model selection id to switch to, as returned by `list`. A bare id (e.g. // `claude-sonnet-4.6`) names a Copilot (CAPI) model; a provider-qualified id // (`provider/id`, e.g. `acme/claude-sonnet`) targets a registry BYOK model. ModelID string `json:"modelId"` - // Reasoning effort level to use for the model. "none" disables reasoning. + // Reasoning effort level to use for the model. CAPI values are model-defined and validated + // against the selected model; BYOK providers may define additional values. "none" disables + // reasoning. When omitted, no effort override is applied. ReasoningEffort *string `json:"reasoningEffort,omitempty"` // Reasoning summary mode to request for supported model clients ReasoningSummary *ReasoningSummary `json:"reasoningSummary,omitempty"` @@ -4720,6 +5459,11 @@ type ModelSwitchToRequest struct { // Experimental: ModelSwitchToResult is part of an experimental API and may change or be // removed. type ModelSwitchToResult struct { + // True when the switch was deferred (enqueued as a cancellable `/model` command) because a + // turn was active or another model change was already queued, rather than applied + // immediately. When true, the session's live model is unchanged until the queued change + // drains. + Deferred *bool `json:"deferred,omitempty"` // Currently active model identifier after the switch ModelID *string `json:"modelId,omitempty"` } @@ -4975,6 +5719,8 @@ func (PermissionDecisionApproveForSession) Kind() PermissionDecisionKind { // Experimental: PermissionDecisionApproveOnce is part of an experimental API and may change // or be removed. type PermissionDecisionApproveOnce struct { + // True only when a host surfaced this request to a user who approved it. + ApprovedInteractively *bool `json:"approvedInteractively,omitempty"` } func (PermissionDecisionApproveOnce) permissionDecision() {} @@ -5186,6 +5932,21 @@ func (PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess) Kin return PermissionDecisionApproveForLocationApprovalKindExtensionPermissionAccess } +// Location-scoped factory approval, optionally narrowed by approval key. +// Experimental: PermissionDecisionApproveForLocationApprovalFactory is part of an +// experimental API and may change or be removed. +type PermissionDecisionApproveForLocationApprovalFactory struct { + // Optional factory operation name or canonical approval key; when omitted, the approval + // covers all factory operations. + ApprovalKey *string `json:"approvalKey,omitempty"` +} + +func (PermissionDecisionApproveForLocationApprovalFactory) permissionDecisionApproveForLocationApproval() { +} +func (PermissionDecisionApproveForLocationApprovalFactory) Kind() PermissionDecisionApproveForLocationApprovalKind { + return PermissionDecisionApproveForLocationApprovalKindFactory +} + // Location-scoped approval details for an MCP server tool, or all tools on the server when // `toolName` is null. // Experimental: PermissionDecisionApproveForLocationApprovalMCP is part of an experimental @@ -5331,6 +6092,21 @@ func (PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess) Kind return PermissionDecisionApproveForSessionApprovalKindExtensionPermissionAccess } +// Session-scoped factory approval, optionally narrowed by approval key. +// Experimental: PermissionDecisionApproveForSessionApprovalFactory is part of an +// experimental API and may change or be removed. +type PermissionDecisionApproveForSessionApprovalFactory struct { + // Optional factory operation name or canonical approval key; when omitted, the approval + // covers all factory operations. + ApprovalKey *string `json:"approvalKey,omitempty"` +} + +func (PermissionDecisionApproveForSessionApprovalFactory) permissionDecisionApproveForSessionApproval() { +} +func (PermissionDecisionApproveForSessionApprovalFactory) Kind() PermissionDecisionApproveForSessionApprovalKind { + return PermissionDecisionApproveForSessionApprovalKindFactory +} + // Session-scoped approval details for an MCP server tool, or all tools on the server when // `toolName` is null. // Experimental: PermissionDecisionApproveForSessionApprovalMCP is part of an experimental @@ -5397,10 +6173,26 @@ func (PermissionDecisionApproveForSessionApprovalWrite) Kind() PermissionDecisio return PermissionDecisionApproveForSessionApprovalKindWrite } +// Optional informational context describing how and where the permission decision was made. +// This does not affect permission behavior. +// Experimental: PermissionDecisionContext is part of an experimental API and may change or +// be removed. +type PermissionDecisionContext struct { + // Disposition of the permission request as observed by the responding client. + Outcome PermissionDecisionOutcome `json:"outcome"` + // Controlled reason or actor responsible for the response. + Source PermissionDecisionSource `json:"source"` + // Client surface that submitted the response. + Surface PermissionDecisionSurface `json:"surface"` +} + // Pending permission request ID and the decision to apply (approve/reject and scope). // Experimental: PermissionDecisionRequest is part of an experimental API and may change or // be removed. type PermissionDecisionRequest struct { + // Optional informational context describing how and where this response was made. Omit it + // to preserve legacy behavior without attributing an origin. + DecisionContext *PermissionDecisionContext `json:"decisionContext,omitempty"` // Request ID of the pending permission request RequestID string `json:"requestId"` // The client's response to the pending permission prompt @@ -5744,6 +6536,21 @@ func (PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess) Kind( return PermissionsLocationsAddToolApprovalDetailsKindExtensionPermissionAccess } +// Location-persisted factory approval, optionally narrowed by approval key. +// Experimental: PermissionsLocationsAddToolApprovalDetailsFactory is part of an +// experimental API and may change or be removed. +type PermissionsLocationsAddToolApprovalDetailsFactory struct { + // Optional factory operation name or canonical approval key; when omitted, the approval + // covers all factory operations. + ApprovalKey *string `json:"approvalKey,omitempty"` +} + +func (PermissionsLocationsAddToolApprovalDetailsFactory) permissionsLocationsAddToolApprovalDetails() { +} +func (PermissionsLocationsAddToolApprovalDetailsFactory) Kind() PermissionsLocationsAddToolApprovalDetailsKind { + return PermissionsLocationsAddToolApprovalDetailsKindFactory +} + // Location-persisted tool approval details for an MCP server tool, or all tools when // `toolName` is null. // Experimental: PermissionsLocationsAddToolApprovalDetailsMCP is part of an experimental @@ -5877,10 +6684,12 @@ type PermissionsPathsUpdatePrimaryResult struct { type PermissionsPendingRequestsRequest struct { } -// No parameters; clears all session-scoped tool permission approvals. +// Clears session-scoped tool permission approvals, and optionally the location-scoped ones. // Experimental: PermissionsResetSessionApprovalsRequest is part of an experimental API and // may change or be removed. type PermissionsResetSessionApprovalsRequest struct { + // Whether location-scoped approvals are cleared too. Defaults to `true`. + IncludeLocation *bool `json:"includeLocation,omitempty"` } // Indicates whether the operation succeeded. @@ -5902,7 +6711,8 @@ type PermissionsSetAllowAllRequest struct { // auto-approval; `off` disables both. Mode *PermissionsAllowAllMode `json:"mode,omitempty"` // Optional model id for the `auto` mode auto-approval LLM judging. Only meaningful when - // `mode` is `auto`; ignored otherwise. When omitted, the session's active model is used. + // `mode` is `auto`; ignored otherwise. When omitted, the session resolves a default judge + // model: `gpt-5.5` for CAPI sessions and the session's active model for BYOK sessions. Model *string `json:"model,omitempty"` // Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers. Source *PermissionsSetAllowAllSource `json:"source,omitempty"` @@ -6187,9 +6997,6 @@ type PluginsMarketplacesRemoveRequest struct { Name string `json:"name"` } -// Optional flags controlling which side effects the reload performs. -// Experimental: PluginsReloadRequest is part of an experimental API and may change or be -// removed. type PluginsReloadRequest struct { // When true, skip repo-level hooks during the hook reload. Use before folder trust is // confirmed; load them post-trust via `sessions.loadDeferredRepoHooks`. @@ -6372,9 +7179,6 @@ type ProviderEndpoint struct { WireAPI *ProviderEndpointWireAPI `json:"wireApi,omitempty"` } -// Optional model identifier to scope the endpoint snapshot to. -// Experimental: ProviderGetEndpointRequest is part of an experimental API and may change or -// be removed. type ProviderGetEndpointRequest struct { // Model identifier the caller intends to use against the returned endpoint. Used to pick // the correct wire shape. Omit to use whichever model the session is currently using. @@ -6812,6 +7616,30 @@ type PushGitHubRepoRef struct { Owner string `json:"owner"` } +// Inputs for starting a deferred-idle drain. +// Experimental: QueueBeginDeferredIdleDrainRequest is part of an experimental API and may +// change or be removed. +type QueueBeginDeferredIdleDrainRequest struct { + // Whether the host still has active background work. + ActiveBackgroundWork bool `json:"activeBackgroundWork"` +} + +// Whether a deferred-idle drain should run. +// Experimental: QueueBeginDeferredIdleDrainResult is part of an experimental API and may +// change or be removed. +type QueueBeginDeferredIdleDrainResult struct { + // True when the host should run finishDeferredIdleDrain asynchronously. + ShouldDrain bool `json:"shouldDrain"` +} + +// Internal filter for consuming queued system notifications. +// Experimental: QueueConsumeSystemNotificationsRequest is part of an experimental API and +// may change or be removed. +type QueueConsumeSystemNotificationsRequest struct { + // Opaque runtime-owned filter object. + Filter any `json:"filter"` +} + // Result of the queued command execution. // Experimental: QueuedCommandResult is part of an experimental API and may change or be // removed. @@ -6847,46 +7675,257 @@ func (QueuedCommandNotHandled) Handled() bool { return false } -// User-facing pending queue entry, with kind and display text for a queued message, slash -// command, or model change. -// Experimental: QueuePendingItems is part of an experimental API and may change or be +// Inputs for marking session.idle deferred in native state. +// Experimental: QueueDeferSessionIdleRequest is part of an experimental API and may change +// or be removed. +type QueueDeferSessionIdleRequest struct { + // Whether the deferred idle was caused by an aborted foreground turn. + Aborted bool `json:"aborted"` +} + +// Parameters for duplicating a queued item. +// Experimental: QueueDuplicateAtRequest is part of an experimental API and may change or be // removed. -type QueuePendingItems struct { - // Human-readable text to display for this queue entry in the UI - DisplayText string `json:"displayText"` - // Whether this item is a queued user message or a queued slash command / model change - Kind QueuePendingItemsKind `json:"kind"` +type QueueDuplicateAtRequest struct { + ID string `json:"id"` } -// Snapshot of the session's pending queued items and immediate-steering messages. -// Experimental: QueuePendingItemsResult is part of an experimental API and may change or be +// Result of duplicating a queued item. +// Experimental: QueueDuplicateAtResult is part of an experimental API and may change or be // removed. -type QueuePendingItemsResult struct { - // Pending queued items in submission order. Includes user messages, queued slash commands, - // and queued model changes; omits internal system items. - Items []QueuePendingItems `json:"items"` - // Display text for messages currently in the immediate steering queue (interjections sent - // during a running turn). - SteeringMessages []string `json:"steeringMessages"` +type QueueDuplicateAtResult struct { + // Fresh stable opaque id assigned to the duplicate. + ID string `json:"id"` } -// Indicates whether a user-facing pending item was removed. -// Experimental: QueueRemoveMostRecentResult is part of an experimental API and may change -// or be removed. -type QueueRemoveMostRecentResult struct { - // True if a user-facing pending item was removed (LIFO across both queues); false when no - // removable items remained. - Removed bool `json:"removed"` +// Result of enqueueing the resume-pending wake item. +// Experimental: QueueEnqueueResumePendingResult is part of an experimental API and may +// change or be removed. +type QueueEnqueueResumePendingResult struct { + // True when a wake item was newly queued. + Queued bool `json:"queued"` } -// Event type to register consumer interest for, used by runtime gating logic. -// Experimental: RegisterEventInterestParams is part of an experimental API and may change -// or be removed. -type RegisterEventInterestParams struct { - // The event type the consumer wants the runtime to treat as 'observed' for - // behavior-switching gating. Some runtime code paths inspect whether any consumer is - // interested in a specific event type and choose a different implementation accordingly - // (e.g. `mcp.oauth_required`: when interest is registered the runtime delegates interactive +// Inputs for completing a deferred-idle drain. +// Experimental: QueueFinishDeferredIdleDrainRequest is part of an experimental API and may +// change or be removed. +type QueueFinishDeferredIdleDrainRequest struct { + // Whether the host still has active background work. + ActiveBackgroundWork bool `json:"activeBackgroundWork"` + // Whether native queued work remains. + HasPending bool `json:"hasPending"` +} + +// Action selected by the native deferred-idle drain. +// Experimental: QueueFinishDeferredIdleDrainResult is part of an experimental API and may +// change or be removed. +type QueueFinishDeferredIdleDrainResult struct { + // Whether the deferred idle was caused by an aborted foreground turn. + Aborted bool `json:"aborted"` + // One of none, processQueue, or emitSessionIdle. + Action string `json:"action"` +} + +// Whether the native queue has pending work. +// Experimental: QueueHasPendingResult is part of an experimental API and may change or be +// removed. +type QueueHasPendingResult struct { + // True when queued or immediate native work is pending. + HasPending bool `json:"hasPending"` +} + +// Parameters for inserting a queued message at a public visible position. +// Experimental: QueueInsertAtRequest is part of an experimental API and may change or be +// removed. +type QueueInsertAtRequest struct { + Message QueueInsertMessage `json:"message"` + // Zero-based position in the public visible queue. Values outside the queue clamp to an end. + Position int64 `json:"position"` +} + +// Result of inserting a queued message. +// Experimental: QueueInsertAtResult is part of an experimental API and may change or be +// removed. +type QueueInsertAtResult struct { + // Fresh stable opaque id assigned to the inserted item. + ID string `json:"id"` +} + +// Serializable message fields accepted by queue.insertAt. +// Experimental: QueueInsertMessage is part of an experimental API and may change or be +// removed. +type QueueInsertMessage struct { + // Optional explicit agent mode. When omitted, the session's current mode is assigned. + AgentMode *SendAgentMode `json:"agentMode,omitempty"` + // Optional attachments for the message. + Attachments []Attachment `json:"attachments,omitzero"` + // Whether the message is billable. + Billable *bool `json:"billable,omitempty"` + // Accepted for internal SendOptions compatibility but ignored; delivery is derived from + // current session activity. + Delivery *string `json:"delivery,omitempty"` + // Optional user-facing display text. + DisplayPrompt *string `json:"displayPrompt,omitempty"` + // Accepted for SendOptions compatibility but ignored; inserted items always use queued + // delivery semantics. + Mode *SendMode `json:"mode,omitempty"` + // Accepted for SendOptions compatibility but ignored; the requested public position + // controls placement. + Prepend *bool `json:"prepend,omitempty"` + // The user message text. + Prompt string `json:"prompt"` + // Per-turn request headers. + RequestHeaders map[string]string `json:"requestHeaders,omitzero"` + // Required tool name for the turn, when any. + RequiredTool *string `json:"requiredTool,omitempty"` + // Optional provenance source. `system` is rejected: it would hide the inserted row from + // `pendingItems` and make it unaddressable while still executing, so inserted items must + // stay visible. + Source *string `json:"source,omitempty"` + // Accepted for SendOptions compatibility but ignored; insertion scheduling is controlled by + // the queue drain state. + Wait *bool `json:"wait,omitempty"` +} + +// Parameters for moving a queued item by stable id. +// Experimental: QueueMoveItemRequest is part of an experimental API and may change or be +// removed. +type QueueMoveItemRequest struct { + // Stable opaque queued-item id. + ID string `json:"id"` + // Zero-based target position in the public visible queue. Values outside the queue clamp to + // an end. + ToPosition int64 `json:"toPosition"` +} + +// Result of moving a queued item. +// Experimental: QueueMoveItemResult is part of an experimental API and may change or be +// removed. +type QueueMoveItemResult struct { + // True when the item changed position; false when it was already at the requested position. + Changed bool `json:"changed"` +} + +// User-facing pending queue entry, with kind and display text for a queued message, slash +// command, or model change. +// Experimental: QueuePendingItems is part of an experimental API and may change or be +// removed. +type QueuePendingItems struct { + // Agent mode stored on this queued entry, as stamped when it was enqueued. Items without an + // explicit mode report interactive. This is not necessarily the mode that will constrain + // the turn: a plan or autopilot session applies its own write gate, continuation loop and + // permission posture to every drained item regardless of the mode stored here. + AgentMode SendAgentMode `json:"agentMode"` + // Human-readable text to display for this queue entry in the UI + DisplayText string `json:"displayText"` + // Stable opaque id for the canonical queued item. Batch rows share one id. + ID string `json:"id"` + // Whether this item is a queued user message or a queued slash command / model change + Kind QueuePendingItemsKind `json:"kind"` +} + +// Snapshot of the session's pending queued items and immediate-steering messages. +// Experimental: QueuePendingItemsResult is part of an experimental API and may change or be +// removed. +type QueuePendingItemsResult struct { + // Pending queued items in submission order. Includes user messages, queued slash commands, + // and queued model changes; omits internal system items. + Items []QueuePendingItems `json:"items"` + // Display text for messages currently in the immediate steering queue (interjections sent + // during a running turn). + SteeringMessages []string `json:"steeringMessages"` +} + +// Parameters for removing a queued item by stable id. +// Experimental: QueueRemoveAtRequest is part of an experimental API and may change or be +// removed. +type QueueRemoveAtRequest struct { + ID string `json:"id"` +} + +// Result of removing a queued item. +// Experimental: QueueRemoveAtResult is part of an experimental API and may change or be +// removed. +type QueueRemoveAtResult struct { + // True when the addressed item was removed. + Removed bool `json:"removed"` +} + +// Indicates whether a user-facing pending item was removed. +// Experimental: QueueRemoveMostRecentResult is part of an experimental API and may change +// or be removed. +type QueueRemoveMostRecentResult struct { + // True if a user-facing pending item was removed (LIFO across both queues); false when no + // removable items remained. + Removed bool `json:"removed"` +} + +// Parameters for steering a queued message into a live turn. +// Experimental: QueueSendNowRequest is part of an experimental API and may change or be +// removed. +type QueueSendNowRequest struct { + ID string `json:"id"` +} + +// Result of trying to steer a queued message into a live turn. +// Experimental: QueueSendNowResult is part of an experimental API and may change or be +// removed. +type QueueSendNowResult struct { + // True when the item was accepted into the steering lane; false when no main turn was live. + Steered bool `json:"steered"` +} + +// Parameters for acquiring or releasing the queued-lane drain pause. Acquisition is +// exclusive and non-idempotent: `paused: true` against an already-paused session fails with +// `queue_already_paused`. The pause is never released automatically — it is not tied to the +// caller's lifetime, so a client that exits without sending `paused: false` leaves the lane +// frozen. Release is unowned: `paused: false` clears the pause for any caller, including +// one that never acquired it. +// Experimental: QueueSetDrainPausedRequest is part of an experimental API and may change or +// be removed. +type QueueSetDrainPausedRequest struct { + Paused bool `json:"paused"` +} + +// Internal snapshot of native queue state for local session orchestration. +// Experimental: QueueSnapshotResult is part of an experimental API and may change or be +// removed. +type QueueSnapshotResult struct { + // Insertion orders for queued items, aligned with `items`. + ItemOrders []int64 `json:"itemOrders,omitzero"` + // User-facing pending items in FIFO order. + Items []QueuePendingItems `json:"items"` + // Insertion orders for immediate steering messages, aligned with `steeringMessages`. + SteeringMessageOrders []int64 `json:"steeringMessageOrders,omitzero"` + // Immediate steering messages waiting for an active turn. + SteeringMessages []string `json:"steeringMessages"` +} + +// Parameters for editing a single queued message. +// Experimental: QueueUpdateTextRequest is part of an experimental API and may change or be +// removed. +type QueueUpdateTextRequest struct { + DisplayPrompt *string `json:"displayPrompt,omitempty"` + ID string `json:"id"` + Prompt string `json:"prompt"` +} + +// Result of editing a queued message. +// Experimental: QueueUpdateTextResult is part of an experimental API and may change or be +// removed. +type QueueUpdateTextResult struct { + // True when the stored text changed. + Updated bool `json:"updated"` +} + +// Event type to register consumer interest for, used by runtime gating logic. +// Experimental: RegisterEventInterestParams is part of an experimental API and may change +// or be removed. +type RegisterEventInterestParams struct { + // The event type the consumer wants the runtime to treat as 'observed' for + // behavior-switching gating. Some runtime code paths inspect whether any consumer is + // interested in a specific event type and choose a different implementation accordingly + // (e.g. `mcp.oauth_required`: when interest is registered the runtime delegates interactive // OAuth token acquisition to the consumer via `mcp.oauth_required` events; when no interest // is registered the runtime still attempts non-interactive reconnect from cached or // refreshable tokens, and only marks the server `needs-auth` if usable credentials are @@ -6911,6 +7950,11 @@ type RegisterEventInterestResult struct { Handle string `json:"handle"` } +// Experimental: RegisterExtensionLaunchProviderResult is part of an experimental API and +// may change or be removed. +type RegisterExtensionLaunchProviderResult struct { +} + // Params to attach an extension loader's tools to a session. // Experimental: RegisterExtensionToolsParams is part of an experimental API and may change // or be removed. @@ -7189,19 +8233,37 @@ type RuntimeShutdownResult struct { type SandboxConfig struct { // Whether to auto-add the current working directory to readwritePaths. Default: true. AddCurrentWorkingDirectory *bool `json:"addCurrentWorkingDirectory,omitempty"` + // Whether to auto-grant read access to common developer-tool caches, registries, and + // toolchains in their default home locations (cargo, go, npm, Maven, and more), plus + // read-write access to (and, on Unix, up-front creation of) the scratch caches builds write + // on every run (go-build, ccache, sccache, Gradle caches, Cargo lock/tracker files), so + // builds work without extra configuration; a relocated CARGO_HOME additionally gets its + // Cargo lock files granted read-write. Default: true (enabled by default; set to false to + // opt out). + AllowDevToolAccess *bool `json:"allowDevToolAccess,omitempty"` + // Credential-injection capability flags. + Auth *SandboxConfigAuth `json:"auth,omitempty"` // Whether sandboxing is enabled for the session. Enabled bool `json:"enabled"` - // Whether to export `GH_TOKEN` so the `gh` CLI authenticates inside the sandbox without the - // OS keyring the sandbox blocks. Default: false (opt-in). - GhAuth *bool `json:"ghAuth,omitempty"` - // Whether to inject the Copilot GitHub token as an `http..extraheader` so - // authenticated HTTPS git works inside the sandbox without the shell-based credential - // helper the sandbox blocks. Default: false (opt-in). - GitAuth *bool `json:"gitAuth,omitempty"` // User-managed sandbox policy fragment merged into the auto-discovered base policy. UserPolicy *SandboxConfigUserPolicy `json:"userPolicy,omitempty"` } +// Credential-injection capability flags applied while the sandbox is enabled. +// Experimental: SandboxConfigAuth is part of an experimental API and may change or be +// removed. +type SandboxConfigAuth struct { + // Whether to export `GH_TOKEN` so the `gh` CLI authenticates inside the sandbox without the + // OS keyring the sandbox blocks. Default: false (opt-in). + Gh *bool `json:"gh,omitempty"` + // Whether to inject git credentials as an `http..extraheader` so authenticated HTTPS + // git works inside the sandbox without the shell-based credential helper the sandbox + // blocks. github.com is served by the Copilot token; every other forge (Azure DevOps, + // GitHub Enterprise Server, GitLab, ...) by a credential the host resolves from the user's + // own helper before the sandbox is applied. Default: false (opt-in). + Git *bool `json:"git,omitempty"` +} + // User-managed sandbox policy fragment merged into the auto-discovered base policy. // Experimental: SandboxConfigUserPolicy is part of an experimental API and may change or be // removed. @@ -7255,6 +8317,36 @@ type SandboxConfigUserPolicyNetwork struct { AllowLocalNetwork *bool `json:"allowLocalNetwork,omitempty"` // Whether outbound network traffic is allowed at all. AllowOutbound *bool `json:"allowOutbound,omitempty"` + // HTTP proxy the sandboxed process routes traffic through. Enforced on Windows and + // cooperative (honored by well-behaved tools, not strictly enforced) on Linux and macOS. + // Credentials go in the separate `username`/`password` fields. A credential-free http:// + // loopback proxy URL is routed through the localhost proxy automatically; an https:// or + // authenticated loopback URL is used as-is. + Proxy *SandboxConfigUserPolicyNetworkProxy `json:"proxy,omitempty"` +} + +// HTTP proxy configuration for sandboxed traffic. +// Experimental: SandboxConfigUserPolicyNetworkProxy is part of an experimental API and may +// change or be removed. +type SandboxConfigUserPolicyNetworkProxy struct { + // Optional password for proxy authentication, combined with the URL at spawn time. The + // persisted value may be a literal password, a `${secret:…}` reference resolved from the OS + // keychain, or a `${VAR}`/`$VAR` environment reference; it is resolved just before the + // sandboxed process routes through the proxy. The /sandbox dialog stores a real password in + // the OS keychain and persists only a `${secret:…}` placeholder (never plaintext in + // settings.json); the field is masked in the dialog and redacted by /settings show. + Password *string `json:"password,omitempty"` + // Proxy URL (e.g. http://proxy.example.com:8080). The port is optional and defaults to the + // scheme's standard port when omitted. Credentials must not be embedded here — a + // `user:pass@` authority is rejected; put them in the separate `username`/`password` + // fields. A credential-free http:// loopback URL is routed through the localhost proxy + // automatically; loopback covers localhost and any *.localhost subdomain, the whole + // 127.0.0.0/8 range, ::1, and IPv4-mapped loopback (::ffff:127.0.0.1). An https:// URL, or + // one with a username/password set, is used as-is. + URL string `json:"url"` + // Optional username for proxy authentication. Combined with the URL (and `password`) into + // `user:pass@host` when the sandboxed process routes through the proxy. + Username *string `json:"username,omitempty"` } // macOS seatbelt-specific options. @@ -7265,6 +8357,70 @@ type SandboxConfigUserPolicySeatbelt struct { KeychainAccess *bool `json:"keychainAccess,omitempty"` } +// Register an absolute-time scheduled prompt. +// Experimental: ScheduleAddAtRequest is part of an experimental API and may change or be +// removed. +type ScheduleAddAtRequest struct { + // Epoch milliseconds when the prompt should fire. + At int64 `json:"at"` + // Optional display-only prompt label. + DisplayPrompt *string `json:"displayPrompt,omitempty"` + // Prompt text to enqueue when the schedule fires. + Prompt string `json:"prompt"` + // Whether the schedule should re-arm after each tick. Defaults to false. + Recurring *bool `json:"recurring,omitempty"` +} + +// Register a cron scheduled prompt. +// Experimental: ScheduleAddCronRequest is part of an experimental API and may change or be +// removed. +type ScheduleAddCronRequest struct { + // 5-field cron expression. + Cron string `json:"cron"` + // Optional display-only prompt label. + DisplayPrompt *string `json:"displayPrompt,omitempty"` + // Prompt text to enqueue when the schedule fires. + Prompt string `json:"prompt"` + // Whether the schedule should re-arm after each tick. Defaults to true. + Recurring *bool `json:"recurring,omitempty"` + // IANA timezone for evaluating the cron expression. + Tz *string `json:"tz,omitempty"` +} + +// Register a relative-interval scheduled prompt. +// Experimental: ScheduleAddRequest is part of an experimental API and may change or be +// removed. +type ScheduleAddRequest struct { + // Optional display-only prompt label. + DisplayPrompt *string `json:"displayPrompt,omitempty"` + // Human-readable interval such as `30s`, `5m`, or `2h`. + Interval string `json:"interval"` + // Prompt text to enqueue when the schedule fires. + Prompt string `json:"prompt"` + // Whether the schedule should re-arm after each tick. Defaults to true. + Recurring *bool `json:"recurring,omitempty"` +} + +// Result of registering or re-arming a scheduled prompt. +// Experimental: ScheduleAddResult is part of an experimental API and may change or be +// removed. +type ScheduleAddResult struct { + // The registered or updated schedule entry. + Entry *ScheduleEntry `json:"entry,omitempty"` + // User-facing validation error, when registration failed. + Error *string `json:"error,omitempty"` +} + +// Register a self-paced scheduled prompt. +// Experimental: ScheduleAddSelfPacedRequest is part of an experimental API and may change +// or be removed. +type ScheduleAddSelfPacedRequest struct { + // Optional display-only prompt label. + DisplayPrompt *string `json:"displayPrompt,omitempty"` + // Prompt text to enqueue when the schedule fires. + Prompt string `json:"prompt"` +} + // Scheduled prompt entry with ID, timing (`intervalMs`, `cron`, or `at`), prompt text, // recurrence, and next run time. // Experimental: ScheduleEntry is part of an experimental API and may change or be removed. @@ -7294,6 +8450,14 @@ type ScheduleEntry struct { Tz *string `json:"tz,omitempty"` } +// Whether the session currently has an active self-paced schedule. +// Experimental: ScheduleHasSelfPacedResult is part of an experimental API and may change or +// be removed. +type ScheduleHasSelfPacedResult struct { + // True when at least one active schedule is self-paced. + HasSelfPaced bool `json:"hasSelfPaced"` +} + // Snapshot of the currently active recurring prompts for this session. // Experimental: ScheduleList is part of an experimental API and may change or be removed. type ScheduleList struct { @@ -7301,6 +8465,16 @@ type ScheduleList struct { Entries []ScheduleEntry `json:"entries"` } +// Re-arm a self-paced scheduled prompt. +// Experimental: ScheduleRearmSelfPacedRequest is part of an experimental API and may change +// or be removed. +type ScheduleRearmSelfPacedRequest struct { + // Epoch milliseconds when the prompt should next fire. + At int64 `json:"at"` + // Id of the self-paced scheduled prompt. + ID int64 `json:"id"` +} + // Identifier of the scheduled prompt to remove. // Experimental: ScheduleStopRequest is part of an experimental API and may change or be // removed. @@ -7365,10 +8539,9 @@ type SendMessageItem struct { // If set, the request will fail if the named tool is not available when this message is // among the user messages at the start of the current exchange RequiredTool *string `json:"requiredTool,omitempty"` - // Optional provenance tag copied to the resulting user.message event. Must match one of - // three forms: the literal `system`, `command-` for messages originating from a - // command (e.g. slash command, Mission Control command), or `schedule-` for - // messages originating from a scheduled job. + // Optional provenance tag copied to the resulting user.message event. Must be `user`, + // `system`, `command-` for command-originated messages, `schedule-` + // for scheduled prompts, or `agent-` for prompts sent by another agent. // Internal: Source is part of the SDK's internal API surface and is not intended for // external use. Source *string `json:"source,omitempty"` @@ -7402,6 +8575,12 @@ type SendMessagesRequest struct { // If true, await completion of the agentic loop for this turn before returning. Defaults to // false (fire-and-forget). When true, the result still contains the same `messageIds`; the // caller can rely on the agent having processed the messages before the call resolves. + // Transport-dependent tail semantics: on a LOCAL (in-process) session the wait additionally + // blocks until the completed turn's event tail has been dispatched to this session's + // in-process subscribers, so a subsequent read of subscriber state already reflects the + // turn; on a REMOTE session the wait resolves once the loop completes and mirrored delivery + // follows over the wire. Callers that need the stronger local guarantee on remote sessions + // should await the event stream explicitly. Wait *bool `json:"wait,omitempty"` } @@ -7442,10 +8621,9 @@ type SendRequest struct { // If set, the request will fail if the named tool is not available when this message is // among the user messages at the start of the current exchange RequiredTool *string `json:"requiredTool,omitempty"` - // Optional provenance tag copied to the resulting user.message event. Must match one of - // three forms: the literal `system`, `command-` for messages originating from a - // command (e.g. slash command, Mission Control command), or `schedule-` for - // messages originating from a scheduled job. + // Optional provenance tag copied to the resulting user.message event. Must be `user`, + // `system`, `command-` for command-originated messages, `schedule-` + // for scheduled prompts, or `agent-` for prompts sent by another agent. // Internal: Source is part of the SDK's internal API surface and is not intended for // external use. Source *string `json:"source,omitempty"` @@ -7456,6 +8634,12 @@ type SendRequest struct { // If true, await completion of the agentic loop for this message before returning. Defaults // to false (fire-and-forget). When true, the result still contains the same `messageId`; // the caller can rely on the agent having processed the message before the call resolves. + // Transport-dependent tail semantics: on a LOCAL (in-process) session the wait additionally + // blocks until the completed turn's event tail has been dispatched to this session's + // in-process subscribers, so a subsequent read of subscriber state already reflects the + // turn; on a REMOTE session the wait resolves once the loop completes and mirrored delivery + // follows over the wire. Callers that need the stronger local guarantee on remote sessions + // should await the event stream explicitly. Wait *bool `json:"wait,omitempty"` } @@ -7466,6 +8650,18 @@ type SendResult struct { MessageID string `json:"messageId"` } +// Internal request for sending a system notification. +// Experimental: SendSystemNotificationRequest is part of an experimental API and may change +// or be removed. +type SendSystemNotificationRequest struct { + // Optional structured notification kind. + Kind any `json:"kind,omitempty"` + // Notification text to deliver to the model. + Message string `json:"message"` + // Internal delivery options, including passive policy. + Options any `json:"options,omitempty"` +} + // Agents discovered across user, project, plugin, and remote sources. // Experimental: ServerAgentList is part of an experimental API and may change or be removed. type ServerAgentList struct { @@ -7488,6 +8684,8 @@ type ServerSkill struct { // Optional freeform hint describing the skill's expected arguments, from the // `argument-hint` frontmatter field ArgumentHint *string `json:"argumentHint,omitempty"` + // Canonical slash command name used to invoke the skill, without the leading '/' + CommandName *string `json:"commandName,omitempty"` // Description of what the skill does Description string `json:"description"` // Whether the skill is currently enabled (based on global config) @@ -7528,6 +8726,25 @@ type SessionActivity struct { type SessionAgentDeselectResult struct { } +// Experimental: SessionAgentListRequest is part of an experimental API and may change or be +// removed. +type SessionAgentListRequest struct { + // When true, request the session's configured built-in agents alongside custom agents. + // Listing applies feature, context, inclusion, exclusion, and user-disabled-agent policy, + // but does not evaluate transient invocation requirements such as model availability. + // Built-in metadata may be omitted when the session cannot project it, such as a relay + // session. + IncludeBuiltInAgents *bool `json:"includeBuiltInAgents,omitempty"` + // When true, request authored base prompt text on each AgentInfo. Prompt text may be + // omitted when unavailable, such as for agents projected through a relay session. + IncludePrompt *bool `json:"includePrompt,omitempty"` +} + +// Experimental: SessionAgentSetPromptResult is part of an experimental API and may change +// or be removed. +type SessionAgentSetPromptResult struct { +} + // Authentication status and account metadata for the session. // Experimental: SessionAuthStatus is part of an experimental API and may change or be // removed. @@ -7556,11 +8773,27 @@ type SessionBulkDeleteResult struct { FreedBytes map[string]int64 `json:"freedBytes"` } +// The number of running background agents (task-registry agents) that were cancelled. +// Experimental: SessionCancelAllBackgroundAgentsResult is part of an experimental API and +// may change or be removed. +type SessionCancelAllBackgroundAgentsResult int64 + // Experimental: SessionCanvasCloseResult is part of an experimental API and may change or // be removed. type SessionCanvasCloseResult struct { } +// Experimental: SessionCommandsListRequest is part of an experimental API and may change or +// be removed. +type SessionCommandsListRequest struct { + // Include runtime built-in commands + IncludeBuiltins *bool `json:"includeBuiltins,omitempty"` + // Include commands registered by protocol clients, including SDK clients and extensions + IncludeClientCommands *bool `json:"includeClientCommands,omitempty"` + // Include enabled user-invocable skills and commands + IncludeSkills *bool `json:"includeSkills,omitempty"` +} + // A single host-driven completion. Accepting an item replaces `[rangeStart, rangeEnd)` // (UTF-16 code units) in the composer with `insertText`; when the range is absent, the // active token around the cursor is replaced. @@ -7600,17 +8833,65 @@ type SessionContext struct { // Experimental: SessionContextAttribution is part of an experimental API and may change or // be removed. type SessionContextAttribution struct { + // Output reserve plus the tokens past the buffer-exhaustion blocking threshold. Mirrors + // `SessionContextInfo.bufferTokens`. + BufferTokens int64 `json:"bufferTokens"` + // The six normalized `/context` header buckets, computed from the same tokenization as + // `entries` so the two never disagree. Convenience rollups: `freeSpace` and `buffer` + // describe window capacity rather than occupied context, so the values do not sum to + // `totalTokens`. + Categories SessionContextAttributionCategories `json:"categories"` // Successful compaction history for the session. Compactions SessionContextAttributionCompactions `json:"compactions"` + // Token count at which background compaction starts. Mirrors + // `SessionContextInfo.compactionThreshold`. + CompactionThreshold int64 `json:"compactionThreshold"` // Flat list of per-source attribution entries. Group by `kind` and render unrecognized // kinds generically. Nesting and rollups are expressed via `parentId`. Entries []SessionContextAttributionEntriesItem `json:"entries"` + // Prompt limit plus the model's output reserve: the full context window + // `categories.freeSpace` and `categories.buffer` are measured against. Mirrors + // `SessionContextInfo.limit`. + Limit int64 `json:"limit"` + // The concrete model id the entire breakdown was tokenized against (feeds the per-model + // token multiplier). Under `Auto` (Free/Student) this is the resolved model, not the + // literal `auto` sentinel, so totals are not undercounted. A single-model approximation of + // a potentially multi-model Auto session. + ModelID string `json:"modelId"` + // How `modelId` was chosen. Not a closed set — tolerate unknown values. Known values today: + // `autoResolved` (the model Auto resolved to), `selected` (the user's explicitly selected + // model), `default` (a fallback before any model is known). + ModelSource string `json:"modelSource"` + // Maximum prompt tokens the resolved model accepts — the denominator for a `##k/###k` + // context-usage display. Mirrors `SessionContextInfo.promptTokenLimit`. + PromptTokenLimit int64 `json:"promptTokenLimit"` // Total token count of the current context window the entries are measured against (system // message + conversation messages + tool definitions — the same total reported by // /context). Divide an entry's `tokens` by this to derive its share. TotalTokens int64 `json:"totalTokens"` } +// The six normalized `/context` header buckets, computed from the same tokenization as +// `entries` so the two never disagree. Convenience rollups: `freeSpace` and `buffer` +// describe window capacity rather than occupied context, so the values do not sum to +// `totalTokens`. +type SessionContextAttributionCategories struct { + // Output reserve plus post-blocking-threshold buffer. + Buffer int64 `json:"buffer"` + // Custom-instructions tokens (0 when none are configured). + CustomInstructions int64 `json:"customInstructions"` + // Remaining unused window capacity (clamped at 0). + FreeSpace int64 `json:"freeSpace"` + // MCP tool-definition tokens. + MCPTools int64 `json:"mcpTools"` + // Conversation (user/assistant/tool) message tokens. + Messages int64 `json:"messages"` + // System prompt tokens, excluding custom instructions. + SystemPrompt int64 `json:"systemPrompt"` + // Non-MCP tool-definition tokens. + SystemTools int64 `json:"systemTools"` +} + // Successful compaction history for the session. type SessionContextAttributionCompactions struct { // Number of successful compactions in this session. @@ -7905,7 +9186,7 @@ type SessionFSSqliteExistsResult struct { } // SQL query, query type, and optional bind parameters for executing a SQLite query against -// the per-session database. +// the per-session database. The provider applies its SQLite busy timeout for every call. // Experimental: SessionFSSqliteQueryRequest is part of an experimental API and may change // or be removed. type SessionFSSqliteQueryRequest struct { @@ -7937,6 +9218,44 @@ type SessionFSSqliteQueryResult struct { RowsAffected int64 `json:"rowsAffected"` } +// Classified SQLite transaction failure. busyOrLocked guarantees rollback; +// postCommitAmbiguous must never be retried. +// Experimental: SessionFSSqliteTransactionError is part of an experimental API and may +// change or be removed. +type SessionFSSqliteTransactionError struct { + ErrorClass SessionFSSqliteTransactionErrorClass `json:"errorClass"` + Message string `json:"message"` +} + +// Statements to execute atomically. Providers apply busy handling for every call. +// Experimental: SessionFSSqliteTransactionRequest is part of an experimental API and may +// change or be removed. +type SessionFSSqliteTransactionRequest struct { + // Target session identifier + SessionID string `json:"sessionId"` + Statements []SessionFSSqliteTransactionStatement `json:"statements"` +} + +// Per-statement results, or a classified transaction error. +// Experimental: SessionFSSqliteTransactionResult is part of an experimental API and may +// change or be removed. +type SessionFSSqliteTransactionResult struct { + Error *SessionFSSqliteTransactionError `json:"error,omitempty"` + Results []SessionFSSqliteQueryResult `json:"results"` +} + +// One statement in an atomic SQLite transaction. +// Experimental: SessionFSSqliteTransactionStatement is part of an experimental API and may +// change or be removed. +type SessionFSSqliteTransactionStatement struct { + // Optional named bind parameters. + Params map[string]any `json:"params,omitzero"` + // SQL statement to execute. + Query string `json:"query"` + // How to execute the statement. + QueryType SessionFSSqliteQueryType `json:"queryType"` +} + // Path whose metadata should be returned from the client-provided session filesystem. // Experimental: SessionFSStatRequest is part of an experimental API and may change or be // removed. @@ -7979,6 +9298,25 @@ type SessionFSWriteFileRequest struct { SessionID string `json:"sessionId"` } +// Experimental: SessionHistoryCompactRequest is part of an experimental API and may change +// or be removed. +type SessionHistoryCompactRequest struct { + // Optional user-provided instructions to focus the compaction summary + CustomInstructions *string `json:"customInstructions,omitempty"` + // Context window token limit this compaction is targeting, recorded as the `tokenLimit` on + // the persisted `session.compaction_start` / `session.compaction_complete` events. Set it + // when the compaction targets a window other than the compacting model's own, e.g. + // switching to a model with a smaller context window: the compaction still runs on the + // current model, so the limit that motivated it would otherwise be lost. When absent, the + // events record the compacting model's own resolved limit. Attribution metadata only - it + // does not change how much the compaction removes. + TokenLimit *int64 `json:"tokenLimit,omitempty"` + // What initiated this compaction request, recorded as the `trigger` on the persisted + // `session.compaction_start` / `session.compaction_complete` events. When absent, the + // compaction is persisted without trigger attribution (initiator unknown). + Trigger *SessionHistoryCompactRequestTrigger `json:"trigger,omitempty"` +} + // Installed plugin record for a session, with marketplace, version, install time, enabled // state, cache path, and source. // Experimental: SessionInstalledPlugin is part of an experimental API and may change or be @@ -7996,6 +9334,12 @@ type SessionInstalledPlugin struct { Name string `json:"name"` // Source descriptor for direct repo installs (when marketplace is empty) Source *SessionInstalledPluginSource `json:"source,omitempty"` + // Per-plugin source fingerprint (a SHA-256 hash of the plugin's catalog source spec plus + // its resolved source subtree — NOT a Git commit SHA) captured at marketplace + // install/update time. Auto-update compares it against the freshly recomputed fingerprint + // to detect a content change that does not bump the version. Absent for pre-existing + // installs and for direct (non-marketplace) installs. + SourceSha *string `json:"source_sha,omitempty"` // Installed version, if known Version *string `json:"version,omitempty"` } @@ -8010,14 +9354,16 @@ type SessionInstalledPluginSource struct { String *string } -// Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref, -// and optional subpath. +// Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref or +// full commit SHA, and optional subpath. // Experimental: SessionInstalledPluginSourceGitHub is part of an experimental API and may // change or be removed. type SessionInstalledPluginSourceGitHub struct { Path *string `json:"path,omitempty"` Ref *string `json:"ref,omitempty"` Repo string `json:"repo"` + // Optional full 40-character hexadecimal commit SHA. + Sha *string `json:"sha,omitempty"` // Constant value. Always "github". Source SessionInstalledPluginSourceGitHubSource `json:"source"` } @@ -8031,18 +9377,118 @@ type SessionInstalledPluginSourceLocal struct { Source SessionInstalledPluginSourceLocalSource `json:"source"` } -// Source descriptor for a direct URL plugin install, with URL, optional ref, and optional -// subpath. +// Source descriptor for a direct URL plugin install, with URL, optional ref or full commit +// SHA, and optional subpath. // Experimental: SessionInstalledPluginSourceURL is part of an experimental API and may // change or be removed. type SessionInstalledPluginSourceURL struct { Path *string `json:"path,omitempty"` Ref *string `json:"ref,omitempty"` + // Optional full 40-character hexadecimal commit SHA. + Sha *string `json:"sha,omitempty"` // Constant value. Always "url". Source SessionInstalledPluginSourceURLSource `json:"source"` URL string `json:"url"` } +// Baseline data provenance for a prediction. +// Experimental: SessionLimitPredictionBaselineData is part of an experimental API and may +// change or be removed. +type SessionLimitPredictionBaselineData struct { + // End of the baseline data slice. + WindowEnd string `json:"windowEnd"` + // Start of the baseline data slice. + WindowStart string `json:"windowStart"` +} + +// Explainable AI-credit session-limit prediction. +// Experimental: SessionLimitPredictionDetails is part of an experimental API and may change +// or be removed. +type SessionLimitPredictionDetails struct { + // Baseline data provenance. + BaselineData SessionLimitPredictionBaselineData `json:"baselineData"` + // Client population used for the prediction. + ClientType SessionLimitPredictionClientType `json:"clientType"` + // Resolved model family when known. + Family *string `json:"family,omitempty"` + // Model identifier used for lookup. + ModelID string `json:"modelId"` + // Recommended maximum AI credits for this session. + RecommendedCap float64 `json:"recommendedCap"` + // Tier chosen as the recommended cap. + RecommendedTier SessionLimitPredictionTier `json:"recommendedTier"` + // Baseline fallback level used to create the prediction. + Source SessionLimitPredictionSource `json:"source"` + // Key matched at the source level, such as a model id, family id, or `global`. + SourceKey string `json:"sourceKey"` + // Ordered usage tiers and their AI-credit caps. + Tiers []SessionLimitPredictionTierOption `json:"tiers"` +} + +// Experimental: SessionLimitPredictionPredictRequest is part of an experimental API and may +// change or be removed. +type SessionLimitPredictionPredictRequest struct { + // Client type to size for. Defaults to `cli-interactive`. + ClientType *SessionLimitPredictionClientType `json:"clientType,omitempty"` + // Optional model identifier override. If omitted, the session's current model is used. + ModelID *string `json:"modelId,omitempty"` +} + +type SessionLimitPredictionRequest struct { + // Client type to size for. Defaults to `cli-interactive`. + ClientType *SessionLimitPredictionClientType `json:"clientType,omitempty"` + // Optional model identifier override. If omitted, the session's current model is used. + ModelID *string `json:"modelId,omitempty"` +} + +// Prediction result. Available results include prediction details; unavailable results +// include an explicit reason. +// Experimental: SessionLimitPredictionResult is part of an experimental API and may change +// or be removed. +type SessionLimitPredictionResult interface { + sessionLimitPredictionResult() + Kind() SessionLimitPredictionResultKind +} + +type RawSessionLimitPredictionResultData struct { + Discriminator SessionLimitPredictionResultKind + Raw json.RawMessage +} + +func (RawSessionLimitPredictionResultData) sessionLimitPredictionResult() {} +func (r RawSessionLimitPredictionResultData) Kind() SessionLimitPredictionResultKind { + return r.Discriminator +} + +type SessionLimitPredictionResultAvailable struct { + // Predicted session limit details. + Prediction SessionLimitPredictionDetails `json:"prediction"` +} + +func (SessionLimitPredictionResultAvailable) sessionLimitPredictionResult() {} +func (SessionLimitPredictionResultAvailable) Kind() SessionLimitPredictionResultKind { + return SessionLimitPredictionResultKindAvailable +} + +type SessionLimitPredictionResultUnavailable struct { + // Reason no prediction is available. + Reason SessionLimitPredictionUnavailableReason `json:"reason"` +} + +func (SessionLimitPredictionResultUnavailable) sessionLimitPredictionResult() {} +func (SessionLimitPredictionResultUnavailable) Kind() SessionLimitPredictionResultKind { + return SessionLimitPredictionResultKindUnavailable +} + +// Semantic usage tier and its AI-credit cap. +// Experimental: SessionLimitPredictionTierOption is part of an experimental API and may +// change or be removed. +type SessionLimitPredictionTierOption struct { + // AI-credit cap for this tier. + Cap float64 `json:"cap"` + Tier SessionLimitPredictionTier `json:"tier"` +} + // Optional session limits. // Experimental: SessionLimitsConfig is part of an experimental API and may change or be // removed. @@ -8142,6 +9588,29 @@ type SessionLoadDeferredRepoHooksResult struct { type SessionLspInitializeResult struct { } +// Enterprise permission policy expressed with the runtime's managed permission-rule syntax. +// Experimental: SessionManagedPermissions is part of an experimental API and may change or +// be removed. +type SessionManagedPermissions struct { + // Permission rules that allow matching operations unless another managed source, deny, or + // ask rule restricts them. + Allow []string `json:"allow,omitzero"` + // Permission rules that require explicit human approval. + Ask []string `json:"ask,omitzero"` + // Permission rules that block matching operations. Deny has highest precedence. + Deny []string `json:"deny,omitzero"` + // When set to `disable`, prevents bypass/allow-all permission modes. + DisableBypassPermissionsMode *DisableBypassPermissionsMode `json:"disableBypassPermissionsMode,omitempty"` +} + +// Managed settings an SDK host may inject at session startup. Only permissions are accepted +// in this initial contract. +// Experimental: SessionManagedSettings is part of an experimental API and may change or be +// removed. +type SessionManagedSettings struct { + Permissions *SessionManagedPermissions `json:"permissions,omitempty"` +} + // Standard MCP CallToolResult // Experimental: SessionMCPAppsCallToolResult is part of an experimental API and may change // or be removed. @@ -8162,6 +9631,11 @@ type SessionMCPDisableResult struct { type SessionMCPEnableResult struct { } +// Experimental: SessionMCPOauthAuthenticationStateChangedResult is part of an experimental +// API and may change or be removed. +type SessionMCPOauthAuthenticationStateChangedResult struct { +} + // Experimental: SessionMCPRegisterExternalClientResult is part of an experimental API and // may change or be removed. type SessionMCPRegisterExternalClientResult struct { @@ -8254,6 +9728,13 @@ type SessionModelList struct { QuotaSnapshots map[string]any `json:"quotaSnapshots,omitzero"` } +// Experimental: SessionModelListRequest is part of an experimental API and may change or be +// removed. +type SessionModelListRequest struct { + // If true, bypasses the per-session model list cache and re-fetches from CAPI. + SkipCache *bool `json:"skipCache,omitempty"` +} + // Cost-category metadata for a CAPI model. // Experimental: SessionModelPriceCategory is part of an experimental API and may change or // be removed. @@ -8280,6 +9761,14 @@ type SessionOpenOptions struct { // Experimental: AdditionalContentExclusionPolicies is part of an experimental API and may // change or be removed. AdditionalContentExclusionPolicies []SessionOpenOptionsAdditionalContentExclusionPolicy `json:"additionalContentExclusionPolicies,omitzero"` + // Additional directories the agent may access beyond the working directory. Each entry is + // granted to the session's file-access allow-list and surfaced to the model (system prompt + // context and `@`-mention completion). Absolute paths are recommended; a relative path is + // resolved against the session's working directory. Nonexistent or unresolvable entries are + // skipped with a warning. This is applied on both session creation and resume, and is not + // persisted: a resumed session that omits this option does not retain previously supplied + // directories (re-supply them, exactly as the CLI re-passes `--add-dir`). + AdditionalDirectories []string `json:"additionalDirectories,omitzero"` // Runtime context discriminator for agent filtering. AgentContext *string `json:"agentContext,omitempty"` // Whether to include instructions from every MCP server in the system prompt instead of @@ -8313,6 +9802,9 @@ type SessionOpenOptions struct { DetachedFromSpawningParentSessionID *string `json:"detachedFromSpawningParentSessionId,omitempty"` // Instruction source IDs disabled for this session. DisabledInstructionSources []string `json:"disabledInstructionSources,omitzero"` + // MCP server names disabled for this session. Disabled servers are not started or + // authenticated on create or cold resume. + DisabledMCPServers []string `json:"disabledMcpServers,omitzero"` // Skill IDs disabled for this session. DisabledSkills []string `json:"disabledSkills,omitzero"` // Experimental: enable native model citations (Anthropic models today), normalized onto the @@ -8320,6 +9812,27 @@ type SessionOpenOptions struct { // surface is experimental. // Experimental: EnableCitations is part of an experimental API and may change or be removed. EnableCitations *bool `json:"enableCitations,omitempty"` + // Opt in to capturing file changes for session rewind and session diff. Capture cannot + // reconstruct changes made before it was enabled. On create it starts capture from the + // first turn. It is also honored on resume: for a session that already has tracked prior + // turns, tracking continues automatically even if this is omitted; passing it on resume + // additionally enables tracking for an eligible session that has no prior root turn yet. + // Resuming a session whose prior root turns were never tracked has no restorable baseline, + // so tracking stays disabled for it and rewind reports file change tracking as unavailable; + // the resume itself still succeeds, so sessions that predate tracking remain loadable. The + // opt-in is only rejected when the session can never track (a subagent session, or one + // without local session storage). It is intentionally absent from the mutable options + // update because enabling it after edits have occurred would create an incomplete, + // misleading baseline. Subagents share the parent session's capture store and are not + // tracked as separate rewind points: a file a subagent writes is attributed to whichever + // root user turn was open when the capture was staged, just before the tool body ran. A + // turn cannot open while a staged capture is still in flight, so a subagent tool that + // staged under the spawning turn stays attributed to it however late the write lands, while + // a capture it stages after the user's next message belongs to that later turn. Attribution + // decides which turn's rewind point counts and file preview include that write; it does not + // narrow which rewinds revert it, because a rewind restores every capture from the selected + // turn onward, so the earlier spawning turn reverts it as well. + EnableFileChangeTracking *bool `json:"enableFileChangeTracking,omitempty"` // Opt-in: self-fetch and enforce enterprise managed settings at session bootstrap. EnableManagedSettings *bool `json:"enableManagedSettings,omitempty"` // Whether on-demand custom instruction discovery is enabled. @@ -8332,6 +9845,8 @@ type SessionOpenOptions struct { EnvValueMode *SessionOpenOptionsEnvValueMode `json:"envValueMode,omitempty"` // Override directory for session event logs. EventsLogDirectory *string `json:"eventsLogDirectory,omitempty"` + // Whether subagent callback events should be forwarded into the session event log sink. + EventsLogIncludesSubagents *bool `json:"eventsLogIncludesSubagents,omitempty"` // Built-in subagent names to exclude from this session. Excluded built-ins are hidden from // agent discovery and cannot be dispatched unless a custom agent with the same name is // available. @@ -8361,6 +9876,9 @@ type SessionOpenOptions struct { LogInteractiveShells *bool `json:"logInteractiveShells,omitempty"` // Identifier sent to LSP-style integrations. LspClientName *string `json:"lspClientName,omitempty"` + // Permissions-only enterprise policy injected by the SDK host at session create or resume. + // Composes restrictively with self-fetched and device policy and is not persisted. + ManagedSettings *SessionManagedSettings `json:"managedSettings,omitempty"` // Maximum decoded byte size of a single inline model-facing binary tool result persisted in // session events (default 10 MB). MaxInlineBinaryBytes *int64 `json:"maxInlineBinaryBytes,omitempty"` @@ -8382,7 +9900,9 @@ type SessionOpenOptions struct { // rejected. // Experimental: Providers is part of an experimental API and may change or be removed. Providers []NamedProviderConfig `json:"providers,omitzero"` - // Initial reasoning effort level. + // Initial reasoning effort level. CAPI values are model-defined and validated against the + // selected model; BYOK providers may define additional values. When omitted, no effort + // override is applied. ReasoningEffort *string `json:"reasoningEffort,omitempty"` // Initial reasoning summary mode for supported model clients. ReasoningSummary *SessionOpenOptionsReasoningSummary `json:"reasoningSummary,omitempty"` @@ -8402,9 +9922,12 @@ type SessionOpenOptions struct { SessionID *string `json:"sessionId,omitempty"` // Initial session limits. SessionLimits *SessionLimitsConfig `json:"sessionLimits,omitempty"` - // Shell init profile. + // Per-session settings for built-in shell tools. + Shell *ShellOptions `json:"shell,omitempty"` + // Use shell.initProfile instead. Shell init profile. + // Deprecated: ShellInitProfile is deprecated. ShellInitProfile *string `json:"shellInitProfile,omitempty"` - // Per-shell process flags. + // PowerShell process flags applied to built-in and user-requested shell commands. ShellProcessFlags []string `json:"shellProcessFlags,omitzero"` // Additional directories to search for skills. SkillDirectories []string `json:"skillDirectories,omitzero"` @@ -8649,11 +10172,38 @@ type SessionPlanDeleteResult struct { type SessionPlanUpdateResult struct { } -// Experimental: SessionPluginsReloadResult is part of an experimental API and may change or -// be removed. +// Experimental: SessionPluginsReloadRequest is part of an experimental API and may change +// or be removed. +type SessionPluginsReloadRequest struct { + // When true, skip repo-level hooks during the hook reload. Use before folder trust is + // confirmed; load them post-trust via `sessions.loadDeferredRepoHooks`. + DeferRepoHooks *bool `json:"deferRepoHooks,omitempty"` + // Re-run custom-agent discovery after refreshing plugins. Defaults to true. + ReloadCustomAgents *bool `json:"reloadCustomAgents,omitempty"` + // Re-discover and relaunch subprocess extensions (including plugin-shipped extensions) + // after refreshing plugins. Defaults to true. Has no effect when the session has no active + // extension controller (e.g. extensions were not requested for the session). + ReloadExtensions *bool `json:"reloadExtensions,omitempty"` + // Re-load user, plugin, and (subject to `deferRepoHooks`) repo hooks. Defaults to true. Has + // no effect when the host has not registered a hook reloader (e.g. remote sessions). + ReloadHooks *bool `json:"reloadHooks,omitempty"` + // Reload MCP server connections after refreshing plugins. Defaults to true. + ReloadMCP *bool `json:"reloadMcp,omitempty"` +} + +// Experimental: SessionPluginsReloadResult is part of an experimental API and may change or +// be removed. type SessionPluginsReloadResult struct { } +// Experimental: SessionProviderGetEndpointRequest is part of an experimental API and may +// change or be removed. +type SessionProviderGetEndpointRequest struct { + // Model identifier the caller intends to use against the returned endpoint. Used to pick + // the correct wire shape. Omit to use whichever model the session is currently using. + ModelID *string `json:"modelId,omitempty"` +} + // Outcome of the prune operation: deleted IDs, dry-run candidates, skipped IDs, total bytes // freed, and the dry-run flag. // Experimental: SessionPruneResult is part of an experimental API and may change or be @@ -8676,6 +10226,21 @@ type SessionPruneResult struct { type SessionQueueClearResult struct { } +// Experimental: SessionQueueDeferSessionIdleResult is part of an experimental API and may +// change or be removed. +type SessionQueueDeferSessionIdleResult struct { +} + +// Experimental: SessionQueueProcessResult is part of an experimental API and may change or +// be removed. +type SessionQueueProcessResult struct { +} + +// Experimental: SessionQueueSetDrainPausedResult is part of an experimental API and may +// change or be removed. +type SessionQueueSetDrainPausedResult struct { +} + // Experimental: SessionRemoteDisableResult is part of an experimental API and may change or // be removed. type SessionRemoteDisableResult struct { @@ -8706,6 +10271,11 @@ type SessionsCheckInUseResult struct { InUse []string `json:"inUse"` } +// Experimental: SessionScheduleHydrateResult is part of an experimental API and may change +// or be removed. +type SessionScheduleHydrateResult struct { +} + // Session ID to close. // Experimental: SessionsCloseRequest is part of an experimental API and may change or be // removed. @@ -8727,6 +10297,26 @@ type SessionsCloseResult struct { type SessionsConfigureSessionExtensionsResult struct { } +// Session ID to delete from disk. +// Experimental: SessionsDeleteRequest is part of an experimental API and may change or be +// removed. +type SessionsDeleteRequest struct { + // Session ID to delete + SessionID string `json:"sessionId"` + // Internal resolved session directory path to delete + SessionPath *string `json:"sessionPath,omitempty"` +} + +// Experimental: SessionsDeleteResult is part of an experimental API and may change or be +// removed. +type SessionsDeleteResult struct { +} + +// Experimental: SessionSendSystemNotificationResult is part of an experimental API and may +// change or be removed. +type SessionSendSystemNotificationResult struct { +} + // Session metadata records to enrich with summary and context information. // Experimental: SessionsEnrichMetadataRequest is part of an experimental API and may change // or be removed. @@ -8976,6 +10566,22 @@ type SessionsGetLastForContextResult struct { SessionID *string `json:"sessionId,omitempty"` } +// Session ID whose persisted metadata should be read. +// Experimental: SessionsGetMetadataRequest is part of an experimental API and may change or +// be removed. +type SessionsGetMetadataRequest struct { + // Session ID to inspect + SessionID string `json:"sessionId"` +} + +// Persisted local session metadata when the session exists. +// Experimental: SessionsGetMetadataResult is part of an experimental API and may change or +// be removed. +type SessionsGetMetadataResult struct { + // Local session metadata, omitted when the session does not exist. + Session *LocalSessionMetadataValue `json:"session,omitempty"` +} + // Session ID to look up the persisted remote-steerable flag for. // Experimental: SessionsGetPersistedRemoteSteerableRequest is part of an experimental API // and may change or be removed. @@ -9021,6 +10627,22 @@ type SessionSkillsEnableResult struct { type SessionSkillsEnsureLoadedResult struct { } +// Limit for non-empty local session IDs. +// Experimental: SessionsListNonEmptySessionIDsRequest is part of an experimental API and +// may change or be removed. +type SessionsListNonEmptySessionIDsRequest struct { + // Maximum number of session IDs to return. + Limit *int64 `json:"limit,omitempty"` +} + +// Recent local session IDs that contain user-visible history. +// Experimental: SessionsListNonEmptySessionIDsResult is part of an experimental API and may +// change or be removed. +type SessionsListNonEmptySessionIDsResult struct { + // Session IDs ordered newest-first. + SessionIDs []string `json:"sessionIds"` +} + // Optional source filter, metadata-load limit, and context filter applied to the returned // sessions. // Experimental: SessionsListRequest is part of an experimental API and may change or be @@ -9259,7 +10881,7 @@ type SessionUpdateOptionsParams struct { EnableHostGitOperations *bool `json:"enableHostGitOperations,omitempty"` // Whether to discover custom instructions on demand after successful file views (AGENTS.md // / CLAUDE.md / .github/copilot-instructions.md surfacing). Combined with - // `skipCustomInstructions` and the runtime-side `ON_DEMAND_INSTRUCTIONS` feature flag. + // `skipCustomInstructions`. EnableOnDemandInstructionDiscovery *bool `json:"enableOnDemandInstructionDiscovery,omitempty"` // Whether to surface reasoning-summary events from the model. EnableReasoningSummaries *bool `json:"enableReasoningSummaries,omitempty"` @@ -9278,6 +10900,8 @@ type SessionUpdateOptionsParams struct { // Override directory for the session-events log. When unset, the runtime's default events // log directory is used. EventsLogDirectory *string `json:"eventsLogDirectory,omitempty"` + // Whether subagent callback events should be forwarded into the session event log sink. + EventsLogIncludesSubagents *bool `json:"eventsLogIncludesSubagents,omitempty"` // Built-in subagent names to exclude from this session. Excluded built-ins are hidden from // agent discovery and cannot be dispatched unless a custom agent with the same name is // available. @@ -9318,7 +10942,9 @@ type SessionUpdateOptionsParams struct { OrganizationCustomInstructions *string `json:"organizationCustomInstructions,omitempty"` // Custom model-provider configuration (BYOK). Provider *ProviderConfig `json:"provider,omitempty"` - // Reasoning effort for the selected model (model-defined enum). + // Reasoning effort for the selected model. CAPI values are model-defined and validated + // against the selected model; BYOK providers may define additional values. When omitted, no + // effort override is applied. ReasoningEffort *string `json:"reasoningEffort,omitempty"` // Reasoning summary mode for supported model clients. ReasoningSummary *OptionsUpdateReasoningSummary `json:"reasoningSummary,omitempty"` @@ -9332,9 +10958,12 @@ type SessionUpdateOptionsParams struct { SessionCapabilities []SessionCapability `json:"sessionCapabilities,omitzero"` // Optional session limits. Pass null to clear the session limits. SessionLimits *SessionLimitsConfig `json:"sessionLimits,omitempty"` - // Shell init profile (`None` or `NonInteractive`). + // Per-session settings for built-in shell tools. + Shell *ShellOptions `json:"shell,omitempty"` + // Use shell.initProfile instead. Shell init profile (`None` or `NonInteractive`). + // Deprecated: ShellInitProfile is deprecated. ShellInitProfile *string `json:"shellInitProfile,omitempty"` - // Per-shell process flags (e.g., `pwsh` arguments). + // PowerShell process flags applied to built-in and user-requested shell commands. ShellProcessFlags []string `json:"shellProcessFlags,omitzero"` // Additional directories to search for skills. SkillDirectories []string `json:"skillDirectories,omitzero"` @@ -9434,6 +11063,16 @@ type ShellExecuteUserRequestedRequest struct { RequestID string `json:"requestId"` } +// A host-provided script sourced before each built-in shell command when its shell target +// matches the active shell. +// Experimental: ShellInitScript is part of an experimental API and may change or be removed. +type ShellInitScript struct { + // Path to the script to source. + Path string `json:"path"` + // Built-in shell that may source this script. + Shell ShellInitScriptShell `json:"shell"` +} + // Identifier of a process previously returned by "shell.exec" and the signal to send. // Experimental: ShellKillRequest is part of an experimental API and may change or be // removed. @@ -9452,6 +11091,34 @@ type ShellKillResult struct { Killed bool `json:"killed"` } +// Per-session settings for built-in shell tools. +// Experimental: ShellOptions is part of an experimental API and may change or be removed. +type ShellOptions struct { + // Controls automatic non-interactive profile loading where supported. Explicit initScripts + // are unaffected. + InitProfile *ShellInitProfile `json:"initProfile,omitempty"` + // Ordered host-provided script paths sourced before each built-in shell command when the + // entry's shell target matches the active shell. Use these for rc files, environment setup + // scripts, + // or other custom scripts. A script that returns a nonzero status is reported, and later + // scripts + // and the user command continue while the shell remains running. Because scripts are + // sourced into + // the command shell, `exit`, `exec`, failures under `set -e`, or other shell-terminating + // behavior + // can prevent continuation. Script standard output is preserved; Bash script stderr is + // discarded, + // PowerShell exception messages are replaced, and runtime-generated failure notices omit + // configured script paths. When sandboxing is enabled, each script must already be readable + // under + // the active sandbox filesystem policy. Pass an empty array to clear the list. + InitScripts []ShellInitScript `json:"initScripts,omitzero"` + // Flags passed to the active built-in shell process on startup, replacing its default flags. + // When omitted, the built-in Bash shell uses `--norc --noprofile`, + // and the built-in PowerShell shell uses `-NoProfile -NoLogo`. + ProcessFlags []string `json:"processFlags,omitzero"` +} + // Parameters for shutting down the session // Experimental: ShutdownRequest is part of an experimental API and may change or be removed. type ShutdownRequest struct { @@ -9469,6 +11136,8 @@ type Skill struct { // Optional freeform hint describing the skill's expected arguments, from the // `argument-hint` frontmatter field ArgumentHint *string `json:"argumentHint,omitempty"` + // Canonical slash command name used to invoke the skill, without the leading '/' + CommandName *string `json:"commandName,omitempty"` // Description of what the skill does Description string `json:"description"` // Whether the skill is currently enabled @@ -10497,6 +12166,11 @@ type UIExitPlanModeResponse struct { Approved bool `json:"approved"` // Whether subsequent edits should be auto-approved without confirmation. AutoApproveEdits *bool `json:"autoApproveEdits,omitempty"` + // When true, the agent is instructed to end its turn without starting implementation so the + // client can restore the session model and auto-submit a fresh implementation turn on it. + // Set only when a distinct plan configuration (a different model, reasoning effort, or + // context tier) actually ran the planning turn. + DeferImplementation *bool `json:"deferImplementation,omitempty"` // Feedback from the user when they declined the plan or requested changes. Feedback *string `json:"feedback,omitempty"` // The action the user selected. Defaults to 'autopilot' when autoApproveEdits is true, @@ -10894,6 +12568,19 @@ func (UserToolSessionApprovalExtensionPermissionAccess) Kind() UserToolSessionAp return UserToolSessionApprovalKindExtensionPermissionAccess } +// Session-scoped factory approval, optionally narrowed by approval key. +// Experimental: UserToolSessionApprovalFactory is part of an experimental API and may +// change or be removed. +type UserToolSessionApprovalFactory struct { + // Optional factory operation name or canonical approval key + ApprovalKey *string `json:"approvalKey,omitempty"` +} + +func (UserToolSessionApprovalFactory) userToolSessionApproval() {} +func (UserToolSessionApprovalFactory) Kind() UserToolSessionApprovalKind { + return UserToolSessionApprovalKindFactory +} + // Session-scoped tool-approval rule for an MCP server tool, or all tools on the server when // `toolName` is null. // Experimental: UserToolSessionApprovalMCP is part of an experimental API and may change or @@ -10995,7 +12682,9 @@ type WorkspaceDiffFileChange struct { IsTruncated *bool `json:"isTruncated,omitempty"` // Original file path for renamed files. OldPath *string `json:"oldPath,omitempty"` - // Path to the changed file, relative to the workspace root. + // Path to the changed file, relative to the workspace root when the file lives under it. A + // file changed outside the workspace root keeps a `../`-relative path, or an absolute path + // when no relative path exists (for example a different Windows drive). Path string `json:"path"` } @@ -11007,12 +12696,47 @@ type WorkspaceDiffResult struct { BaseBranch *string `json:"baseBranch,omitempty"` // Changed files and their unified diffs. Changes []WorkspaceDiffFileChange `json:"changes"` - // Whether a requested branch diff fell back to unstaged changes because branch diff failed. + // Whether the requested diff fell back to unstaged changes, either because branch diff + // failed or session diff was unavailable. IsFallback bool `json:"isFallback"` // Effective mode used for the returned changes. Mode WorkspaceDiffMode `json:"mode"` // Diff mode requested by the client. RequestedMode WorkspaceDiffMode `json:"requestedMode"` + // Why the session diff could not be produced, when applicable. Set only when `session` mode + // was requested and `isFallback` is true, so a client can tell the permanent + // `file-change-tracking-disabled` apart from the transient `session-busy`, which the same + // request answers once the session settles. Never set for `unstaged` or `branch` mode, and + // never `unsupported-remote-session`: a remote session's captures live on its own host, so + // a `session`-mode diff is rejected for one rather than answered with a controller-side + // fallback. + UnavailableReason *HistoryRewindUnavailableReason `json:"unavailableReason,omitempty"` +} + +// Compaction summary checkpoint to persist. +// Experimental: WorkspacesAddSummaryRequest is part of an experimental API and may change +// or be removed. +type WorkspacesAddSummaryRequest struct { + // Markdown summary content to persist. + Content string `json:"content"` + // Summary title shown in checkpoint listings. + Title string `json:"title"` +} + +// Persisted summary metadata and refreshed workspace metadata. +// Experimental: WorkspacesAddSummaryResult is part of an experimental API and may change or +// be removed. +type WorkspacesAddSummaryResult struct { + Summary any `json:"summary,omitempty"` + Workspace any `json:"workspace,omitempty"` +} + +// Whether the autopilot objective file exists. +// Experimental: WorkspacesAutopilotObjectiveExistsResult is part of an experimental API and +// may change or be removed. +type WorkspacesAutopilotObjectiveExistsResult struct { + // True when the objective file exists. + Exists bool `json:"exists"` } // Workspace checkpoint metadata with assigned number, human-readable title, and checkpoint @@ -11038,6 +12762,14 @@ type WorkspacesCreateFileRequest struct { Path string `json:"path"` } +// Result of deleting the autopilot objective file. +// Experimental: WorkspacesDeleteAutopilotObjectiveResult is part of an experimental API and +// may change or be removed. +type WorkspacesDeleteAutopilotObjectiveResult struct { + // True when a file was deleted. + Deleted bool `json:"deleted"` +} + // Parameters for computing a workspace diff. // Experimental: WorkspacesDiffRequest is part of an experimental API and may change or be // removed. @@ -11048,6 +12780,14 @@ type WorkspacesDiffRequest struct { Mode WorkspaceDiffMode `json:"mode"` } +// Optional session context used when creating a local workspace. +// Experimental: WorkspacesEnsureRequest is part of an experimental API and may change or be +// removed. +type WorkspacesEnsureRequest struct { + // Opaque workspace context supplied by the session host. + Context any `json:"context,omitempty"` +} + // Current workspace metadata for the session, including its absolute filesystem path when // available. // Experimental: WorkspacesGetWorkspaceResult is part of an experimental API and may change @@ -11097,6 +12837,14 @@ type WorkspacesListFilesResult struct { Files []string `json:"files"` } +// Autopilot objective file content, or null when missing. +// Experimental: WorkspacesReadAutopilotObjectiveResult is part of an experimental API and +// may change or be removed. +type WorkspacesReadAutopilotObjectiveResult struct { + // Autopilot objective file content, or null when missing. + Content *string `json:"content"` +} + // Checkpoint number to read. // Experimental: WorkspacesReadCheckpointRequest is part of an experimental API and may // change or be removed. @@ -11155,6 +12903,14 @@ type WorkspacesSaveLargePasteResultSaved struct { SizeBytes int64 `json:"sizeBytes"` } +// Rollback point for local workspace summaries. +// Experimental: WorkspacesTruncateSummariesRequest is part of an experimental API and may +// change or be removed. +type WorkspacesTruncateSummariesRequest struct { + // Number of newest summaries to keep. + KeepCount int64 `json:"keepCount"` +} + // Public-facing projection of workspace metadata for SDK / TUI consumers // Experimental: WorkspaceSummary is part of an experimental API and may change or be // removed. @@ -11181,11 +12937,40 @@ type WorkspaceSummary struct { UserNamed *bool `json:"user_named,omitempty"` } +// Workspace metadata fields to update. +// Experimental: WorkspacesUpdateMetadataRequest is part of an experimental API and may +// change or be removed. +type WorkspacesUpdateMetadataRequest struct { + // Opaque workspace context supplied by the session host. + Context any `json:"context,omitempty"` + // Optional workspace display name override. + Name *string `json:"name,omitempty"` +} + +// Autopilot objective file content to persist. +// Experimental: WorkspacesWriteAutopilotObjectiveRequest is part of an experimental API and +// may change or be removed. +type WorkspacesWriteAutopilotObjectiveRequest struct { + // Autopilot objective file content. + Content string `json:"content"` +} + +// Result of writing the autopilot objective file. +// Experimental: WorkspacesWriteAutopilotObjectiveResult is part of an experimental API and +// may change or be removed. +type WorkspacesWriteAutopilotObjectiveResult struct { + // Filesystem operation performed. + Operation string `json:"operation"` +} + // Finite reason code describing why the current turn was aborted // Experimental: AbortReason is part of an experimental API and may change or be removed. type AbortReason string const ( + // Autopilot stopped the run because the active objective reached its user-set + // --max-ai-credits limit. + AbortReasonAutopilotCreditLimit AbortReason = "autopilot_credit_limit" // A remote command requested the abort. AbortReasonRemoteCommand AbortReason = "remote_command" // An MCP server delivered a user.abort notification. @@ -11533,6 +13318,40 @@ const ( DebugCollectLogsSourceShellLog DebugCollectLogsSource = "shell-log" ) +// Experimental: DisableBypassPermissionsMode is part of an experimental API and may change +// or be removed. +type DisableBypassPermissionsMode string + +const ( + DisableBypassPermissionsModeDisable DisableBypassPermissionsMode = "disable" +) + +// Effective extension loading and agent-management mode +// Experimental: DiscoveredExtensionMode is part of an experimental API and may change or be +// removed. +type DiscoveredExtensionMode string + +const ( + // Extensions are not loaded. + DiscoveredExtensionModeDisabled DiscoveredExtensionMode = "disabled" + // Extensions are loaded and the agent can create, reload, and manage them. + DiscoveredExtensionModeLoadAndAugment DiscoveredExtensionMode = "load_and_augment" + // Extensions are loaded, but the agent cannot create, reload, or manage them. + DiscoveredExtensionModeLoadOnly DiscoveredExtensionMode = "load_only" +) + +// Persisted extension discovery source +// Experimental: DiscoveredExtensionSource is part of an experimental API and may change or +// be removed. +type DiscoveredExtensionSource string + +const ( + // Extension contributed by an installed plugin. + DiscoveredExtensionSourcePlugin DiscoveredExtensionSource = "plugin" + // Extension discovered from the user's extensions directory. + DiscoveredExtensionSourceUser DiscoveredExtensionSource = "user" +) + // Server transport type: stdio, http, sse (deprecated), or memory // Experimental: DiscoveredMCPServerType is part of an experimental API and may change or be // removed. @@ -11572,7 +13391,11 @@ const ( // Cursor status: 'ok' means the cursor was applied successfully; 'expired' means the cursor // referred to an event that no longer exists in history (e.g. truncated or compacted away) -// and the read started from the beginning of the remaining history. +// and the read fell back to a boundary of the remaining history (the beginning for a +// forward read, the tail for a backward read). The fallback page is a fresh boundary +// snapshot, not a continuation of the requested cursor, so it may overlap already-rendered +// events; on 'expired' a consumer should reset/rebase its pagination state (or deduplicate +// by event id) before continuing from the returned cursor. // Experimental: EventsCursorStatus is part of an experimental API and may change or be // removed. type EventsCursorStatus string @@ -11584,6 +13407,21 @@ const ( EventsCursorStatusOk EventsCursorStatus = "ok" ) +// Direction to page through the session's persisted event history. 'forward' pages from the +// cursor toward newer events; 'backward' returns the newest window first (tail-first) and +// pages toward older events. Events within a returned batch are always chronological +// (oldest-to-newest), even for a backward read. +// Experimental: EventsReadDirection is part of an experimental API and may change or be +// removed. +type EventsReadDirection string + +const ( + // Tail-first: return the newest events and page toward older events. + EventsReadDirectionBackward EventsReadDirection = "backward" + // Page from the cursor toward newer events (default). + EventsReadDirectionForward EventsReadDirection = "forward" +) + // Discovery source: project (.github/extensions/), user (~/.copilot/extensions/), plugin // (installed plugin), or session (session-state//extensions/) // Experimental: ExtensionSource is part of an experimental API and may change or be removed. @@ -11654,6 +13492,34 @@ const ( ExternalToolTextResultForLlmContentTypeText ExternalToolTextResultForLlmContentType = "text" ) +// Execution-critical factory storage operation. +// Experimental: FactoryDurableOperation is part of an experimental API and may change or be +// removed. +type FactoryDurableOperation string + +const ( + // Persisting active execution time. + FactoryDurableOperationAddElapsed FactoryDurableOperation = "addElapsed" + // Persisting an idempotent model-usage charge. + FactoryDurableOperationChargeCredit FactoryDurableOperation = "chargeCredit" + // Creating the durable run and declared phases. + FactoryDurableOperationCreateRun FactoryDurableOperation = "createRun" + // Persisting the terminal run envelope. + FactoryDurableOperationFinishRun FactoryDurableOperation = "finishRun" + // Reading a journal entry without treating storage failure as a cache miss. + FactoryDurableOperationJournalGet FactoryDurableOperation = "journalGet" + // Persisting a journal entry before reporting success. + FactoryDurableOperationJournalPut FactoryDurableOperation = "journalPut" + // Persisting the transition to running. + FactoryDurableOperationMarkRunStarted FactoryDurableOperation = "markRunStarted" + // Reading the authoritative AI-credit total. + FactoryDurableOperationReconcileCreditTotal FactoryDurableOperation = "reconcileCreditTotal" + // Rolling back an uncommitted subagent admission. + FactoryDurableOperationReleaseAgent FactoryDurableOperation = "releaseAgent" + // Persisting subagent admission accounting. + FactoryDurableOperationReserveAgent FactoryDurableOperation = "reserveAgent" +) + // Kind of factory progress line. // Experimental: FactoryLogLineKind is part of an experimental API and may change or be // removed. @@ -11666,24 +13532,46 @@ const ( FactoryLogLineKindPhase FactoryLogLineKind = "phase" ) +// Derived lifecycle state of a factory phase. +// Experimental: FactoryPhaseStatus is part of an experimental API and may change or be +// removed. +type FactoryPhaseStatus string + +const ( + // The phase is currently entered and accumulating active time. + FactoryPhaseStatusActive FactoryPhaseStatus = "active" + // The phase was entered and has since been closed. + FactoryPhaseStatusCompleted FactoryPhaseStatus = "completed" + // The phase has not been entered yet. + FactoryPhaseStatusPending FactoryPhaseStatus = "pending" + // The phase was never entered because a later phase was entered or the run reached a + // terminal state. + FactoryPhaseStatusSkipped FactoryPhaseStatus = "skipped" +) + // Cumulative resource ceiling that stopped a factory run. // Experimental: FactoryRunFailureKind is part of an experimental API and may change or be // removed. type FactoryRunFailureKind string const ( + // The run's settled subagent model usage exceeded the approved AI-credit ceiling, or no + // headroom remained for another subagent. + FactoryRunFailureKindMaxAiCredits FactoryRunFailureKind = "maxAiCredits" // The run admitted the approved maximum total number of subagents. FactoryRunFailureKindMaxTotalSubagents FactoryRunFailureKind = "maxTotalSubagents" - // The run reached the approved timeout deadline. - FactoryRunFailureKindTimeout FactoryRunFailureKind = "timeout" + // The run reached the approved accumulated active-execution time in seconds. + FactoryRunFailureKindTimeoutSeconds FactoryRunFailureKind = "timeoutSeconds" ) // Type discriminator for FactoryRunFailure. type FactoryRunFailureType string const ( - FactoryRunFailureTypeFactoryLimitReached FactoryRunFailureType = "factory_limit_reached" - FactoryRunFailureTypeFactoryResumeDeclined FactoryRunFailureType = "factory_resume_declined" + FactoryRunFailureTypeFactoryAccountingIncomplete FactoryRunFailureType = "factory_accounting_incomplete" + FactoryRunFailureTypeFactoryDurableFailure FactoryRunFailureType = "factory_durable_failure" + FactoryRunFailureTypeFactoryLimitReached FactoryRunFailureType = "factory_limit_reached" + FactoryRunFailureTypeFactoryResumeDeclined FactoryRunFailureType = "factory_resume_declined" ) // Current or terminal state of a factory run. @@ -11706,6 +13594,107 @@ const ( FactoryRunStatusRunning FactoryRunStatus = "running" ) +// What initiated this compaction request, recorded as the `trigger` on the persisted +// `session.compaction_start` / `session.compaction_complete` events. When absent, the +// compaction is persisted without trigger attribution (initiator unknown). +type HistoryCompactRequestTrigger string + +const ( + // User-requested compaction, e.g. the /compact command or a direct history.compact call. + HistoryCompactRequestTriggerManual HistoryCompactRequestTrigger = "manual" + // Compaction requested while switching to a model with a smaller context window. + HistoryCompactRequestTriggerModelSwitch HistoryCompactRequestTrigger = "model_switch" +) + +// Reason a captured file was not restored. +// Experimental: HistoryFileRestoreSkipReason is part of an experimental API and may change +// or be removed. +type HistoryFileRestoreSkipReason string + +const ( + // A faithful preimage was not captured. + HistoryFileRestoreSkipReasonSkippedCapture HistoryFileRestoreSkipReason = "skipped-capture" + // The file changed after Copilot's last captured write. + HistoryFileRestoreSkipReasonUserModified HistoryFileRestoreSkipReason = "user-modified" +) + +// Aggregate file change represented by a rewind preview. +// Experimental: HistoryRewindChangeType is part of an experimental API and may change or be +// removed. +type HistoryRewindChangeType string + +const ( + // The discarded turns created the file. + HistoryRewindChangeTypeCreated HistoryRewindChangeType = "created" + // The discarded turns deleted the file. + HistoryRewindChangeTypeDeleted HistoryRewindChangeType = "deleted" + // The discarded turns modified the file. + HistoryRewindChangeTypeModified HistoryRewindChangeType = "modified" +) + +// Scope of a rewind operation. +// Experimental: HistoryRewindMode is part of an experimental API and may change or be +// removed. +type HistoryRewindMode string + +const ( + // Discard conversation events while leaving files unchanged. + HistoryRewindModeConversation HistoryRewindMode = "conversation" + // Discard conversation events and restore captured files changed by those turns. + HistoryRewindModeConversationAndFiles HistoryRewindMode = "conversation-and-files" +) + +// Outcome of a rewind request. +// Experimental: HistoryRewindOutcome is part of an experimental API and may change or be +// removed. +type HistoryRewindOutcome string + +const ( + // The conversation was rewound (and, in conversation-and-files mode, captured files were + // restored), but persisted checkpoints could not be cleaned up; reachable in either mode. + HistoryRewindOutcomeCheckpointCleanupFailed HistoryRewindOutcome = "checkpoint-cleanup-failed" + // A conversation-and-files rewind was requested for a session that did not enable capture; + // conversation-only rewinds never produce this. + HistoryRewindOutcomeFileChangeTrackingDisabled HistoryRewindOutcome = "file-change-tracking-disabled" + // File restore failed and all applied file changes were rolled back; only + // conversation-and-files rewinds produce this. + HistoryRewindOutcomeFilesRolledBack HistoryRewindOutcome = "files-rolled-back" + // File restore failed and its rollback could not fully restore the pre-rewind state; only + // conversation-and-files rewinds produce this. + HistoryRewindOutcomeRollbackIncomplete HistoryRewindOutcome = "rollback-incomplete" + // The session still has work that may mutate files or history; reachable in either mode. + HistoryRewindOutcomeSessionBusy HistoryRewindOutcome = "session-busy" + // Files and conversation were rewound, but obsolete file snapshots could not be removed; + // only conversation-and-files rewinds produce this. + HistoryRewindOutcomeSnapshotPruneFailed HistoryRewindOutcome = "snapshot-prune-failed" + // The requested rewind completed; reachable in either mode. + HistoryRewindOutcomeSuccess HistoryRewindOutcome = "success" + // Conversation truncation failed. In conversation-and-files mode any files that were + // restored are left in place because conversation history cannot be un-truncated; in + // conversation-only mode no files are restored. Consult restoredFiles for what, if + // anything, was applied. + HistoryRewindOutcomeTruncationFailed HistoryRewindOutcome = "truncation-failed" + // Remote-backed rewind routing is not supported; reachable in either mode. + HistoryRewindOutcomeUnsupportedRemoteSession HistoryRewindOutcome = "unsupported-remote-session" +) + +// Reason a rewind read (rewind points, file-restore preview, or session diff) could not be +// answered from the session's file-change captures. +// Experimental: HistoryRewindUnavailableReason is part of an experimental API and may +// change or be removed. +type HistoryRewindUnavailableReason string + +const ( + // The session did not opt into file-change tracking before its first turn. + HistoryRewindUnavailableReasonFileChangeTrackingDisabled HistoryRewindUnavailableReason = "file-change-tracking-disabled" + // The session still has work that may mutate files or history. Transient: the same request + // succeeds once the session settles, so callers should retry rather than treat it as a + // failure. + HistoryRewindUnavailableReasonSessionBusy HistoryRewindUnavailableReason = "session-busy" + // Remote-backed rewind routing is not supported. + HistoryRewindUnavailableReasonUnsupportedRemoteSession HistoryRewindUnavailableReason = "unsupported-remote-session" +) + // Authentication host. HMAC auth always targets the public GitHub host. type HMACAuthInfoHost string @@ -12067,7 +14056,8 @@ const ( MCPServerSourceWorkspace MCPServerSource = "workspace" ) -// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured +// Connection status: connected, failed, needs-auth, pending, disabled, stopped, or +// not_configured // Experimental: MCPServerStatus is part of an experimental API and may change or be removed. type MCPServerStatus string @@ -12084,6 +14074,10 @@ const ( MCPServerStatusNotConfigured MCPServerStatus = "not_configured" // The server connection is still being established. MCPServerStatusPending MCPServerStatus = "pending" + // The server was intentionally stopped and can be restarted on demand when policy permits; + // a server quarantined by restrictive managed policy stays stopped and cannot be restarted + // until the policy allows it. + MCPServerStatusStopped MCPServerStatus = "stopped" ) // How environment-variable values supplied to MCP servers are resolved. "direct" passes @@ -12276,6 +14270,7 @@ const ( PermissionDecisionApproveForLocationApprovalKindCustomTool PermissionDecisionApproveForLocationApprovalKind = "custom-tool" PermissionDecisionApproveForLocationApprovalKindExtensionManagement PermissionDecisionApproveForLocationApprovalKind = "extension-management" PermissionDecisionApproveForLocationApprovalKindExtensionPermissionAccess PermissionDecisionApproveForLocationApprovalKind = "extension-permission-access" + PermissionDecisionApproveForLocationApprovalKindFactory PermissionDecisionApproveForLocationApprovalKind = "factory" PermissionDecisionApproveForLocationApprovalKindMCP PermissionDecisionApproveForLocationApprovalKind = "mcp" PermissionDecisionApproveForLocationApprovalKindMCPSampling PermissionDecisionApproveForLocationApprovalKind = "mcp-sampling" PermissionDecisionApproveForLocationApprovalKindMemory PermissionDecisionApproveForLocationApprovalKind = "memory" @@ -12291,6 +14286,7 @@ const ( PermissionDecisionApproveForSessionApprovalKindCustomTool PermissionDecisionApproveForSessionApprovalKind = "custom-tool" PermissionDecisionApproveForSessionApprovalKindExtensionManagement PermissionDecisionApproveForSessionApprovalKind = "extension-management" PermissionDecisionApproveForSessionApprovalKindExtensionPermissionAccess PermissionDecisionApproveForSessionApprovalKind = "extension-permission-access" + PermissionDecisionApproveForSessionApprovalKindFactory PermissionDecisionApproveForSessionApprovalKind = "factory" PermissionDecisionApproveForSessionApprovalKindMCP PermissionDecisionApproveForSessionApprovalKind = "mcp" PermissionDecisionApproveForSessionApprovalKindMCPSampling PermissionDecisionApproveForSessionApprovalKind = "mcp-sampling" PermissionDecisionApproveForSessionApprovalKindMemory PermissionDecisionApproveForSessionApprovalKind = "memory" @@ -12319,6 +14315,53 @@ const ( PermissionDecisionKindUserNotAvailable PermissionDecisionKind = "user-not-available" ) +// Disposition of a permission request as observed by the responding client. +// Experimental: PermissionDecisionOutcome is part of an experimental API and may change or +// be removed. +type PermissionDecisionOutcome string + +const ( + // The request was approved automatically without a new human decision. + PermissionDecisionOutcomeAutoApproved PermissionDecisionOutcome = "auto_approved" + // The request was denied without an interactive user decision; source records why. + PermissionDecisionOutcomeAutopilotDenied PermissionDecisionOutcome = "autopilot_denied" + // The response came from an interactive user prompt. + PermissionDecisionOutcomePromptedUser PermissionDecisionOutcome = "prompted_user" +) + +// Controlled reason or actor responsible for a permission response. +// Experimental: PermissionDecisionSource is part of an experimental API and may change or +// be removed. +type PermissionDecisionSource string + +const ( + // The host applied a standing policy or override rather than a judge recommendation or + // human decision. + PermissionDecisionSourceHostPolicy PermissionDecisionSource = "host_policy" + // A human supplied the response through an interactive prompt. + PermissionDecisionSourceHumanResponse PermissionDecisionSource = "human_response" + // The response followed the auto-approval judge recommendation. + PermissionDecisionSourceJudgeRecommendation PermissionDecisionSource = "judge_recommendation" + // The host denied the request because no interactive user response was available. + PermissionDecisionSourceUnattendedFallback PermissionDecisionSource = "unattended_fallback" +) + +// Client surface that submitted a permission response. +// Experimental: PermissionDecisionSurface is part of an experimental API and may change or +// be removed. +type PermissionDecisionSurface string + +const ( + // The Copilot App client. + PermissionDecisionSurfaceCopilotApp PermissionDecisionSurface = "copilot_app" + // The non-interactive Copilot CLI prompt mode. + PermissionDecisionSurfacePromptMode PermissionDecisionSurface = "prompt_mode" + // A generic Copilot SDK client. + PermissionDecisionSurfaceSDK PermissionDecisionSurface = "sdk" + // The interactive Copilot CLI terminal UI. + PermissionDecisionSurfaceTui PermissionDecisionSurface = "tui" +) + // Whether the location is a git repo or directory // Experimental: PermissionLocationType is part of an experimental API and may change or be // removed. @@ -12367,6 +14410,7 @@ const ( PermissionsLocationsAddToolApprovalDetailsKindCustomTool PermissionsLocationsAddToolApprovalDetailsKind = "custom-tool" PermissionsLocationsAddToolApprovalDetailsKindExtensionManagement PermissionsLocationsAddToolApprovalDetailsKind = "extension-management" PermissionsLocationsAddToolApprovalDetailsKindExtensionPermissionAccess PermissionsLocationsAddToolApprovalDetailsKind = "extension-permission-access" + PermissionsLocationsAddToolApprovalDetailsKindFactory PermissionsLocationsAddToolApprovalDetailsKind = "factory" PermissionsLocationsAddToolApprovalDetailsKindMCP PermissionsLocationsAddToolApprovalDetailsKind = "mcp" PermissionsLocationsAddToolApprovalDetailsKindMCPSampling PermissionsLocationsAddToolApprovalDetailsKind = "mcp-sampling" PermissionsLocationsAddToolApprovalDetailsKindMemory PermissionsLocationsAddToolApprovalDetailsKind = "memory" @@ -12714,6 +14758,35 @@ const ( SessionFSSqliteQueryTypeRun SessionFSSqliteQueryType = "run" ) +// SQLite transaction failure classification. +// Experimental: SessionFSSqliteTransactionErrorClass is part of an experimental API and may +// change or be removed. +type SessionFSSqliteTransactionErrorClass string + +const ( + // SQLite reported BUSY or LOCKED before commit; the transaction was rolled back and may be + // retried. + SessionFSSqliteTransactionErrorClassBusyOrLocked SessionFSSqliteTransactionErrorClass = "busyOrLocked" + // The statement, database, or provider failed definitively and must not be retried + // automatically. + SessionFSSqliteTransactionErrorClassFatal SessionFSSqliteTransactionErrorClass = "fatal" + // The transport failed after the provider may have committed; retrying could duplicate + // effects. + SessionFSSqliteTransactionErrorClassPostCommitAmbiguous SessionFSSqliteTransactionErrorClass = "postCommitAmbiguous" +) + +// What initiated this compaction request, recorded as the `trigger` on the persisted +// `session.compaction_start` / `session.compaction_complete` events. When absent, the +// compaction is persisted without trigger attribution (initiator unknown). +type SessionHistoryCompactRequestTrigger string + +const ( + // User-requested compaction, e.g. the /compact command or a direct history.compact call. + SessionHistoryCompactRequestTriggerManual SessionHistoryCompactRequestTrigger = "manual" + // Compaction requested while switching to a model with a smaller context window. + SessionHistoryCompactRequestTriggerModelSwitch SessionHistoryCompactRequestTrigger = "model_switch" +) + // Constant value. Always "github". type SessionInstalledPluginSourceGitHubSource string @@ -12735,6 +14808,69 @@ const ( SessionInstalledPluginSourceURLSourceURL SessionInstalledPluginSourceURLSource = "url" ) +// Client population used for the prediction baseline. +// Experimental: SessionLimitPredictionClientType is part of an experimental API and may +// change or be removed. +type SessionLimitPredictionClientType string + +const ( + // Interactive CLI sessions where a user can accept, edit, or top up the limit. + SessionLimitPredictionClientTypeCLIInteractive SessionLimitPredictionClientType = "cli-interactive" + // Prompt/non-interactive CLI sessions where the initial limit must cover more of the run. + SessionLimitPredictionClientTypeCLIPrompt SessionLimitPredictionClientType = "cli-prompt" +) + +// Kind discriminator for SessionLimitPredictionResult. +type SessionLimitPredictionResultKind string + +const ( + SessionLimitPredictionResultKindAvailable SessionLimitPredictionResultKind = "available" + SessionLimitPredictionResultKindUnavailable SessionLimitPredictionResultKind = "unavailable" +) + +// Baseline fallback level used to create the prediction. +// Experimental: SessionLimitPredictionSource is part of an experimental API and may change +// or be removed. +type SessionLimitPredictionSource string + +const ( + // The exact model was unavailable, so the prediction used the model family's baseline cell. + SessionLimitPredictionSourceFamily SessionLimitPredictionSource = "family" + // No model or family cell was available, so the prediction used the global client-type + // baseline cell. + SessionLimitPredictionSourceGlobal SessionLimitPredictionSource = "global" + // The prediction used the exact resolved model's baseline cell. + SessionLimitPredictionSourceModel SessionLimitPredictionSource = "model" +) + +// Semantic usage tier used for a recommended cap or additional headroom. +// Experimental: SessionLimitPredictionTier is part of an experimental API and may change or +// be removed. +type SessionLimitPredictionTier string + +const ( + // Additional headroom for longer-running sessions. + SessionLimitPredictionTierAdditionalHeadroom SessionLimitPredictionTier = "additional_headroom" + // Generous headroom for unusually high usage. + SessionLimitPredictionTierGenerousHeadroom SessionLimitPredictionTier = "generous_headroom" + // Maximum available headroom tier. + SessionLimitPredictionTierMaximumHeadroom SessionLimitPredictionTier = "maximum_headroom" + // Recommended starting tier. + SessionLimitPredictionTierRecommended SessionLimitPredictionTier = "recommended" +) + +// Reason a prediction could not be computed. +// Experimental: SessionLimitPredictionUnavailableReason is part of an experimental API and +// may change or be removed. +type SessionLimitPredictionUnavailableReason string + +const ( + // The current model is auto and has not resolved to a concrete model yet. + SessionLimitPredictionUnavailableReasonAutoUnresolved SessionLimitPredictionUnavailableReason = "auto_unresolved" + // No model was provided and the session does not currently have a selected model. + SessionLimitPredictionUnavailableReasonNoModel SessionLimitPredictionUnavailableReason = "no_model" +) + // Log severity level. Determines how the message is displayed in the timeline. Defaults to // "info". // Experimental: SessionLogLevel is part of an experimental API and may change or be removed. @@ -12962,6 +15098,32 @@ const ( SessionWorkingDirectoryContextHostTypeGitHub SessionWorkingDirectoryContextHostType = "github" ) +// Controls automatic non-interactive profile loading where supported. Explicit initScripts +// are unaffected. +// Experimental: ShellInitProfile is part of an experimental API and may change or be +// removed. +type ShellInitProfile string + +const ( + // Disable automatic non-interactive profile loading. Explicit initScripts still run. + ShellInitProfileNone ShellInitProfile = "none" + // Allow automatic non-interactive profile loading when supported. Explicit initScripts + // still run. + ShellInitProfileNonInteractive ShellInitProfile = "non-interactive" +) + +// Supported built-in shells for initialization scripts. +// Experimental: ShellInitScriptShell is part of an experimental API and may change or be +// removed. +type ShellInitScriptShell string + +const ( + // Source the script in the built-in Bash shell on macOS and Linux. + ShellInitScriptShellBash ShellInitScriptShell = "bash" + // Source the script in the built-in PowerShell shell on Windows. + ShellInitScriptShellPowershell ShellInitScriptShell = "powershell" +) + // Signal to send (default: SIGTERM) // Experimental: ShellKillSignal is part of an experimental API and may change or be removed. type ShellKillSignal string @@ -13253,6 +15415,7 @@ const ( UserToolSessionApprovalKindCustomTool UserToolSessionApprovalKind = "custom-tool" UserToolSessionApprovalKindExtensionManagement UserToolSessionApprovalKind = "extension-management" UserToolSessionApprovalKindExtensionPermissionAccess UserToolSessionApprovalKind = "extension-permission-access" + UserToolSessionApprovalKindFactory UserToolSessionApprovalKind = "factory" UserToolSessionApprovalKindMCP UserToolSessionApprovalKind = "mcp" UserToolSessionApprovalKindMemory UserToolSessionApprovalKind = "memory" UserToolSessionApprovalKindRead UserToolSessionApprovalKind = "read" @@ -13521,62 +15684,123 @@ func (a *ServerCommandsAPI) List(ctx context.Context) (*CommandList, error) { return &result, nil } -// Experimental: ServerInstructionsAPI contains experimental APIs that may change or be +// Experimental: ServerExtensionsAPI contains experimental APIs that may change or be // removed. -type ServerInstructionsAPI serverAPI +type ServerExtensionsAPI serverAPI -// Discovers instruction sources across user, repository, and plugin sources. -// -// RPC method: instructions.discover. +// Disable persistently disables extension IDs for future sessions. Active sessions are +// unchanged; use session.extensions.disable to update them. // -// Parameters: Optional project paths to include in instruction discovery. +// RPC method: extensions.disable. // -// Returns: Instruction sources discovered across user, repository, and plugin sources. -func (a *ServerInstructionsAPI) Discover(ctx context.Context, params *InstructionsDiscoverRequest) (*ServerInstructionSourceList, error) { - raw, err := a.client.Request(ctx, "instructions.discover", params) +// Parameters: Source-qualified extension identifiers to persistently disable for future +// sessions. +func (a *ServerExtensionsAPI) Disable(ctx context.Context, params *DiscoveredExtensionsDisableRequest) (*ExtensionsDisableResult, error) { + raw, err := a.client.Request(ctx, "extensions.disable", params) if err != nil { return nil, err } - var result ServerInstructionSourceList + var result ExtensionsDisableResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -// GetDiscoveryPaths returns the canonical files and directories where a client may create -// custom instructions that the runtime will recognize, including ones that do not exist -// yet. Repository targets become active once created. -// -// RPC method: instructions.getDiscoveryPaths. +// Discovers user and enabled installed-plugin extensions from persisted Copilot home state, +// including enablement preferences. Launch-scoped additional plugins are not included. // -// Parameters: Optional project paths to include when enumerating instruction discovery -// targets. +// RPC method: extensions.discover. // -// Returns: Canonical files and directories where custom instructions can be created so the -// runtime will recognize them. -func (a *ServerInstructionsAPI) GetDiscoveryPaths(ctx context.Context, params *InstructionsGetDiscoveryPathsRequest) (*InstructionDiscoveryPathList, error) { - raw, err := a.client.Request(ctx, "instructions.getDiscoveryPaths", params) +// Returns: Extensions discovered from persisted Copilot home state and their effective +// loading mode. Launch-scoped additional plugins are not included. +func (a *ServerExtensionsAPI) Discover(ctx context.Context) (*DiscoveredExtensions, error) { + raw, err := a.client.Request(ctx, "extensions.discover", nil) if err != nil { return nil, err } - var result InstructionDiscoveryPathList + var result DiscoveredExtensions if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -// Experimental: ServerLlmInferenceAPI contains experimental APIs that may change or be -// removed. -type ServerLlmInferenceAPI serverAPI - -// HttpResponseChunk delivers a body byte range (or a terminal transport error) for an -// in-flight response, correlated by requestId. Set `end` true on the last chunk. When -// `error` is set the response terminates with a transport-level failure and the runtime -// raises an APIConnectionError. +// Enable persistently enables extension IDs for future sessions. Active sessions are +// unchanged; use session.extensions.enable to update them. // -// RPC method: llmInference.httpResponseChunk. +// RPC method: extensions.enable. +// +// Parameters: Source-qualified extension identifiers to persistently enable for future +// sessions. +func (a *ServerExtensionsAPI) Enable(ctx context.Context, params *DiscoveredExtensionsEnableRequest) (*ExtensionsEnableResult, error) { + raw, err := a.client.Request(ctx, "extensions.enable", params) + if err != nil { + return nil, err + } + var result ExtensionsEnableResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Experimental: ServerInstructionsAPI contains experimental APIs that may change or be +// removed. +type ServerInstructionsAPI serverAPI + +// Discovers instruction sources across user, repository, and plugin sources. +// +// RPC method: instructions.discover. +// +// Parameters: Optional project paths to include in instruction discovery. +// +// Returns: Instruction sources discovered across user, repository, and plugin sources. +func (a *ServerInstructionsAPI) Discover(ctx context.Context, params *InstructionsDiscoverRequest) (*ServerInstructionSourceList, error) { + raw, err := a.client.Request(ctx, "instructions.discover", params) + if err != nil { + return nil, err + } + var result ServerInstructionSourceList + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// GetDiscoveryPaths returns the canonical files and directories where a client may create +// custom instructions that the runtime will recognize, including ones that do not exist +// yet. Repository targets become active once created. +// +// RPC method: instructions.getDiscoveryPaths. +// +// Parameters: Optional project paths to include when enumerating instruction discovery +// targets. +// +// Returns: Canonical files and directories where custom instructions can be created so the +// runtime will recognize them. +func (a *ServerInstructionsAPI) GetDiscoveryPaths(ctx context.Context, params *InstructionsGetDiscoveryPathsRequest) (*InstructionDiscoveryPathList, error) { + raw, err := a.client.Request(ctx, "instructions.getDiscoveryPaths", params) + if err != nil { + return nil, err + } + var result InstructionDiscoveryPathList + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Experimental: ServerLlmInferenceAPI contains experimental APIs that may change or be +// removed. +type ServerLlmInferenceAPI serverAPI + +// HttpResponseChunk delivers a body byte range (or a terminal transport error) for an +// in-flight response, correlated by requestId. Set `end` true on the last chunk. When +// `error` is set the response terminates with a transport-level failure and the runtime +// raises an APIConnectionError. +// +// RPC method: llmInference.httpResponseChunk. // // Parameters: A response body chunk or terminal error. // @@ -13632,6 +15856,29 @@ func (a *ServerLlmInferenceAPI) SetProvider(ctx context.Context) (*LlmInferenceS return &result, nil } +// Experimental: ServerManagedSettingsAPI contains experimental APIs that may change or be +// removed. +type ServerManagedSettingsAPI serverAPI + +// Read discovers device-managed settings from production MDM and managed-file sources, +// validates them against the runtime-owned managed-settings schema, and returns the +// canonical JSON without requiring a session. +// +// RPC method: managedSettings.read. +// +// Returns: Validated device-managed settings discovered before a session exists. +func (a *ServerManagedSettingsAPI) Read(ctx context.Context) (*ManagedSettingsReadResult, error) { + raw, err := a.client.Request(ctx, "managedSettings.read", nil) + if err != nil { + return nil, err + } + var result ManagedSettingsReadResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // Experimental: ServerMCPAPI contains experimental APIs that may change or be removed. type ServerMCPAPI serverAPI @@ -13783,6 +16030,25 @@ func (s *ServerMCPAPI) Config() *ServerMCPConfigAPI { // Experimental: ServerModelsAPI contains experimental APIs that may change or be removed. type ServerModelsAPI serverAPI +// GetBuiltInCatalog returns the running runtime's complete catalog of well-known built-in +// model IDs without authentication or network access. +// +// RPC method: models.getBuiltInCatalog. +// +// Returns: The running runtime's complete catalog of well-known built-in model IDs, +// including supported models and additional IDs with built-in metadata. +func (a *ServerModelsAPI) GetBuiltInCatalog(ctx context.Context) (*BuiltInModelCatalog, error) { + raw, err := a.client.Request(ctx, "models.getBuiltInCatalog", nil) + if err != nil { + return nil, err + } + var result BuiltInModelCatalog + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // Lists Copilot models available to the authenticated user. // // RPC method: models.list. @@ -14755,22 +17021,24 @@ type ServerRPC struct { // Reuse a single struct instead of allocating one for each service on the heap. common serverAPI - Account *ServerAccountAPI - AgentRegistry *ServerAgentRegistryAPI - Agents *ServerAgentsAPI - Commands *ServerCommandsAPI - Instructions *ServerInstructionsAPI - LlmInference *ServerLlmInferenceAPI - MCP *ServerMCPAPI - Models *ServerModelsAPI - Plugins *ServerPluginsAPI - Runtime *ServerRuntimeAPI - Secrets *ServerSecretsAPI - SessionFS *ServerSessionFSAPI - Sessions *ServerSessionsAPI - Skills *ServerSkillsAPI - Tools *ServerToolsAPI - User *ServerUserAPI + Account *ServerAccountAPI + AgentRegistry *ServerAgentRegistryAPI + Agents *ServerAgentsAPI + Commands *ServerCommandsAPI + Extensions *ServerExtensionsAPI + Instructions *ServerInstructionsAPI + LlmInference *ServerLlmInferenceAPI + ManagedSettings *ServerManagedSettingsAPI + MCP *ServerMCPAPI + Models *ServerModelsAPI + Plugins *ServerPluginsAPI + Runtime *ServerRuntimeAPI + Secrets *ServerSecretsAPI + SessionFS *ServerSessionFSAPI + Sessions *ServerSessionsAPI + Skills *ServerSkillsAPI + Tools *ServerToolsAPI + User *ServerUserAPI } // Ping checks server responsiveness and returns protocol information. @@ -14794,6 +17062,25 @@ func (a *ServerRPC) Ping(ctx context.Context, params *PingRequest) (*PingResult, return &result, nil } +// RegisterExtensionLaunchProvider registers the calling SDK client as the per-entrypoint +// extension launch provider. Call before creating any sessions. When omitted, the runtime +// temporarily falls back to its built-in Node launcher for backward compatibility. +// +// RPC method: registerExtensionLaunchProvider. +// Experimental: RegisterExtensionLaunchProvider is an experimental API and may change or be +// removed in future versions. +func (a *ServerRPC) RegisterExtensionLaunchProvider(ctx context.Context) (*RegisterExtensionLaunchProviderResult, error) { + raw, err := a.common.client.Request(ctx, "registerExtensionLaunchProvider", nil) + if err != nil { + return nil, err + } + var result RegisterExtensionLaunchProviderResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + func NewServerRPC(client *jsonrpc2.Client) *ServerRPC { r := &ServerRPC{} r.common = serverAPI{client: client} @@ -14801,8 +17088,10 @@ func NewServerRPC(client *jsonrpc2.Client) *ServerRPC { r.AgentRegistry = (*ServerAgentRegistryAPI)(&r.common) r.Agents = (*ServerAgentsAPI)(&r.common) r.Commands = (*ServerCommandsAPI)(&r.common) + r.Extensions = (*ServerExtensionsAPI)(&r.common) r.Instructions = (*ServerInstructionsAPI)(&r.common) r.LlmInference = (*ServerLlmInferenceAPI)(&r.common) + r.ManagedSettings = (*ServerManagedSettingsAPI)(&r.common) r.MCP = (*ServerMCPAPI)(&r.common) r.Models = (*ServerModelsAPI)(&r.common) r.Plugins = (*ServerPluginsAPI)(&r.common) @@ -14849,6 +17138,26 @@ func (a *InternalServerSessionsAPI) ConfigureSessionExtensions(ctx context.Conte return &result, nil } +// Deletes one local session from disk after running the same lifecycle hooks as the session +// manager. +// +// RPC method: sessions.delete. +// +// Parameters: Session ID to delete from disk. +// Internal: Delete is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalServerSessionsAPI) Delete(ctx context.Context, params *SessionsDeleteRequest) (*SessionsDeleteResult, error) { + raw, err := a.client.Request(ctx, "sessions.delete", params) + if err != nil { + return nil, err + } + var result SessionsDeleteResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // GetBoardEntryCount gets the dynamic-context board entry count associated with a session, // when available. Internal: this exists solely so CLI telemetry events (`rem_spawn_gate`, // `rem_consolidation_complete`) can pair START / END board counts around the detached @@ -14900,6 +17209,27 @@ func (a *InternalServerSessionsAPI) GetEventFilePath(ctx context.Context, params return &result, nil } +// GetMetadata reads lightweight persisted metadata for one local session without opening it. +// +// RPC method: sessions.getMetadata. +// +// Parameters: Session ID whose persisted metadata should be read. +// +// Returns: Persisted local session metadata when the session exists. +// Internal: GetMetadata is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalServerSessionsAPI) GetMetadata(ctx context.Context, params *SessionsGetMetadataRequest) (*SessionsGetMetadataResult, error) { + raw, err := a.client.Request(ctx, "sessions.getMetadata", params) + if err != nil { + return nil, err + } + var result SessionsGetMetadataResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // GetPersistedRemoteSteerable returns a session's persisted remote-steerable flag, if any // has been recorded. Internal: this is CLI-specific book-keeping used by `--continue` / // `--resume` to inherit the prior session's remote-steerable preference. SDK consumers that @@ -14926,6 +17256,28 @@ func (a *InternalServerSessionsAPI) GetPersistedRemoteSteerable(ctx context.Cont return &result, nil } +// ListNonEmptySessionIds lists recent local session IDs that contain user-visible history, +// omitting housekeeping-only sessions. +// +// RPC method: sessions.listNonEmptySessionIds. +// +// Parameters: Limit for non-empty local session IDs. +// +// Returns: Recent local session IDs that contain user-visible history. +// Internal: ListNonEmptySessionIds is part of the SDK's internal handshake/plumbing; +// external callers should not use it. +func (a *InternalServerSessionsAPI) ListNonEmptySessionIds(ctx context.Context, params *SessionsListNonEmptySessionIDsRequest) (*SessionsListNonEmptySessionIDsResult, error) { + raw, err := a.client.Request(ctx, "sessions.listNonEmptySessionIds", params) + if err != nil { + return nil, err + } + var result SessionsListNonEmptySessionIDsResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // RegisterExtensionToolsOnSession registers extension-provided tools on the given session, // gated by an optional `enabled` callback. Returns an opaque unsubscribe function the // caller must invoke to deregister the tools when the extension is torn down. Marked @@ -15041,13 +17393,28 @@ func (a *AgentAPI) GetCurrent(ctx context.Context) (*AgentGetCurrentResult, erro return &result, nil } -// Lists custom agents available to the session. +// Lists agents available to the session. Defaults to custom agents only; pass +// includeBuiltInAgents to include the effective built-in agents. // // RPC method: session.agent.list. // -// Returns: Custom agents available to the session. -func (a *AgentAPI) List(ctx context.Context) (*AgentList, error) { +// Parameters: Controls whether built-in agents and authored prompt text are included. +// +// Returns: Agents available to the session. +func (a *AgentAPI) List(ctx context.Context, params ...*SessionAgentListRequest) (*AgentList, error) { + var requestParams *SessionAgentListRequest + if len(params) > 0 { + requestParams = params[0] + } req := map[string]any{"sessionId": a.sessionID} + if requestParams != nil { + if requestParams.IncludeBuiltInAgents != nil { + req["includeBuiltInAgents"] = *requestParams.IncludeBuiltInAgents + } + if requestParams.IncludePrompt != nil { + req["includePrompt"] = *requestParams.IncludePrompt + } + } raw, err := a.client.Request(ctx, "session.agent.list", req) if err != nil { return nil, err @@ -15100,6 +17467,32 @@ func (a *AgentAPI) Select(ctx context.Context, params *AgentSelectRequest) (*Age return &result, nil } +// SetPrompt sets an in-memory authored prompt override for an available agent. For built-in +// agents, this replaces only the static base prompt while preserving runtime-owned dynamic +// prompt composition and behavior. The special `general-purpose` agent is not overrideable. +// Overrides are not persisted; resumed and forked sessions start without them, so the host +// must re-apply them. +// +// RPC method: session.agent.setPrompt. +// +// Parameters: An in-memory authored prompt override for an available agent. +func (a *AgentAPI) SetPrompt(ctx context.Context, params *AgentSetPromptRequest) (*SessionAgentSetPromptResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["id"] = params.ID + req["prompt"] = params.Prompt + } + raw, err := a.client.Request(ctx, "session.agent.setPrompt", req) + if err != nil { + return nil, err + } + var result SessionAgentSetPromptResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // Experimental: CanvasAPI contains experimental APIs that may change or be removed. type CanvasAPI sessionAPI @@ -15336,8 +17729,8 @@ func (a *CommandsAPI) Invoke(ctx context.Context, params *CommandsInvokeRequest) // // Returns: Slash commands available in the session, after applying any include/exclude // filters. -func (a *CommandsAPI) List(ctx context.Context, params ...*CommandsListRequest) (*CommandList, error) { - var requestParams *CommandsListRequest +func (a *CommandsAPI) List(ctx context.Context, params ...*SessionCommandsListRequest) (*CommandList, error) { + var requestParams *SessionCommandsListRequest if len(params) > 0 { requestParams = params[0] } @@ -15441,6 +17834,38 @@ func (a *CompletionsAPI) Request(ctx context.Context, params *CompletionsRequest return &result, nil } +// Experimental: ContentExclusionAPI contains experimental APIs that may change or be +// removed. +type ContentExclusionAPI sessionAPI + +// CheckPaths checks local file system absolute paths within the session working directory +// against its content-exclusion policy. Results preserve input order. Unsupported +// paths/filesystems and unavailable policy evaluation return available false, and callers +// must treat every requested path as excluded. +// +// RPC method: session.contentExclusion.checkPaths. +// +// Parameters: Local file system absolute paths within the session working directory to +// check against its content-exclusion policy. +// +// Returns: Batch content-exclusion result. Callers must fail closed when policy evaluation +// is unavailable. +func (a *ContentExclusionAPI) CheckPaths(ctx context.Context, params *ContentExclusionCheckPathsRequest) (*ContentExclusionCheckPathsResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["paths"] = params.Paths + } + raw, err := a.client.Request(ctx, "session.contentExclusion.checkPaths", req) + if err != nil { + return nil, err + } + var result ContentExclusionCheckPathsResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // Experimental: DebugAPI contains experimental APIs that may change or be removed. type DebugAPI sessionAPI @@ -15480,6 +17905,7 @@ func (a *DebugAPI) CollectLogs(ctx context.Context, params *DebugCollectLogsRequ type EventLogAPI sessionAPI // Reads a batch of session events from a cursor, optionally waiting for new events. +// Supports tail-first reads via `direction: backward`. // // RPC method: session.eventLog.read. // @@ -15491,12 +17917,21 @@ type EventLogAPI sessionAPI func (a *EventLogAPI) Read(ctx context.Context, params *EventLogReadRequest) (*EventsReadResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { + if params.AgentIDs != nil { + req["agentIds"] = params.AgentIDs + } if params.AgentScope != nil { req["agentScope"] = *params.AgentScope } if params.Cursor != nil { req["cursor"] = *params.Cursor } + if params.Direction != nil { + req["direction"] = *params.Direction + } + if params.IncludeEphemeral != nil { + req["includeEphemeral"] = *params.IncludeEphemeral + } if params.Max != nil { req["max"] = *params.Max } @@ -15703,6 +18138,7 @@ type FactoryAPI sessionAPI func (a *FactoryAPI) Agent(ctx context.Context, params *FactoryAgentRequest) (*FactoryAgentResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { + req["executionToken"] = params.ExecutionToken req["factoryRunId"] = params.FactoryRunID req["opts"] = params.Opts req["prompt"] = params.Prompt @@ -15764,99 +18200,110 @@ func (a *FactoryAPI) GetRun(ctx context.Context, params *FactoryGetRunRequest) ( return &result, nil } -// Log records a batch of ordered factory progress lines. +// GetRunDetail gets durable and live observability detail for one factory run. // -// RPC method: session.factory.log. +// RPC method: session.factory.getRunDetail. // -// Parameters: Parameters for recording factory progress. +// Parameters: Parameters for retrieving a factory run. // -// Returns: Acknowledgement that a factory request was accepted. -func (a *FactoryAPI) Log(ctx context.Context, params *FactoryLogRequest) (*FactoryAckResult, error) { +// Returns: Full factory run observability detail. +func (a *FactoryAPI) GetRunDetail(ctx context.Context, params *FactoryGetRunRequest) (*FactoryRunDetail, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { - req["lines"] = params.Lines req["runId"] = params.RunID } - raw, err := a.client.Request(ctx, "session.factory.log", req) + raw, err := a.client.Request(ctx, "session.factory.getRunDetail", req) if err != nil { return nil, err } - var result FactoryAckResult + var result FactoryRunDetail if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -// Runs a registered factory by name at the top level. +// GetRunProgress pages durable progress for one factory run. // -// RPC method: session.factory.run. +// RPC method: session.factory.getRunProgress. // -// Parameters: Parameters for invoking a registered factory. +// Parameters: Parameters for paging factory progress. // -// Returns: Complete current or terminal factory run envelope. -func (a *FactoryAPI) Run(ctx context.Context, params *FactoryRunRequest) (*FactoryRunResult, error) { +// Returns: A bidirectional page of factory progress. +func (a *FactoryAPI) GetRunProgress(ctx context.Context, params *FactoryGetRunProgressRequest) (*FactoryProgressPage, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { - req["args"] = params.Args - req["name"] = params.Name - if params.Options != nil { - req["options"] = *params.Options + if params.AfterSeq != nil { + req["afterSeq"] = *params.AfterSeq + } + if params.BeforeSeq != nil { + req["beforeSeq"] = *params.BeforeSeq + } + if params.Limit != nil { + req["limit"] = *params.Limit } + if params.PhaseID != nil { + req["phaseId"] = *params.PhaseID + } + req["runId"] = params.RunID } - raw, err := a.client.Request(ctx, "session.factory.run", req) + raw, err := a.client.Request(ctx, "session.factory.getRunProgress", req) if err != nil { return nil, err } - var result FactoryRunResult + var result FactoryProgressPage if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -// Experimental: FactoryJournalAPI contains experimental APIs that may change or be removed. -type FactoryJournalAPI sessionAPI - -// Get reads a memoized factory journal entry. +// ListRuns lists durable factory runs for this session in creation order. // -// RPC method: session.factory.journal.get. +// RPC method: session.factory.listRuns. // -// Parameters: Parameters for reading a factory journal entry. +// Parameters: Parameters for paging factory runs. // -// Returns: Result of reading a factory journal entry. -func (a *FactoryJournalAPI) Get(ctx context.Context, params *FactoryJournalGetRequest) (*FactoryJournalGetResult, error) { +// Returns: A page of factory runs in durable creation order. +func (a *FactoryAPI) ListRuns(ctx context.Context, params *FactoryListRunsRequest) (*FactoryListRunsResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { - req["key"] = params.Key - req["runId"] = params.RunID + if params.AfterSeq != nil { + req["afterSeq"] = *params.AfterSeq + } + if params.BeforeSeq != nil { + req["beforeSeq"] = *params.BeforeSeq + } + if params.Limit != nil { + req["limit"] = *params.Limit + } } - raw, err := a.client.Request(ctx, "session.factory.journal.get", req) + raw, err := a.client.Request(ctx, "session.factory.listRuns", req) if err != nil { return nil, err } - var result FactoryJournalGetResult + var result FactoryListRunsResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -// Put stores a memoized factory journal entry. +// Log records a batch of ordered factory progress lines. // -// RPC method: session.factory.journal.put. +// RPC method: session.factory.log. // -// Parameters: Parameters for storing a factory journal entry. +// Parameters: Parameters for recording factory progress. // // Returns: Acknowledgement that a factory request was accepted. -func (a *FactoryJournalAPI) Put(ctx context.Context, params *FactoryJournalPutRequest) (*FactoryAckResult, error) { +func (a *FactoryAPI) Log(ctx context.Context, params *FactoryLogRequest) (*FactoryAckResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { - req["key"] = params.Key - req["resultJson"] = params.ResultJSON + req["executionToken"] = params.ExecutionToken + req["lines"] = params.Lines req["runId"] = params.RunID } - raw, err := a.client.Request(ctx, "session.factory.journal.put", req) + raw, err := a.client.Request(ctx, "session.factory.log", req) if err != nil { return nil, err } @@ -15867,26 +18314,133 @@ func (a *FactoryJournalAPI) Put(ctx context.Context, params *FactoryJournalPutRe return &result, nil } -// Experimental: Journal returns experimental APIs that may change or be removed. -func (s *FactoryAPI) Journal() *FactoryJournalAPI { - return (*FactoryJournalAPI)(s) -} - -// Experimental: FleetAPI contains experimental APIs that may change or be removed. -type FleetAPI sessionAPI - -// Starts fleet mode by submitting the fleet orchestration prompt to the session. +// Resumes a factory run using its persisted name, arguments, journal, and accounting. // -// RPC method: session.fleet.start. +// RPC method: session.factory.resume. // -// Parameters: Optional user prompt to combine with the fleet orchestration instructions. +// Parameters: Parameters for resuming a factory run from its persisted identity. // -// Returns: Indicates whether fleet mode was successfully activated. -func (a *FleetAPI) Start(ctx context.Context, params *FleetStartRequest) (*FleetStartResult, error) { +// Returns: Resolved persisted factory identity and resumed run envelope. +func (a *FactoryAPI) Resume(ctx context.Context, params *FactoryResumeRequest) (*FactoryResumeResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { - if params.Prompt != nil { - req["prompt"] = *params.Prompt + if params.Limits != nil { + req["limits"] = *params.Limits + } + req["runId"] = params.RunID + } + raw, err := a.client.Request(ctx, "session.factory.resume", req) + if err != nil { + return nil, err + } + var result FactoryResumeResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Runs a registered factory by name at the top level. +// +// RPC method: session.factory.run. +// +// Parameters: Parameters for invoking a registered factory. +// +// Returns: Complete current or terminal factory run envelope. +func (a *FactoryAPI) Run(ctx context.Context, params *FactoryRunRequest) (*FactoryRunResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["args"] = params.Args + req["name"] = params.Name + if params.Options != nil { + req["options"] = *params.Options + } + } + raw, err := a.client.Request(ctx, "session.factory.run", req) + if err != nil { + return nil, err + } + var result FactoryRunResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Experimental: FactoryJournalAPI contains experimental APIs that may change or be removed. +type FactoryJournalAPI sessionAPI + +// Get reads a memoized factory journal entry. +// +// RPC method: session.factory.journal.get. +// +// Parameters: Parameters for reading a factory journal entry. +// +// Returns: Result of reading a factory journal entry. +func (a *FactoryJournalAPI) Get(ctx context.Context, params *FactoryJournalGetRequest) (*FactoryJournalGetResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["executionToken"] = params.ExecutionToken + req["key"] = params.Key + req["runId"] = params.RunID + } + raw, err := a.client.Request(ctx, "session.factory.journal.get", req) + if err != nil { + return nil, err + } + var result FactoryJournalGetResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Put stores a memoized factory journal entry. +// +// RPC method: session.factory.journal.put. +// +// Parameters: Parameters for storing a factory journal entry. +// +// Returns: Acknowledgement that a factory request was accepted. +func (a *FactoryJournalAPI) Put(ctx context.Context, params *FactoryJournalPutRequest) (*FactoryAckResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["executionToken"] = params.ExecutionToken + req["key"] = params.Key + req["resultJson"] = params.ResultJSON + req["runId"] = params.RunID + } + raw, err := a.client.Request(ctx, "session.factory.journal.put", req) + if err != nil { + return nil, err + } + var result FactoryAckResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Experimental: Journal returns experimental APIs that may change or be removed. +func (s *FactoryAPI) Journal() *FactoryJournalAPI { + return (*FactoryJournalAPI)(s) +} + +// Experimental: FleetAPI contains experimental APIs that may change or be removed. +type FleetAPI sessionAPI + +// Starts fleet mode by submitting the fleet orchestration prompt to the session. +// +// RPC method: session.fleet.start. +// +// Parameters: Optional user prompt to combine with the fleet orchestration instructions. +// +// Returns: Indicates whether fleet mode was successfully activated. +func (a *FleetAPI) Start(ctx context.Context, params *FleetStartRequest) (*FleetStartResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + if params.Prompt != nil { + req["prompt"] = *params.Prompt } } raw, err := a.client.Request(ctx, "session.fleet.start", req) @@ -15988,6 +18542,34 @@ func (a *HistoryAPI) CancelBackgroundCompaction(ctx context.Context) (*HistoryCa return &result, nil } +// ClearContext clears the session's conversation history, keeping only system and developer +// messages, and seeds the fresh context window with a first user message. Must be called +// from inside a tool handler: the clear has to drop the results of the tool calls its wipe +// orphans, and it rejects when no tool call is in flight. +// +// RPC method: session.history.clearContext. +// +// Parameters: Parameters for clearing the conversation and seeding the window that replaces +// it. +// +// Returns: What a successful clear removed. A clear that could not be applied rejects +// instead of reporting a count. +func (a *HistoryAPI) ClearContext(ctx context.Context, params *HistoryClearContextRequest) (*HistoryClearContextResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["prompt"] = params.Prompt + } + raw, err := a.client.Request(ctx, "session.history.clearContext", req) + if err != nil { + return nil, err + } + var result HistoryClearContextResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // Compacts the session history to reduce context usage. // // RPC method: session.history.compact. @@ -15996,8 +18578,8 @@ func (a *HistoryAPI) CancelBackgroundCompaction(ctx context.Context) (*HistoryCa // // Returns: Compaction outcome with the number of tokens and messages removed, summary text, // and the resulting context window breakdown. -func (a *HistoryAPI) Compact(ctx context.Context, params ...*HistoryCompactRequest) (*HistoryCompactResult, error) { - var requestParams *HistoryCompactRequest +func (a *HistoryAPI) Compact(ctx context.Context, params ...*SessionHistoryCompactRequest) (*HistoryCompactResult, error) { + var requestParams *SessionHistoryCompactRequest if len(params) > 0 { requestParams = params[0] } @@ -16006,6 +18588,12 @@ func (a *HistoryAPI) Compact(ctx context.Context, params ...*HistoryCompactReque if requestParams.CustomInstructions != nil { req["customInstructions"] = *requestParams.CustomInstructions } + if requestParams.TokenLimit != nil { + req["tokenLimit"] = *requestParams.TokenLimit + } + if requestParams.Trigger != nil { + req["trigger"] = *requestParams.Trigger + } } raw, err := a.client.Request(ctx, "session.history.compact", req) if err != nil { @@ -16018,6 +18606,84 @@ func (a *HistoryAPI) Compact(ctx context.Context, params ...*HistoryCompactReque return &result, nil } +// ListRewindPoints lists the user turns that the session can rewind to. Never rejects for a +// busy session: rewind reads need the session's file-change captures to be settled, so a +// session that still holds active work answers with `unavailableReason: "session-busy"` and +// no points, which the caller can retry. +// +// RPC method: session.history.listRewindPoints. +// +// Returns: Rewind points and file-change-tracking availability for the session. +func (a *HistoryAPI) ListRewindPoints(ctx context.Context) (*HistoryListRewindPointsResult, error) { + req := map[string]any{"sessionId": a.sessionID} + raw, err := a.client.Request(ctx, "session.history.listRewindPoints", req) + if err != nil { + return nil, err + } + var result HistoryListRewindPointsResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// PreviewRewind previews the files that a conversation-and-files rewind would restore. +// +// RPC method: session.history.previewRewind. +// +// Parameters: Event boundary to preview for conversation-and-files rewind. +// +// Returns: Files and aggregate changes for a prospective rewind. +func (a *HistoryAPI) PreviewRewind(ctx context.Context, params *HistoryPreviewRewindRequest) (*HistoryPreviewRewindResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["eventId"] = params.EventID + } + raw, err := a.client.Request(ctx, "session.history.previewRewind", req) + if err != nil { + return nil, err + } + var result HistoryPreviewRewindResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Rewinds the session conversation, optionally restoring files changed by the discarded +// turns. Not crash-atomic: file restore and conversation truncation are separate stores, +// applied in that order, so a process crash between them can leave the workspace rewound +// while the conversation still contains the discarded turns. There is no recovery journal; +// re-running the same rewind is the recovery path for a crash before truncation lands, +// since file restore is idempotent (already-restored files are reported as skipped) and +// truncation is re-derived from the still-retained boundary event. After truncation lands +// that boundary no longer exists, so the same request is rejected; the only stage that can +// still be outstanding is snapshot pruning, whose failure leaves orphan snapshots the +// capture store tolerates. The reverse inconsistency cannot occur, because truncation is +// never applied before file restore succeeds. +// +// RPC method: session.history.rewind. +// +// Parameters: Boundary and mode for rewinding session history. +// +// Returns: Structured outcome of a rewind request. +func (a *HistoryAPI) Rewind(ctx context.Context, params *HistoryRewindRequest) (*HistoryRewindResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["eventId"] = params.EventID + req["mode"] = params.Mode + } + raw, err := a.client.Request(ctx, "session.history.rewind", req) + if err != nil { + return nil, err + } + var result HistoryRewindResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // SummarizeForHandoff produces a markdown summary of the session's conversation context for // hand-off scenarios. // @@ -16082,6 +18748,44 @@ func (a *InstructionsAPI) GetSources(ctx context.Context) (*InstructionsGetSourc return &result, nil } +// Experimental: LimitPredictionAPI contains experimental APIs that may change or be removed. +type LimitPredictionAPI sessionAPI + +// Predicts an AI-credit session limit for the session's resolved model. Returns an +// unavailable result instead of falling back when the current model is unresolved auto. +// +// RPC method: session.limitPrediction.predict. +// +// Parameters: Parameters for predicting an AI-credit session limit. Omitting `modelId` uses +// the session's currently selected model. +// +// Returns: Prediction result. Available results include prediction details; unavailable +// results include an explicit reason. +func (a *LimitPredictionAPI) Predict(ctx context.Context, params ...*SessionLimitPredictionPredictRequest) (SessionLimitPredictionResult, error) { + var requestParams *SessionLimitPredictionPredictRequest + if len(params) > 0 { + requestParams = params[0] + } + req := map[string]any{"sessionId": a.sessionID} + if requestParams != nil { + if requestParams.ClientType != nil { + req["clientType"] = *requestParams.ClientType + } + if requestParams.ModelID != nil { + req["modelId"] = *requestParams.ModelID + } + } + raw, err := a.client.Request(ctx, "session.limitPrediction.predict", req) + if err != nil { + return nil, err + } + result, err := unmarshalSessionLimitPredictionResult(raw) + if err != nil { + return nil, err + } + return result, nil +} + // Experimental: LspAPI contains experimental APIs that may change or be removed. type LspAPI sessionAPI @@ -16372,20 +19076,25 @@ func (a *MCPAPI) SetEnvValueMode(ctx context.Context, params *MCPSetEnvValueMode return &result, nil } -// StartServer starts an individual MCP server on the live session from a caller-supplied -// config. Session-scoped and ephemeral: the server is added to this session's running set -// only and is reaped when the session ends. Does NOT modify persistent user configuration -// (`mcp.config.*`), so it does not affect future sessions. The server surfaces through -// `session.mcp.list` and the `session.mcp_servers_loaded` / +// StartServer starts an individual MCP server on the live session. Omit `config` for a +// config-free start-by-name of an already-configured server (reuses the server's +// already-registered configuration); supply `config` to start from a caller-supplied +// configuration. Session-scoped and ephemeral: the server is added to this session's +// running set only and is reaped when the session ends. Does NOT modify persistent user +// configuration (`mcp.config.*`), so it does not affect future sessions. The server +// surfaces through `session.mcp.list` and the `session.mcp_servers_loaded` / // `session.mcp_server_status_changed` events like any other server. // // RPC method: session.mcp.startServer. // -// Parameters: Server name and configuration for an individual MCP server start. +// Parameters: Server name and optional configuration for an individual MCP server start. +// Omit `config` for a config-free start-by-name of an already-configured server. func (a *MCPAPI) StartServer(ctx context.Context, params *MCPStartServerRequest) (*SessionMCPStartServerResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { - req["config"] = params.Config + if params.Config != nil { + req["config"] = params.Config + } req["serverName"] = params.ServerName } raw, err := a.client.Request(ctx, "session.mcp.startServer", req) @@ -16611,6 +19320,33 @@ func (s *MCPAPI) Headers() *MCPHeadersAPI { // Experimental: MCPOauthAPI contains experimental APIs that may change or be removed. type MCPOauthAPI sessionAPI +// AuthenticationStateChanged notifies the session that MCP OAuth authentication succeeded +// and updated credentials were persisted, so cached tool definitions can be refreshed. +// +// RPC method: session.mcp.oauth.authenticationStateChanged. +// +// Parameters: Identifies the MCP server whose persisted OAuth credentials were updated. +func (a *MCPOauthAPI) AuthenticationStateChanged(ctx context.Context, params *MCPOauthAuthenticationStateChangedRequest) (*SessionMCPOauthAuthenticationStateChangedResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + if params.RefreshSessionToken != nil { + req["refreshSessionToken"] = *params.RefreshSessionToken + } + if params.ServerName != nil { + req["serverName"] = *params.ServerName + } + } + raw, err := a.client.Request(ctx, "session.mcp.oauth.authenticationStateChanged", req) + if err != nil { + return nil, err + } + var result SessionMCPOauthAuthenticationStateChangedResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // HandlePendingRequest resolves a pending MCP OAuth request with a host-provided token or // cancellation. The pending request is emitted as mcp.oauth_required with the data // necessary to authorize the request. @@ -16683,6 +19419,29 @@ func (a *MCPOauthAPI) Login(ctx context.Context, params *MCPOauthLoginRequest) ( return &result, nil } +// Responds to a pending MCP OAuth authorization request by its request id. +// +// RPC method: session.mcp.oauth.respond. +// +// Parameters: Pending MCP OAuth request id to respond to. +// +// Returns: Indicates whether the pending MCP OAuth response was accepted. +func (a *MCPOauthAPI) Respond(ctx context.Context, params *MCPOauthRespondRequest) (*MCPOauthRespondResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["requestId"] = params.RequestID + } + raw, err := a.client.Request(ctx, "session.mcp.oauth.respond", req) + if err != nil { + return nil, err + } + var result MCPOauthRespondResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // Experimental: Oauth returns experimental APIs that may change or be removed. func (s *MCPAPI) Oauth() *MCPOauthAPI { return (*MCPOauthAPI)(s) @@ -17087,8 +19846,8 @@ func (a *ModelAPI) GetCurrent(ctx context.Context) (*CurrentModel, error) { // Parameters: Optional listing options. // // Returns: The list of models available to this session. -func (a *ModelAPI) List(ctx context.Context, params ...*ModelListRequest) (*SessionModelList, error) { - var requestParams *ModelListRequest +func (a *ModelAPI) List(ctx context.Context, params ...*SessionModelListRequest) (*SessionModelList, error) { + var requestParams *SessionModelListRequest if len(params) > 0 { requestParams = params[0] } @@ -17149,6 +19908,9 @@ func (a *ModelAPI) SwitchTo(ctx context.Context, params *ModelSwitchToRequest) ( if params.ContextTier != nil { req["contextTier"] = *params.ContextTier } + if params.DeferIfModelChangeQueued != nil { + req["deferIfModelChangeQueued"] = *params.DeferIfModelChangeQueued + } if params.ModelCapabilities != nil { req["modelCapabilities"] = *params.ModelCapabilities } @@ -17326,6 +20088,9 @@ func (a *OptionsAPI) Update(ctx context.Context, params *SessionUpdateOptionsPar if params.EventsLogDirectory != nil { req["eventsLogDirectory"] = *params.EventsLogDirectory } + if params.EventsLogIncludesSubagents != nil { + req["eventsLogIncludesSubagents"] = *params.EventsLogIncludesSubagents + } if params.ExcludedBuiltinAgents != nil { req["excludedBuiltinAgents"] = params.ExcludedBuiltinAgents } @@ -17389,6 +20154,9 @@ func (a *OptionsAPI) Update(ctx context.Context, params *SessionUpdateOptionsPar if params.SessionLimits != nil { req["sessionLimits"] = *params.SessionLimits } + if params.Shell != nil { + req["shell"] = *params.Shell + } if params.ShellInitProfile != nil { req["shellInitProfile"] = *params.ShellInitProfile } @@ -17506,6 +20274,9 @@ func (a *PermissionsAPI) GetAllowAll(ctx context.Context) (*AllowAllPermissionSt func (a *PermissionsAPI) HandlePendingPermissionRequest(ctx context.Context, params *PermissionDecisionRequest) (*PermissionRequestResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { + if params.DecisionContext != nil { + req["decisionContext"] = *params.DecisionContext + } req["requestId"] = params.RequestID req["result"] = params.Result } @@ -17601,9 +20372,17 @@ func (a *PermissionsAPI) PendingRequests(ctx context.Context) (*PendingPermissio // // RPC method: session.permissions.resetSessionApprovals. // +// Parameters: Clears session-scoped tool permission approvals, and optionally the +// location-scoped ones. +// // Returns: Indicates whether the operation succeeded. -func (a *PermissionsAPI) ResetSessionApprovals(ctx context.Context) (*PermissionsResetSessionApprovalsResult, error) { +func (a *PermissionsAPI) ResetSessionApprovals(ctx context.Context, params *PermissionsResetSessionApprovalsRequest) (*PermissionsResetSessionApprovalsResult, error) { req := map[string]any{"sessionId": a.sessionID} + if params != nil { + if params.IncludeLocation != nil { + req["includeLocation"] = *params.IncludeLocation + } + } raw, err := a.client.Request(ctx, "session.permissions.resetSessionApprovals", req) if err != nil { return nil, err @@ -18123,8 +20902,8 @@ func (a *PluginsAPI) List(ctx context.Context) (*PluginList, error) { // RPC method: session.plugins.reload. // // Parameters: Optional flags controlling which side effects the reload performs. -func (a *PluginsAPI) Reload(ctx context.Context, params ...*PluginsReloadRequest) (*SessionPluginsReloadResult, error) { - var requestParams *PluginsReloadRequest +func (a *PluginsAPI) Reload(ctx context.Context, params ...*SessionPluginsReloadRequest) (*SessionPluginsReloadResult, error) { + var requestParams *SessionPluginsReloadRequest if len(params) > 0 { requestParams = params[0] } @@ -18206,8 +20985,8 @@ func (a *ProviderAPI) Add(ctx context.Context, params *ProviderAddRequest) (*Pro // // Returns: A snapshot of the provider endpoint the session is currently configured to talk // to. -func (a *ProviderAPI) GetEndpoint(ctx context.Context, params ...*ProviderGetEndpointRequest) (*ProviderEndpoint, error) { - var requestParams *ProviderGetEndpointRequest +func (a *ProviderAPI) GetEndpoint(ctx context.Context, params ...*SessionProviderGetEndpointRequest) (*ProviderEndpoint, error) { + var requestParams *SessionProviderGetEndpointRequest if len(params) > 0 { requestParams = params[0] } @@ -18247,31 +21026,125 @@ func (a *QueueAPI) Clear(ctx context.Context) (*SessionQueueClearResult, error) return &result, nil } -// PendingItems returns the local session's pending user-facing queued items and steering -// messages. +// DuplicateAt duplicates an addressable queued item immediately after its source. // -// RPC method: session.queue.pendingItems. +// RPC method: session.queue.duplicateAt. // -// Returns: Snapshot of the session's pending queued items and immediate-steering messages. -func (a *QueueAPI) PendingItems(ctx context.Context) (*QueuePendingItemsResult, error) { +// Parameters: Parameters for duplicating a queued item. +// +// Returns: Result of duplicating a queued item. +func (a *QueueAPI) DuplicateAt(ctx context.Context, params *QueueDuplicateAtRequest) (*QueueDuplicateAtResult, error) { req := map[string]any{"sessionId": a.sessionID} - raw, err := a.client.Request(ctx, "session.queue.pendingItems", req) + if params != nil { + req["id"] = params.ID + } + raw, err := a.client.Request(ctx, "session.queue.duplicateAt", req) if err != nil { return nil, err } - var result QueuePendingItemsResult + var result QueueDuplicateAtResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -// RemoveMostRecent removes the most recently queued user-facing item (LIFO). +// InsertAt inserts a new queued message at a public visible position. // -// RPC method: session.queue.removeMostRecent. +// RPC method: session.queue.insertAt. // -// Returns: Indicates whether a user-facing pending item was removed. -func (a *QueueAPI) RemoveMostRecent(ctx context.Context) (*QueueRemoveMostRecentResult, error) { +// Parameters: Parameters for inserting a queued message at a public visible position. +// +// Returns: Result of inserting a queued message. +func (a *QueueAPI) InsertAt(ctx context.Context, params *QueueInsertAtRequest) (*QueueInsertAtResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["message"] = params.Message + req["position"] = params.Position + } + raw, err := a.client.Request(ctx, "session.queue.insertAt", req) + if err != nil { + return nil, err + } + var result QueueInsertAtResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// MoveItem moves an addressable queued item to a public visible position. +// +// RPC method: session.queue.moveItem. +// +// Parameters: Parameters for moving a queued item by stable id. +// +// Returns: Result of moving a queued item. +func (a *QueueAPI) MoveItem(ctx context.Context, params *QueueMoveItemRequest) (*QueueMoveItemResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["id"] = params.ID + req["toPosition"] = params.ToPosition + } + raw, err := a.client.Request(ctx, "session.queue.moveItem", req) + if err != nil { + return nil, err + } + var result QueueMoveItemResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// PendingItems returns the local session's pending user-facing queued items and steering +// messages. +// +// RPC method: session.queue.pendingItems. +// +// Returns: Snapshot of the session's pending queued items and immediate-steering messages. +func (a *QueueAPI) PendingItems(ctx context.Context) (*QueuePendingItemsResult, error) { + req := map[string]any{"sessionId": a.sessionID} + raw, err := a.client.Request(ctx, "session.queue.pendingItems", req) + if err != nil { + return nil, err + } + var result QueuePendingItemsResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// RemoveAt removes an addressable queued item by its stable id. +// +// RPC method: session.queue.removeAt. +// +// Parameters: Parameters for removing a queued item by stable id. +// +// Returns: Result of removing a queued item. +func (a *QueueAPI) RemoveAt(ctx context.Context, params *QueueRemoveAtRequest) (*QueueRemoveAtResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["id"] = params.ID + } + raw, err := a.client.Request(ctx, "session.queue.removeAt", req) + if err != nil { + return nil, err + } + var result QueueRemoveAtResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// RemoveMostRecent removes the most recently queued user-facing item (LIFO). +// +// RPC method: session.queue.removeMostRecent. +// +// Returns: Indicates whether a user-facing pending item was removed. +func (a *QueueAPI) RemoveMostRecent(ctx context.Context) (*QueueRemoveMostRecentResult, error) { req := map[string]any{"sessionId": a.sessionID} raw, err := a.client.Request(ctx, "session.queue.removeMostRecent", req) if err != nil { @@ -18284,6 +21157,82 @@ func (a *QueueAPI) RemoveMostRecent(ctx context.Context) (*QueueRemoveMostRecent return &result, nil } +// SendNow moves an addressable queued message into the live turn's steering lane. +// +// RPC method: session.queue.sendNow. +// +// Parameters: Parameters for steering a queued message into a live turn. +// +// Returns: Result of trying to steer a queued message into a live turn. +func (a *QueueAPI) SendNow(ctx context.Context, params *QueueSendNowRequest) (*QueueSendNowResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["id"] = params.ID + } + raw, err := a.client.Request(ctx, "session.queue.sendNow", req) + if err != nil { + return nil, err + } + var result QueueSendNowResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// SetDrainPaused acquires or releases the queued-lane drain pause. +// +// RPC method: session.queue.setDrainPaused. +// +// Parameters: Parameters for acquiring or releasing the queued-lane drain pause. +// Acquisition is exclusive and non-idempotent: `paused: true` against an already-paused +// session fails with `queue_already_paused`. The pause is never released automatically — it +// is not tied to the caller's lifetime, so a client that exits without sending `paused: +// false` leaves the lane frozen. Release is unowned: `paused: false` clears the pause for +// any caller, including one that never acquired it. +func (a *QueueAPI) SetDrainPaused(ctx context.Context, params *QueueSetDrainPausedRequest) (*SessionQueueSetDrainPausedResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["paused"] = params.Paused + } + raw, err := a.client.Request(ctx, "session.queue.setDrainPaused", req) + if err != nil { + return nil, err + } + var result SessionQueueSetDrainPausedResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// UpdateText updates the text of an addressable single-message queue item. +// +// RPC method: session.queue.updateText. +// +// Parameters: Parameters for editing a single queued message. +// +// Returns: Result of editing a queued message. +func (a *QueueAPI) UpdateText(ctx context.Context, params *QueueUpdateTextRequest) (*QueueUpdateTextResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + if params.DisplayPrompt != nil { + req["displayPrompt"] = *params.DisplayPrompt + } + req["id"] = params.ID + req["prompt"] = params.Prompt + } + raw, err := a.client.Request(ctx, "session.queue.updateText", req) + if err != nil { + return nil, err + } + var result QueueUpdateTextResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // Experimental: RemoteAPI contains experimental APIs that may change or be removed. type RemoteAPI sessionAPI @@ -18428,7 +21377,14 @@ func (a *ShellAPI) CancelUserRequested(ctx context.Context, params *ShellCancelU return &result, nil } -// Exec starts a shell command and streams output through session notifications. +// Exec starts a shell command and streams output through session notifications. The command +// runs as the leader of its own process group (POSIX) or in a dedicated job object +// (Windows), so a forced termination — via "shell.kill", the request timeout, or session +// disposal — signals that whole group/job rather than only the direct child. Two gaps are +// worth planning for: a command that exits on its own does not trigger that teardown, and +// on POSIX a descendant that moves itself into a new session or process group (for example +// via "setsid") leaves the signalled group, so either can leave a background process +// running. // // RPC method: session.shell.exec. // @@ -18483,7 +21439,11 @@ func (a *ShellAPI) ExecuteUserRequested(ctx context.Context, params *ShellExecut return &result, nil } -// Kill sends a signal to a shell process previously started via "shell.exec". +// Kill sends a signal to a shell process previously started via "shell.exec". The signal +// targets the command's whole process group (POSIX) or job object (Windows), so descendants +// still in that group are signalled too, not just the direct child. On POSIX a descendant +// that moved itself into a new session or process group (for example via "setsid") is no +// longer in the signalled group and survives. // // RPC method: session.shell.kill. // @@ -19366,6 +22326,49 @@ func (a *VisibilityAPI) Set(ctx context.Context, params *VisibilitySetRequest) ( // Experimental: WorkspacesAPI contains experimental APIs that may change or be removed. type WorkspacesAPI sessionAPI +// AddSummary adds a compaction summary checkpoint to the local session workspace. +// +// RPC method: session.workspaces.addSummary. +// +// Parameters: Compaction summary checkpoint to persist. +// +// Returns: Persisted summary metadata and refreshed workspace metadata. +func (a *WorkspacesAPI) AddSummary(ctx context.Context, params *WorkspacesAddSummaryRequest) (*WorkspacesAddSummaryResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["content"] = params.Content + req["title"] = params.Title + } + raw, err := a.client.Request(ctx, "session.workspaces.addSummary", req) + if err != nil { + return nil, err + } + var result WorkspacesAddSummaryResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// AutopilotObjectiveExists checks whether the local session workspace has an autopilot +// objective state file. +// +// RPC method: session.workspaces.autopilotObjectiveExists. +// +// Returns: Whether the autopilot objective file exists. +func (a *WorkspacesAPI) AutopilotObjectiveExists(ctx context.Context) (*WorkspacesAutopilotObjectiveExistsResult, error) { + req := map[string]any{"sessionId": a.sessionID} + raw, err := a.client.Request(ctx, "session.workspaces.autopilotObjectiveExists", req) + if err != nil { + return nil, err + } + var result WorkspacesAutopilotObjectiveExistsResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // CreateFile creates or overwrites a file in the session workspace files directory. // // RPC method: session.workspaces.createFile. @@ -19388,7 +22391,28 @@ func (a *WorkspacesAPI) CreateFile(ctx context.Context, params *WorkspacesCreate return &result, nil } -// Diff computes a diff for the session workspace. +// DeleteAutopilotObjective deletes the autopilot objective state file from the local +// session workspace. +// +// RPC method: session.workspaces.deleteAutopilotObjective. +// +// Returns: Result of deleting the autopilot objective file. +func (a *WorkspacesAPI) DeleteAutopilotObjective(ctx context.Context) (*WorkspacesDeleteAutopilotObjectiveResult, error) { + req := map[string]any{"sessionId": a.sessionID} + raw, err := a.client.Request(ctx, "session.workspaces.deleteAutopilotObjective", req) + if err != nil { + return nil, err + } + var result WorkspacesDeleteAutopilotObjectiveResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Diff computes a diff for the session workspace. Never rejects for a busy session: a +// `session`-mode diff that cannot read the session's file-change captures falls back to an +// unstaged git diff with `isFallback: true` and reports why in `unavailableReason`. // // RPC method: session.workspaces.diff. // @@ -19414,6 +22438,32 @@ func (a *WorkspacesAPI) Diff(ctx context.Context, params *WorkspacesDiffRequest) return &result, nil } +// Ensures a local session workspace exists and returns it. +// +// RPC method: session.workspaces.ensure. +// +// Parameters: Optional session context used when creating a local workspace. +// +// Returns: Current workspace metadata for the session, including its absolute filesystem +// path when available. +func (a *WorkspacesAPI) Ensure(ctx context.Context, params *WorkspacesEnsureRequest) (*WorkspacesGetWorkspaceResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + if params.Context != nil { + req["context"] = params.Context + } + } + raw, err := a.client.Request(ctx, "session.workspaces.ensure", req) + if err != nil { + return nil, err + } + var result WorkspacesGetWorkspaceResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // GetWorkspace gets current workspace metadata for the session. // // RPC method: session.workspaces.getWorkspace. @@ -19470,6 +22520,25 @@ func (a *WorkspacesAPI) ListFiles(ctx context.Context) (*WorkspacesListFilesResu return &result, nil } +// ReadAutopilotObjective reads the autopilot objective state file from the local session +// workspace. +// +// RPC method: session.workspaces.readAutopilotObjective. +// +// Returns: Autopilot objective file content, or null when missing. +func (a *WorkspacesAPI) ReadAutopilotObjective(ctx context.Context) (*WorkspacesReadAutopilotObjectiveResult, error) { + req := map[string]any{"sessionId": a.sessionID} + raw, err := a.client.Request(ctx, "session.workspaces.readAutopilotObjective", req) + if err != nil { + return nil, err + } + var result WorkspacesReadAutopilotObjectiveResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // ReadCheckpoint reads the content of a workspace checkpoint by number. // // RPC method: session.workspaces.readCheckpoint. @@ -19540,46 +22609,126 @@ func (a *WorkspacesAPI) SaveLargePaste(ctx context.Context, params *WorkspacesSa return &result, nil } +// TruncateSummaries truncates local workspace compaction summaries after a rollback. +// +// RPC method: session.workspaces.truncateSummaries. +// +// Parameters: Rollback point for local workspace summaries. +// +// Returns: Current workspace metadata for the session, including its absolute filesystem +// path when available. +func (a *WorkspacesAPI) TruncateSummaries(ctx context.Context, params *WorkspacesTruncateSummariesRequest) (*WorkspacesGetWorkspaceResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["keepCount"] = params.KeepCount + } + raw, err := a.client.Request(ctx, "session.workspaces.truncateSummaries", req) + if err != nil { + return nil, err + } + var result WorkspacesGetWorkspaceResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// UpdateMetadata updates workspace metadata for a local session and returns the refreshed +// workspace. +// +// RPC method: session.workspaces.updateMetadata. +// +// Parameters: Workspace metadata fields to update. +// +// Returns: Current workspace metadata for the session, including its absolute filesystem +// path when available. +func (a *WorkspacesAPI) UpdateMetadata(ctx context.Context, params *WorkspacesUpdateMetadataRequest) (*WorkspacesGetWorkspaceResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + if params.Context != nil { + req["context"] = params.Context + } + if params.Name != nil { + req["name"] = *params.Name + } + } + raw, err := a.client.Request(ctx, "session.workspaces.updateMetadata", req) + if err != nil { + return nil, err + } + var result WorkspacesGetWorkspaceResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// WriteAutopilotObjective writes the autopilot objective state file in the local session +// workspace. +// +// RPC method: session.workspaces.writeAutopilotObjective. +// +// Parameters: Autopilot objective file content to persist. +// +// Returns: Result of writing the autopilot objective file. +func (a *WorkspacesAPI) WriteAutopilotObjective(ctx context.Context, params *WorkspacesWriteAutopilotObjectiveRequest) (*WorkspacesWriteAutopilotObjectiveResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["content"] = params.Content + } + raw, err := a.client.Request(ctx, "session.workspaces.writeAutopilotObjective", req) + if err != nil { + return nil, err + } + var result WorkspacesWriteAutopilotObjectiveResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // SessionRPC provides typed session-scoped RPC methods. type SessionRPC struct { // Reuse a single struct instead of allocating one for each service on the heap. common sessionAPI - Agent *AgentAPI - Canvas *CanvasAPI - Commands *CommandsAPI - Completions *CompletionsAPI - Debug *DebugAPI - EventLog *EventLogAPI - Extensions *ExtensionsAPI - Factory *FactoryAPI - Fleet *FleetAPI - GitHubAuth *GitHubAuthAPI - History *HistoryAPI - Instructions *InstructionsAPI - Lsp *LspAPI - MCP *MCPAPI - Metadata *MetadataAPI - Mode *ModeAPI - Model *ModelAPI - Name *NameAPI - Options *OptionsAPI - Permissions *PermissionsAPI - Plan *PlanAPI - Plugins *PluginsAPI - Provider *ProviderAPI - Queue *QueueAPI - Remote *RemoteAPI - Schedule *ScheduleAPI - Shell *ShellAPI - Skills *SkillsAPI - Tasks *TasksAPI - Telemetry *TelemetryAPI - Tools *ToolsAPI - UI *UIAPI - Usage *UsageAPI - Visibility *VisibilityAPI - Workspaces *WorkspacesAPI + Agent *AgentAPI + Canvas *CanvasAPI + Commands *CommandsAPI + Completions *CompletionsAPI + ContentExclusion *ContentExclusionAPI + Debug *DebugAPI + EventLog *EventLogAPI + Extensions *ExtensionsAPI + Factory *FactoryAPI + Fleet *FleetAPI + GitHubAuth *GitHubAuthAPI + History *HistoryAPI + Instructions *InstructionsAPI + LimitPrediction *LimitPredictionAPI + Lsp *LspAPI + MCP *MCPAPI + Metadata *MetadataAPI + Mode *ModeAPI + Model *ModelAPI + Name *NameAPI + Options *OptionsAPI + Permissions *PermissionsAPI + Plan *PlanAPI + Plugins *PluginsAPI + Provider *ProviderAPI + Queue *QueueAPI + Remote *RemoteAPI + Schedule *ScheduleAPI + Shell *ShellAPI + Skills *SkillsAPI + Tasks *TasksAPI + Telemetry *TelemetryAPI + Tools *ToolsAPI + UI *UIAPI + Usage *UsageAPI + Visibility *VisibilityAPI + Workspaces *WorkspacesAPI } // Aborts the current agent turn. @@ -19609,18 +22758,70 @@ func (a *SessionRPC) Abort(ctx context.Context, params *AbortRequest) (*AbortRes return &result, nil } -// Log emits a user-visible session log event. -// -// RPC method: session.log. +// CancelAllBackgroundAgents cancels every running background agent (task-registry subagents +// plus sidekick agents) without interrupting the main agent loop. Promoted attached shells +// are left running. // -// Parameters: Message text, optional severity level, persistence flag, optional follow-up -// URL, and optional tip. +// RPC method: session.cancelAllBackgroundAgents. // -// Returns: Identifier of the session event that was emitted for the log message. -// Experimental: Log is an experimental API and may change or be removed in future versions. -func (a *SessionRPC) Log(ctx context.Context, params *LogRequest) (*LogResult, error) { +// Returns: The number of running background agents (task-registry agents) that were +// cancelled. +// Experimental: CancelAllBackgroundAgents is an experimental API and may change or be +// removed in future versions. +func (a *SessionRPC) CancelAllBackgroundAgents(ctx context.Context) (*SessionCancelAllBackgroundAgentsResult, error) { req := map[string]any{"sessionId": a.common.sessionID} - if params != nil { + raw, err := a.common.client.Request(ctx, "session.cancelAllBackgroundAgents", req) + if err != nil { + return nil, err + } + var result SessionCancelAllBackgroundAgentsResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// InterruptMainTurn interrupts the current main agent turn while leaving running background +// work (subagents, sidekicks, and promoted attached shells) alive. No-op when the main loop +// is not processing. +// +// RPC method: session.interruptMainTurn. +// +// Parameters: Parameters for interrupting the main agent turn. +// +// Returns: Result of interrupting the main agent turn. +// Experimental: InterruptMainTurn is an experimental API and may change or be removed in +// future versions. +func (a *SessionRPC) InterruptMainTurn(ctx context.Context, params *InterruptMainTurnRequest) (*InterruptMainTurnResult, error) { + req := map[string]any{"sessionId": a.common.sessionID} + if params != nil { + if params.FlushQueued != nil { + req["flushQueued"] = *params.FlushQueued + } + } + raw, err := a.common.client.Request(ctx, "session.interruptMainTurn", req) + if err != nil { + return nil, err + } + var result InterruptMainTurnResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Log emits a user-visible session log event. +// +// RPC method: session.log. +// +// Parameters: Message text, optional severity level, persistence flag, optional follow-up +// URL, and optional tip. +// +// Returns: Identifier of the session event that was emitted for the log message. +// Experimental: Log is an experimental API and may change or be removed in future versions. +func (a *SessionRPC) Log(ctx context.Context, params *LogRequest) (*LogResult, error) { + req := map[string]any{"sessionId": a.common.sessionID} + if params != nil { if params.Ephemeral != nil { req["ephemeral"] = *params.Ephemeral } @@ -19816,6 +23017,7 @@ func NewSessionRPC(client *jsonrpc2.Client, sessionID string) *SessionRPC { r.Canvas = (*CanvasAPI)(&r.common) r.Commands = (*CommandsAPI)(&r.common) r.Completions = (*CompletionsAPI)(&r.common) + r.ContentExclusion = (*ContentExclusionAPI)(&r.common) r.Debug = (*DebugAPI)(&r.common) r.EventLog = (*EventLogAPI)(&r.common) r.Extensions = (*ExtensionsAPI)(&r.common) @@ -19824,6 +23026,7 @@ func NewSessionRPC(client *jsonrpc2.Client, sessionID string) *SessionRPC { r.GitHubAuth = (*GitHubAuthAPI)(&r.common) r.History = (*HistoryAPI)(&r.common) r.Instructions = (*InstructionsAPI)(&r.common) + r.LimitPrediction = (*LimitPredictionAPI)(&r.common) r.Lsp = (*LspAPI)(&r.common) r.MCP = (*MCPAPI)(&r.common) r.Metadata = (*MetadataAPI)(&r.common) @@ -19969,6 +23172,385 @@ func (a *InternalMCPAPI) UnregisterExternalClient(ctx context.Context, params *M return &result, nil } +// Experimental: InternalQueueAPI contains experimental APIs that may change or be removed. +type InternalQueueAPI internalSessionAPI + +// BeginDeferredIdleDrain begins a native deferred-idle drain when background work has +// quiesced. +// +// RPC method: session.queue.beginDeferredIdleDrain. +// +// Parameters: Inputs for starting a deferred-idle drain. +// +// Returns: Whether a deferred-idle drain should run. +// Internal: BeginDeferredIdleDrain is part of the SDK's internal handshake/plumbing; +// external callers should not use it. +func (a *InternalQueueAPI) BeginDeferredIdleDrain(ctx context.Context, params *QueueBeginDeferredIdleDrainRequest) (*QueueBeginDeferredIdleDrainResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["activeBackgroundWork"] = params.ActiveBackgroundWork + } + raw, err := a.client.Request(ctx, "session.queue.beginDeferredIdleDrain", req) + if err != nil { + return nil, err + } + var result QueueBeginDeferredIdleDrainResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// ConsumeSystemNotifications consumes queued native system notifications matching an +// internal filter. +// +// RPC method: session.queue.consumeSystemNotifications. +// +// Parameters: Internal filter for consuming queued system notifications. +// +// Returns: Indicates whether a user-facing pending item was removed. +// Internal: ConsumeSystemNotifications is part of the SDK's internal handshake/plumbing; +// external callers should not use it. +func (a *InternalQueueAPI) ConsumeSystemNotifications(ctx context.Context, params *QueueConsumeSystemNotificationsRequest) (*QueueRemoveMostRecentResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["filter"] = params.Filter + } + raw, err := a.client.Request(ctx, "session.queue.consumeSystemNotifications", req) + if err != nil { + return nil, err + } + var result QueueRemoveMostRecentResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// DeferSessionIdle marks session.idle as deferred by native background work state. +// +// RPC method: session.queue.deferSessionIdle. +// +// Parameters: Inputs for marking session.idle deferred in native state. +// Internal: DeferSessionIdle is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalQueueAPI) DeferSessionIdle(ctx context.Context, params *QueueDeferSessionIdleRequest) (*SessionQueueDeferSessionIdleResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["aborted"] = params.Aborted + } + raw, err := a.client.Request(ctx, "session.queue.deferSessionIdle", req) + if err != nil { + return nil, err + } + var result SessionQueueDeferSessionIdleResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// EnqueueResumePending enqueues the internal resume-pending wake item when orphan handling +// needs a follow-up turn. +// +// RPC method: session.queue.enqueueResumePending. +// +// Returns: Result of enqueueing the resume-pending wake item. +// Internal: EnqueueResumePending is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalQueueAPI) EnqueueResumePending(ctx context.Context) (*QueueEnqueueResumePendingResult, error) { + req := map[string]any{"sessionId": a.sessionID} + raw, err := a.client.Request(ctx, "session.queue.enqueueResumePending", req) + if err != nil { + return nil, err + } + var result QueueEnqueueResumePendingResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// FinishDeferredIdleDrain finishes a native deferred-idle drain and reports whether to +// drain queue work or emit idle. +// +// RPC method: session.queue.finishDeferredIdleDrain. +// +// Parameters: Inputs for completing a deferred-idle drain. +// +// Returns: Action selected by the native deferred-idle drain. +// Internal: FinishDeferredIdleDrain is part of the SDK's internal handshake/plumbing; +// external callers should not use it. +func (a *InternalQueueAPI) FinishDeferredIdleDrain(ctx context.Context, params *QueueFinishDeferredIdleDrainRequest) (*QueueFinishDeferredIdleDrainResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["activeBackgroundWork"] = params.ActiveBackgroundWork + req["hasPending"] = params.HasPending + } + raw, err := a.client.Request(ctx, "session.queue.finishDeferredIdleDrain", req) + if err != nil { + return nil, err + } + var result QueueFinishDeferredIdleDrainResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// HasPending reports whether the local session has native queued work pending. +// +// RPC method: session.queue.hasPending. +// +// Returns: Whether the native queue has pending work. +// Internal: HasPending is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalQueueAPI) HasPending(ctx context.Context) (*QueueHasPendingResult, error) { + req := map[string]any{"sessionId": a.sessionID} + raw, err := a.client.Request(ctx, "session.queue.hasPending", req) + if err != nil { + return nil, err + } + var result QueueHasPendingResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Process drains the native local-session work queue for in-process session orchestration. +// +// RPC method: session.queue.process. +// Internal: Process is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalQueueAPI) Process(ctx context.Context) (*SessionQueueProcessResult, error) { + req := map[string]any{"sessionId": a.sessionID} + raw, err := a.client.Request(ctx, "session.queue.process", req) + if err != nil { + return nil, err + } + var result SessionQueueProcessResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Snapshot returns the internal native queue snapshot for in-process session orchestration. +// +// RPC method: session.queue.snapshot. +// +// Returns: Internal snapshot of native queue state for local session orchestration. +// Internal: Snapshot is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalQueueAPI) Snapshot(ctx context.Context) (*QueueSnapshotResult, error) { + req := map[string]any{"sessionId": a.sessionID} + raw, err := a.client.Request(ctx, "session.queue.snapshot", req) + if err != nil { + return nil, err + } + var result QueueSnapshotResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Experimental: InternalScheduleAPI contains experimental APIs that may change or be +// removed. +type InternalScheduleAPI internalSessionAPI + +// Add registers a relative-interval scheduled prompt. +// +// RPC method: session.schedule.add. +// +// Parameters: Register a relative-interval scheduled prompt. +// +// Returns: Result of registering or re-arming a scheduled prompt. +// Internal: Add is part of the SDK's internal handshake/plumbing; external callers should +// not use it. +func (a *InternalScheduleAPI) Add(ctx context.Context, params *ScheduleAddRequest) (*ScheduleAddResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + if params.DisplayPrompt != nil { + req["displayPrompt"] = *params.DisplayPrompt + } + req["interval"] = params.Interval + req["prompt"] = params.Prompt + if params.Recurring != nil { + req["recurring"] = *params.Recurring + } + } + raw, err := a.client.Request(ctx, "session.schedule.add", req) + if err != nil { + return nil, err + } + var result ScheduleAddResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// AddAt registers an absolute-time scheduled prompt. +// +// RPC method: session.schedule.addAt. +// +// Parameters: Register an absolute-time scheduled prompt. +// +// Returns: Result of registering or re-arming a scheduled prompt. +// Internal: AddAt is part of the SDK's internal handshake/plumbing; external callers should +// not use it. +func (a *InternalScheduleAPI) AddAt(ctx context.Context, params *ScheduleAddAtRequest) (*ScheduleAddResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["at"] = params.At + if params.DisplayPrompt != nil { + req["displayPrompt"] = *params.DisplayPrompt + } + req["prompt"] = params.Prompt + if params.Recurring != nil { + req["recurring"] = *params.Recurring + } + } + raw, err := a.client.Request(ctx, "session.schedule.addAt", req) + if err != nil { + return nil, err + } + var result ScheduleAddResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// AddCron registers a recurring cron scheduled prompt. +// +// RPC method: session.schedule.addCron. +// +// Parameters: Register a cron scheduled prompt. +// +// Returns: Result of registering or re-arming a scheduled prompt. +// Internal: AddCron is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalScheduleAPI) AddCron(ctx context.Context, params *ScheduleAddCronRequest) (*ScheduleAddResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["cron"] = params.Cron + if params.DisplayPrompt != nil { + req["displayPrompt"] = *params.DisplayPrompt + } + req["prompt"] = params.Prompt + if params.Recurring != nil { + req["recurring"] = *params.Recurring + } + if params.Tz != nil { + req["tz"] = *params.Tz + } + } + raw, err := a.client.Request(ctx, "session.schedule.addCron", req) + if err != nil { + return nil, err + } + var result ScheduleAddResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// AddSelfPaced registers a self-paced scheduled prompt. +// +// RPC method: session.schedule.addSelfPaced. +// +// Parameters: Register a self-paced scheduled prompt. +// +// Returns: Result of registering or re-arming a scheduled prompt. +// Internal: AddSelfPaced is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalScheduleAPI) AddSelfPaced(ctx context.Context, params *ScheduleAddSelfPacedRequest) (*ScheduleAddResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + if params.DisplayPrompt != nil { + req["displayPrompt"] = *params.DisplayPrompt + } + req["prompt"] = params.Prompt + } + raw, err := a.client.Request(ctx, "session.schedule.addSelfPaced", req) + if err != nil { + return nil, err + } + var result ScheduleAddResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// HasSelfPaced reports whether the session has an active self-paced scheduled prompt. +// +// RPC method: session.schedule.hasSelfPaced. +// +// Returns: Whether the session currently has an active self-paced schedule. +// Internal: HasSelfPaced is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalScheduleAPI) HasSelfPaced(ctx context.Context) (*ScheduleHasSelfPacedResult, error) { + req := map[string]any{"sessionId": a.sessionID} + raw, err := a.client.Request(ctx, "session.schedule.hasSelfPaced", req) + if err != nil { + return nil, err + } + var result ScheduleHasSelfPacedResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Hydrates the native schedule registry from persisted session events. +// +// RPC method: session.schedule.hydrate. +// Internal: Hydrate is part of the SDK's internal handshake/plumbing; external callers +// should not use it. +func (a *InternalScheduleAPI) Hydrate(ctx context.Context) (*SessionScheduleHydrateResult, error) { + req := map[string]any{"sessionId": a.sessionID} + raw, err := a.client.Request(ctx, "session.schedule.hydrate", req) + if err != nil { + return nil, err + } + var result SessionScheduleHydrateResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// RearmSelfPaced re-arms an active self-paced scheduled prompt. +// +// RPC method: session.schedule.rearmSelfPaced. +// +// Parameters: Re-arm a self-paced scheduled prompt. +// +// Returns: Result of registering or re-arming a scheduled prompt. +// Internal: RearmSelfPaced is part of the SDK's internal handshake/plumbing; external +// callers should not use it. +func (a *InternalScheduleAPI) RearmSelfPaced(ctx context.Context, params *ScheduleRearmSelfPacedRequest) (*ScheduleAddResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["at"] = params.At + req["id"] = params.ID + } + raw, err := a.client.Request(ctx, "session.schedule.rearmSelfPaced", req) + if err != nil { + return nil, err + } + var result ScheduleAddResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // Experimental: InternalSettingsAPI contains experimental APIs that may change or be // removed. type InternalSettingsAPI internalSessionAPI @@ -20036,13 +23618,49 @@ type InternalSessionRPC struct { common internalSessionAPI MCP *InternalMCPAPI + Queue *InternalQueueAPI + Schedule *InternalScheduleAPI Settings *InternalSettingsAPI } +// SendSystemNotification queues or sends an internal system notification to the session +// according to its passive policy. +// +// RPC method: session.sendSystemNotification. +// +// Parameters: Internal request for sending a system notification. +// Experimental: SendSystemNotification is an experimental API and may change or be removed +// in future versions. +// Internal: SendSystemNotification is part of the SDK's internal handshake/plumbing; +// external callers should not use it. +func (a *InternalSessionRPC) SendSystemNotification(ctx context.Context, params *SendSystemNotificationRequest) (*SessionSendSystemNotificationResult, error) { + req := map[string]any{"sessionId": a.common.sessionID} + if params != nil { + if params.Kind != nil { + req["kind"] = params.Kind + } + req["message"] = params.Message + if params.Options != nil { + req["options"] = params.Options + } + } + raw, err := a.common.client.Request(ctx, "session.sendSystemNotification", req) + if err != nil { + return nil, err + } + var result SessionSendSystemNotificationResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + func NewInternalSessionRPC(client *jsonrpc2.Client, sessionID string) *InternalSessionRPC { r := &InternalSessionRPC{} r.common = internalSessionAPI{client: client, sessionID: sessionID} r.MCP = (*InternalMCPAPI)(&r.common) + r.Queue = (*InternalQueueAPI)(&r.common) + r.Schedule = (*InternalScheduleAPI)(&r.common) r.Settings = (*InternalSettingsAPI)(&r.common) return r } @@ -20202,16 +23820,27 @@ type SessionFSHandler interface { // // Returns: Indicates whether the per-session SQLite database already exists. SqliteExists(request *SessionFSSqliteExistsRequest) (*SessionFSSqliteExistsResult, error) - // SqliteQuery executes a SQLite query against the per-session database. + // SqliteQuery executes a SQLite query against the per-session database. Providers apply + // busy handling for every call. // // RPC method: sessionFs.sqliteQuery. // // Parameters: SQL query, query type, and optional bind parameters for executing a SQLite - // query against the per-session database. + // query against the per-session database. The provider applies its SQLite busy timeout for + // every call. // // Returns: Query results including rows, columns, and rows affected, or a filesystem error // if execution failed. SqliteQuery(request *SessionFSSqliteQueryRequest) (*SessionFSSqliteQueryResult, error) + // SqliteTransaction executes SQLite statements atomically on the provider-owned connection. + // + // RPC method: sessionFs.sqliteTransaction. + // + // Parameters: Statements to execute atomically. Providers apply busy handling for every + // call. + // + // Returns: Per-statement results, or a classified transaction error. + SqliteTransaction(request *SessionFSSqliteTransactionRequest) (*SessionFSSqliteTransactionResult, error) // Stat gets metadata for a path in the client-provided session filesystem. // // RPC method: sessionFs.stat. @@ -20559,6 +24188,25 @@ func RegisterClientSessionAPIHandlers(client *jsonrpc2.Client, getHandlers func( } return raw, nil }) + client.SetRequestHandler("sessionFs.sqliteTransaction", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { + var request SessionFSSqliteTransactionRequest + if err := json.Unmarshal(params, &request); err != nil { + return nil, &jsonrpc2.Error{Code: -32602, Message: fmt.Sprintf("Invalid params: %v", err)} + } + handlers := getHandlers(request.SessionID) + if handlers == nil || handlers.SessionFS == nil { + return nil, &jsonrpc2.Error{Code: -32603, Message: fmt.Sprintf("No sessionFs handler registered for session: %s", request.SessionID)} + } + result, err := handlers.SessionFS.SqliteTransaction(&request) + if err != nil { + return nil, clientSessionHandlerError(err) + } + raw, err := json.Marshal(result) + if err != nil { + return nil, &jsonrpc2.Error{Code: -32603, Message: fmt.Sprintf("Failed to marshal response: %v", err)} + } + return raw, nil + }) client.SetRequestHandler("sessionFs.stat", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { var request SessionFSStatRequest if err := json.Unmarshal(params, &request); err != nil { @@ -20599,6 +24247,23 @@ func RegisterClientSessionAPIHandlers(client *jsonrpc2.Client, getHandlers func( }) } +// Experimental: ExtensionLaunchProviderHandler contains experimental APIs that may change +// or be removed. +type ExtensionLaunchProviderHandler interface { + // Resolve asks the registered SDK client to resolve an opaque process launch profile for + // one discovered extension entrypoint immediately before launch or reload. The provider + // must respond within 15 seconds. + // + // RPC method: extensionLaunchProvider.resolve. + // + // Parameters: A discovered extension entrypoint that the registered integrator may classify + // and resolve to an opaque launch profile. + // + // Returns: The launch profile for a supported entrypoint. Omit launch when the provider + // does not support the entrypoint. + Resolve(request *ExtensionLaunchProviderResolveRequest) (*ExtensionLaunchProviderResolveResult, error) +} + // Experimental: GitHubTelemetryHandler contains experimental APIs that may change or be // removed. type GitHubTelemetryHandler interface { @@ -20665,9 +24330,10 @@ type LlmInferenceHandler interface { // Unlike client-session handlers these carry no implicit session id dispatch // key; a single set of handlers serves the entire connection. type ClientGlobalAPIHandlers struct { - GitHubTelemetry GitHubTelemetryHandler - Hooks HooksHandler - LlmInference LlmInferenceHandler + ExtensionLaunchProvider ExtensionLaunchProviderHandler + GitHubTelemetry GitHubTelemetryHandler + Hooks HooksHandler + LlmInference LlmInferenceHandler } func clientGlobalHandlerError(err error) *jsonrpc2.Error { @@ -20684,6 +24350,24 @@ func clientGlobalHandlerError(err error) *jsonrpc2.Error { // RegisterClientGlobalAPIHandlers registers handlers for server-to-client client-global API // calls. func RegisterClientGlobalAPIHandlers(client *jsonrpc2.Client, handlers *ClientGlobalAPIHandlers) { + client.SetRequestHandler("extensionLaunchProvider.resolve", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { + var request ExtensionLaunchProviderResolveRequest + if err := json.Unmarshal(params, &request); err != nil { + return nil, &jsonrpc2.Error{Code: -32602, Message: fmt.Sprintf("Invalid params: %v", err)} + } + if handlers == nil || handlers.ExtensionLaunchProvider == nil { + return nil, &jsonrpc2.Error{Code: -32603, Message: "No extensionLaunchProvider client-global handler registered"} + } + result, err := handlers.ExtensionLaunchProvider.Resolve(&request) + if err != nil { + return nil, clientGlobalHandlerError(err) + } + raw, err := json.Marshal(result) + if err != nil { + return nil, &jsonrpc2.Error{Code: -32603, Message: fmt.Sprintf("Failed to marshal response: %v", err)} + } + return raw, nil + }) client.SetRequestHandler("gitHubTelemetry.event", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { var request GitHubTelemetryNotification if err := json.Unmarshal(params, &request); err != nil { diff --git a/go/rpc/zrpc_encoding.go b/go/rpc/zrpc_encoding.go index 82f6e1077..29c253e1c 100644 --- a/go/rpc/zrpc_encoding.go +++ b/go/rpc/zrpc_encoding.go @@ -1075,6 +1075,18 @@ func unmarshalFactoryRunFailure(data []byte) (FactoryRunFailure, error) { } switch raw.Type { + case FactoryRunFailureTypeFactoryAccountingIncomplete: + var d FactoryRunFailureFactoryAccountingIncomplete + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case FactoryRunFailureTypeFactoryDurableFailure: + var d FactoryRunFailureFactoryDurableFailure + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil case FactoryRunFailureTypeFactoryLimitReached: var d FactoryRunFailureFactoryLimitReached if err := json.Unmarshal(data, &d); err != nil { @@ -1103,6 +1115,28 @@ func (r RawFactoryRunFailureData) MarshalJSON() ([]byte, error) { }) } +func (r FactoryRunFailureFactoryAccountingIncomplete) MarshalJSON() ([]byte, error) { + type alias FactoryRunFailureFactoryAccountingIncomplete + return json.Marshal(struct { + Type FactoryRunFailureType `json:"type"` + alias + }{ + Type: r.Type(), + alias: alias(r), + }) +} + +func (r FactoryRunFailureFactoryDurableFailure) MarshalJSON() ([]byte, error) { + type alias FactoryRunFailureFactoryDurableFailure + return json.Marshal(struct { + Type FactoryRunFailureType `json:"type"` + alias + }{ + Type: r.Type(), + alias: alias(r), + }) +} + func (r FactoryRunFailureFactoryLimitReached) MarshalJSON() ([]byte, error) { type alias FactoryRunFailureFactoryLimitReached return json.Marshal(struct { @@ -1125,6 +1159,30 @@ func (r FactoryRunFailureFactoryResumeDeclined) MarshalJSON() ([]byte, error) { }) } +func (r *FactoryRunTerminal) UnmarshalJSON(data []byte) error { + type rawFactoryRunTerminal struct { + Error *string `json:"error,omitempty"` + Failure json.RawMessage `json:"failure,omitempty"` + Reason *string `json:"reason,omitempty"` + ResultPreview *string `json:"resultPreview,omitempty"` + } + var raw rawFactoryRunTerminal + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + r.Error = raw.Error + if raw.Failure != nil { + value, err := unmarshalFactoryRunFailure(raw.Failure) + if err != nil { + return err + } + r.Failure = value + } + r.Reason = raw.Reason + r.ResultPreview = raw.ResultPreview + return nil +} + func (r *FactoryRunResult) UnmarshalJSON(data []byte) error { type rawFactoryRunResult struct { Error *string `json:"error,omitempty"` @@ -1327,6 +1385,7 @@ func (r *MCPServerConfigHTTP) UnmarshalJSON(data []byte) error { type rawMCPServerConfigHTTP struct { Auth json.RawMessage `json:"auth,omitempty"` DeferTools *MCPServerConfigDeferTools `json:"deferTools,omitempty"` + DisableToolCache *bool `json:"disableToolCache,omitempty"` FilterMapping json.RawMessage `json:"filterMapping,omitempty"` Headers map[string]string `json:"headers,omitzero"` IsDefaultServer *bool `json:"isDefaultServer,omitempty"` @@ -1351,6 +1410,7 @@ func (r *MCPServerConfigHTTP) UnmarshalJSON(data []byte) error { r.Auth = value } r.DeferTools = raw.DeferTools + r.DisableToolCache = raw.DisableToolCache if raw.FilterMapping != nil { value, err := unmarshalFilterMapping(raw.FilterMapping) if err != nil { @@ -1379,17 +1439,18 @@ func (r *MCPServerConfigHTTP) UnmarshalJSON(data []byte) error { func (r *MCPServerConfigStdio) UnmarshalJSON(data []byte) error { type rawMCPServerConfigStdio struct { - Args []string `json:"args,omitzero"` - Auth json.RawMessage `json:"auth,omitempty"` - Command string `json:"command"` - Cwd *string `json:"cwd,omitempty"` - DeferTools *MCPServerConfigDeferTools `json:"deferTools,omitempty"` - Env map[string]string `json:"env,omitzero"` - FilterMapping json.RawMessage `json:"filterMapping,omitempty"` - IsDefaultServer *bool `json:"isDefaultServer,omitempty"` - Oidc json.RawMessage `json:"oidc,omitempty"` - Timeout *int64 `json:"timeout,omitempty"` - Tools []string `json:"tools,omitzero"` + Args []string `json:"args,omitzero"` + Auth json.RawMessage `json:"auth,omitempty"` + Command string `json:"command"` + Cwd *string `json:"cwd,omitempty"` + DeferTools *MCPServerConfigDeferTools `json:"deferTools,omitempty"` + DisableToolCache *bool `json:"disableToolCache,omitempty"` + Env map[string]string `json:"env,omitzero"` + FilterMapping json.RawMessage `json:"filterMapping,omitempty"` + IsDefaultServer *bool `json:"isDefaultServer,omitempty"` + Oidc json.RawMessage `json:"oidc,omitempty"` + Timeout *int64 `json:"timeout,omitempty"` + Tools []string `json:"tools,omitzero"` } var raw rawMCPServerConfigStdio if err := json.Unmarshal(data, &raw); err != nil { @@ -1406,6 +1467,7 @@ func (r *MCPServerConfigStdio) UnmarshalJSON(data []byte) error { r.Command = raw.Command r.Cwd = raw.Cwd r.DeferTools = raw.DeferTools + r.DisableToolCache = raw.DisableToolCache r.Env = raw.Env if raw.FilterMapping != nil { value, err := unmarshalFilterMapping(raw.FilterMapping) @@ -1676,7 +1738,7 @@ func (r *MCPRestartServerRequest) UnmarshalJSON(data []byte) error { func (r *MCPStartServerRequest) UnmarshalJSON(data []byte) error { type rawMCPStartServerRequest struct { - Config json.RawMessage `json:"config"` + Config json.RawMessage `json:"config,omitempty"` ServerName string `json:"serverName"` } var raw rawMCPStartServerRequest @@ -1861,6 +1923,12 @@ func unmarshalUserToolSessionApproval(data []byte) (UserToolSessionApproval, err return nil, err } return &d, nil + case UserToolSessionApprovalKindFactory: + var d UserToolSessionApprovalFactory + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil case UserToolSessionApprovalKindMCP: var d UserToolSessionApprovalMCP if err := json.Unmarshal(data, &d); err != nil { @@ -1945,6 +2013,17 @@ func (r UserToolSessionApprovalExtensionPermissionAccess) MarshalJSON() ([]byte, }) } +func (r UserToolSessionApprovalFactory) MarshalJSON() ([]byte, error) { + type alias UserToolSessionApprovalFactory + return json.Marshal(struct { + Kind UserToolSessionApprovalKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + func (r UserToolSessionApprovalMCP) MarshalJSON() ([]byte, error) { type alias UserToolSessionApprovalMCP return json.Marshal(struct { @@ -2086,6 +2165,12 @@ func unmarshalPermissionDecisionApproveForLocationApproval(data []byte) (Permiss return nil, err } return &d, nil + case PermissionDecisionApproveForLocationApprovalKindFactory: + var d PermissionDecisionApproveForLocationApprovalFactory + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil case PermissionDecisionApproveForLocationApprovalKindMCP: var d PermissionDecisionApproveForLocationApprovalMCP if err := json.Unmarshal(data, &d); err != nil { @@ -2176,6 +2261,17 @@ func (r PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess) M }) } +func (r PermissionDecisionApproveForLocationApprovalFactory) MarshalJSON() ([]byte, error) { + type alias PermissionDecisionApproveForLocationApprovalFactory + return json.Marshal(struct { + Kind PermissionDecisionApproveForLocationApprovalKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + func (r PermissionDecisionApproveForLocationApprovalMCP) MarshalJSON() ([]byte, error) { type alias PermissionDecisionApproveForLocationApprovalMCP return json.Marshal(struct { @@ -2299,6 +2395,12 @@ func unmarshalPermissionDecisionApproveForSessionApproval(data []byte) (Permissi return nil, err } return &d, nil + case PermissionDecisionApproveForSessionApprovalKindFactory: + var d PermissionDecisionApproveForSessionApprovalFactory + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil case PermissionDecisionApproveForSessionApprovalKindMCP: var d PermissionDecisionApproveForSessionApprovalMCP if err := json.Unmarshal(data, &d); err != nil { @@ -2389,6 +2491,17 @@ func (r PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess) Ma }) } +func (r PermissionDecisionApproveForSessionApprovalFactory) MarshalJSON() ([]byte, error) { + type alias PermissionDecisionApproveForSessionApprovalFactory + return json.Marshal(struct { + Kind PermissionDecisionApproveForSessionApprovalKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + func (r PermissionDecisionApproveForSessionApprovalMCP) MarshalJSON() ([]byte, error) { type alias PermissionDecisionApproveForSessionApprovalMCP return json.Marshal(struct { @@ -2587,13 +2700,15 @@ func (r PermissionDecisionUserNotAvailable) MarshalJSON() ([]byte, error) { func (r *PermissionDecisionRequest) UnmarshalJSON(data []byte) error { type rawPermissionDecisionRequest struct { - RequestID string `json:"requestId"` - Result json.RawMessage `json:"result"` + DecisionContext *PermissionDecisionContext `json:"decisionContext,omitempty"` + RequestID string `json:"requestId"` + Result json.RawMessage `json:"result"` } var raw rawPermissionDecisionRequest if err := json.Unmarshal(data, &raw); err != nil { return err } + r.DecisionContext = raw.DecisionContext r.RequestID = raw.RequestID if raw.Result != nil { value, err := unmarshalPermissionDecision(raw.Result) @@ -2642,6 +2757,12 @@ func unmarshalPermissionsLocationsAddToolApprovalDetails(data []byte) (Permissio return nil, err } return &d, nil + case PermissionsLocationsAddToolApprovalDetailsKindFactory: + var d PermissionsLocationsAddToolApprovalDetailsFactory + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil case PermissionsLocationsAddToolApprovalDetailsKindMCP: var d PermissionsLocationsAddToolApprovalDetailsMCP if err := json.Unmarshal(data, &d); err != nil { @@ -2732,6 +2853,17 @@ func (r PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess) Mar }) } +func (r PermissionsLocationsAddToolApprovalDetailsFactory) MarshalJSON() ([]byte, error) { + type alias PermissionsLocationsAddToolApprovalDetailsFactory + return json.Marshal(struct { + Kind PermissionsLocationsAddToolApprovalDetailsKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + func (r PermissionsLocationsAddToolApprovalDetailsMCP) MarshalJSON() ([]byte, error) { type alias PermissionsLocationsAddToolApprovalDetailsMCP return json.Marshal(struct { @@ -3091,6 +3223,49 @@ func (r PushAttachmentSelection) MarshalJSON() ([]byte, error) { }) } +func (r *QueueInsertMessage) UnmarshalJSON(data []byte) error { + type rawQueueInsertMessage struct { + AgentMode *SendAgentMode `json:"agentMode,omitempty"` + Attachments []json.RawMessage `json:"attachments,omitzero"` + Billable *bool `json:"billable,omitempty"` + Delivery *string `json:"delivery,omitempty"` + DisplayPrompt *string `json:"displayPrompt,omitempty"` + Mode *SendMode `json:"mode,omitempty"` + Prepend *bool `json:"prepend,omitempty"` + Prompt string `json:"prompt"` + RequestHeaders map[string]string `json:"requestHeaders,omitzero"` + RequiredTool *string `json:"requiredTool,omitempty"` + Source *string `json:"source,omitempty"` + Wait *bool `json:"wait,omitempty"` + } + var raw rawQueueInsertMessage + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + r.AgentMode = raw.AgentMode + if raw.Attachments != nil { + r.Attachments = make([]Attachment, 0, len(raw.Attachments)) + for _, rawItem := range raw.Attachments { + value, err := unmarshalAttachment(rawItem) + if err != nil { + return err + } + r.Attachments = append(r.Attachments, value) + } + } + r.Billable = raw.Billable + r.Delivery = raw.Delivery + r.DisplayPrompt = raw.DisplayPrompt + r.Mode = raw.Mode + r.Prepend = raw.Prepend + r.Prompt = raw.Prompt + r.RequestHeaders = raw.RequestHeaders + r.RequiredTool = raw.RequiredTool + r.Source = raw.Source + r.Wait = raw.Wait + return nil +} + func unmarshalRemoteControlStatus(data []byte) (RemoteControlStatus, error) { if string(data) == "null" { return nil, nil @@ -3397,6 +3572,69 @@ func (r *SessionInstalledPluginSource) UnmarshalJSON(data []byte) error { return errors.New("data did not match any union variant for SessionInstalledPluginSource") } +func unmarshalSessionLimitPredictionResult(data []byte) (SessionLimitPredictionResult, error) { + if string(data) == "null" { + return nil, nil + } + type rawUnion struct { + Kind SessionLimitPredictionResultKind `json:"kind"` + } + var raw rawUnion + if err := json.Unmarshal(data, &raw); err != nil { + return nil, err + } + + switch raw.Kind { + case SessionLimitPredictionResultKindAvailable: + var d SessionLimitPredictionResultAvailable + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + case SessionLimitPredictionResultKindUnavailable: + var d SessionLimitPredictionResultUnavailable + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil + default: + return &RawSessionLimitPredictionResultData{Discriminator: raw.Kind, Raw: data}, nil + } +} + +func (r RawSessionLimitPredictionResultData) MarshalJSON() ([]byte, error) { + if r.Raw != nil { + return r.Raw, nil + } + return json.Marshal(struct { + Kind SessionLimitPredictionResultKind `json:"kind"` + }{ + Kind: r.Discriminator, + }) +} + +func (r SessionLimitPredictionResultAvailable) MarshalJSON() ([]byte, error) { + type alias SessionLimitPredictionResultAvailable + return json.Marshal(struct { + Kind SessionLimitPredictionResultKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + +func (r SessionLimitPredictionResultUnavailable) MarshalJSON() ([]byte, error) { + type alias SessionLimitPredictionResultUnavailable + return json.Marshal(struct { + Kind SessionLimitPredictionResultKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + func unmarshalSessionListEntry(data []byte) (SessionListEntry, error) { if string(data) == "null" { return nil, nil @@ -3475,6 +3713,7 @@ func (r *SessionList) UnmarshalJSON(data []byte) error { func (r *SessionOpenOptions) UnmarshalJSON(data []byte) error { type rawSessionOpenOptions struct { AdditionalContentExclusionPolicies []SessionOpenOptionsAdditionalContentExclusionPolicy `json:"additionalContentExclusionPolicies,omitzero"` + AdditionalDirectories []string `json:"additionalDirectories,omitzero"` AgentContext *string `json:"agentContext,omitempty"` AllowAllMCPServerInstructions *bool `json:"allowAllMcpServerInstructions,omitempty"` AskUserDisabled *bool `json:"askUserDisabled,omitempty"` @@ -3491,14 +3730,17 @@ func (r *SessionOpenOptions) UnmarshalJSON(data []byte) error { DetachedFromSpawningParentEngagementID *string `json:"detachedFromSpawningParentEngagementId,omitempty"` DetachedFromSpawningParentSessionID *string `json:"detachedFromSpawningParentSessionId,omitempty"` DisabledInstructionSources []string `json:"disabledInstructionSources,omitzero"` + DisabledMCPServers []string `json:"disabledMcpServers,omitzero"` DisabledSkills []string `json:"disabledSkills,omitzero"` EnableCitations *bool `json:"enableCitations,omitempty"` + EnableFileChangeTracking *bool `json:"enableFileChangeTracking,omitempty"` EnableManagedSettings *bool `json:"enableManagedSettings,omitempty"` EnableOnDemandInstructionDiscovery *bool `json:"enableOnDemandInstructionDiscovery,omitempty"` EnableScriptSafety *bool `json:"enableScriptSafety,omitempty"` EnableStreaming *bool `json:"enableStreaming,omitempty"` EnvValueMode *SessionOpenOptionsEnvValueMode `json:"envValueMode,omitempty"` EventsLogDirectory *string `json:"eventsLogDirectory,omitempty"` + EventsLogIncludesSubagents *bool `json:"eventsLogIncludesSubagents,omitempty"` ExcludedBuiltinAgents []string `json:"excludedBuiltinAgents,omitzero"` ExcludedTools []string `json:"excludedTools,omitzero"` ExpAssignments any `json:"expAssignments,omitempty"` @@ -3509,6 +3751,7 @@ func (r *SessionOpenOptions) UnmarshalJSON(data []byte) error { IsExperimentalMode *bool `json:"isExperimentalMode,omitempty"` LogInteractiveShells *bool `json:"logInteractiveShells,omitempty"` LspClientName *string `json:"lspClientName,omitempty"` + ManagedSettings *SessionManagedSettings `json:"managedSettings,omitempty"` MaxInlineBinaryBytes *int64 `json:"maxInlineBinaryBytes,omitempty"` Memory *MemoryConfiguration `json:"memory,omitempty"` Model *string `json:"model,omitempty"` @@ -3527,6 +3770,7 @@ func (r *SessionOpenOptions) UnmarshalJSON(data []byte) error { SessionCapabilities []SessionCapability `json:"sessionCapabilities,omitzero"` SessionID *string `json:"sessionId,omitempty"` SessionLimits *SessionLimitsConfig `json:"sessionLimits,omitempty"` + Shell *ShellOptions `json:"shell,omitempty"` ShellInitProfile *string `json:"shellInitProfile,omitempty"` ShellProcessFlags []string `json:"shellProcessFlags,omitzero"` SkillDirectories []string `json:"skillDirectories,omitzero"` @@ -3541,6 +3785,7 @@ func (r *SessionOpenOptions) UnmarshalJSON(data []byte) error { return err } r.AdditionalContentExclusionPolicies = raw.AdditionalContentExclusionPolicies + r.AdditionalDirectories = raw.AdditionalDirectories r.AgentContext = raw.AgentContext r.AllowAllMCPServerInstructions = raw.AllowAllMCPServerInstructions r.AskUserDisabled = raw.AskUserDisabled @@ -3563,14 +3808,17 @@ func (r *SessionOpenOptions) UnmarshalJSON(data []byte) error { r.DetachedFromSpawningParentEngagementID = raw.DetachedFromSpawningParentEngagementID r.DetachedFromSpawningParentSessionID = raw.DetachedFromSpawningParentSessionID r.DisabledInstructionSources = raw.DisabledInstructionSources + r.DisabledMCPServers = raw.DisabledMCPServers r.DisabledSkills = raw.DisabledSkills r.EnableCitations = raw.EnableCitations + r.EnableFileChangeTracking = raw.EnableFileChangeTracking r.EnableManagedSettings = raw.EnableManagedSettings r.EnableOnDemandInstructionDiscovery = raw.EnableOnDemandInstructionDiscovery r.EnableScriptSafety = raw.EnableScriptSafety r.EnableStreaming = raw.EnableStreaming r.EnvValueMode = raw.EnvValueMode r.EventsLogDirectory = raw.EventsLogDirectory + r.EventsLogIncludesSubagents = raw.EventsLogIncludesSubagents r.ExcludedBuiltinAgents = raw.ExcludedBuiltinAgents r.ExcludedTools = raw.ExcludedTools r.ExpAssignments = raw.ExpAssignments @@ -3581,6 +3829,7 @@ func (r *SessionOpenOptions) UnmarshalJSON(data []byte) error { r.IsExperimentalMode = raw.IsExperimentalMode r.LogInteractiveShells = raw.LogInteractiveShells r.LspClientName = raw.LspClientName + r.ManagedSettings = raw.ManagedSettings r.MaxInlineBinaryBytes = raw.MaxInlineBinaryBytes r.Memory = raw.Memory r.Model = raw.Model @@ -3599,6 +3848,7 @@ func (r *SessionOpenOptions) UnmarshalJSON(data []byte) error { r.SessionCapabilities = raw.SessionCapabilities r.SessionID = raw.SessionID r.SessionLimits = raw.SessionLimits + r.Shell = raw.Shell r.ShellInitProfile = raw.ShellInitProfile r.ShellProcessFlags = raw.ShellProcessFlags r.SkillDirectories = raw.SkillDirectories diff --git a/go/rpc/zsession_encoding.go b/go/rpc/zsession_encoding.go index fe99126cf..05e466012 100644 --- a/go/rpc/zsession_encoding.go +++ b/go/rpc/zsession_encoding.go @@ -203,6 +203,12 @@ func (e *SessionEvent) UnmarshalJSON(data []byte) error { return err } e.Data = &d + case SessionEventTypeFactoryRunUpdated: + var d FactoryRunUpdatedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d case SessionEventTypeHookEnd: var d HookEndData if err := json.Unmarshal(raw.Data, &d); err != nil { @@ -389,6 +395,12 @@ func (e *SessionEvent) UnmarshalJSON(data []byte) error { return err } e.Data = &d + case SessionEventTypeSessionContextCleared: + var d SessionContextClearedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d case SessionEventTypeSessionCustomAgentsUpdated: var d SessionCustomAgentsUpdatedData if err := json.Unmarshal(raw.Data, &d); err != nil { @@ -1346,6 +1358,12 @@ func unmarshalSystemNotification(data []byte) (SystemNotification, error) { return nil, err } return &d, nil + case SystemNotificationTypeFactoryCompleted: + var d SystemNotificationFactoryCompleted + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil case SystemNotificationTypeInstructionDiscovered: var d SystemNotificationInstructionDiscovered if err := json.Unmarshal(data, &d); err != nil { @@ -1370,6 +1388,12 @@ func unmarshalSystemNotification(data []byte) (SystemNotification, error) { return nil, err } return &d, nil + case SystemNotificationTypeUnclassified: + var d SystemNotificationUnclassified + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil default: return &RawSystemNotification{Discriminator: raw.Type, Raw: data}, nil } @@ -1408,6 +1432,17 @@ func (r SystemNotificationAgentIdle) MarshalJSON() ([]byte, error) { }) } +func (r SystemNotificationFactoryCompleted) MarshalJSON() ([]byte, error) { + type alias SystemNotificationFactoryCompleted + return json.Marshal(struct { + Type SystemNotificationType `json:"type"` + alias + }{ + Type: r.Type(), + alias: alias(r), + }) +} + func (r SystemNotificationInstructionDiscovered) MarshalJSON() ([]byte, error) { type alias SystemNotificationInstructionDiscovered return json.Marshal(struct { @@ -1452,6 +1487,17 @@ func (r SystemNotificationShellDetachedCompleted) MarshalJSON() ([]byte, error) }) } +func (r SystemNotificationUnclassified) MarshalJSON() ([]byte, error) { + type alias SystemNotificationUnclassified + return json.Marshal(struct { + Type SystemNotificationType `json:"type"` + alias + }{ + Type: r.Type(), + alias: alias(r), + }) +} + func (r *SystemNotificationData) UnmarshalJSON(data []byte) error { type rawSystemNotificationData struct { Content string `json:"content"` @@ -1503,6 +1549,12 @@ func unmarshalPermissionRequest(data []byte) (PermissionRequest, error) { return nil, err } return &d, nil + case PermissionRequestKindFactory: + var d PermissionRequestFactory + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil case PermissionRequestKindHook: var d PermissionRequestHook if err := json.Unmarshal(data, &d); err != nil { @@ -1594,6 +1646,17 @@ func (r PermissionRequestExtensionPermissionAccess) MarshalJSON() ([]byte, error }) } +func (r PermissionRequestFactory) MarshalJSON() ([]byte, error) { + type alias PermissionRequestFactory + return json.Marshal(struct { + Kind PermissionRequestKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + func (r PermissionRequestHook) MarshalJSON() ([]byte, error) { type alias PermissionRequestHook return json.Marshal(struct { @@ -1708,6 +1771,12 @@ func unmarshalPermissionPromptRequest(data []byte) (PermissionPromptRequest, err return nil, err } return &d, nil + case PermissionPromptRequestKindFactory: + var d PermissionPromptRequestFactory + if err := json.Unmarshal(data, &d); err != nil { + return nil, err + } + return &d, nil case PermissionPromptRequestKindHook: var d PermissionPromptRequestHook if err := json.Unmarshal(data, &d); err != nil { @@ -1810,6 +1879,17 @@ func (r PermissionPromptRequestExtensionPermissionAccess) MarshalJSON() ([]byte, }) } +func (r PermissionPromptRequestFactory) MarshalJSON() ([]byte, error) { + type alias PermissionPromptRequestFactory + return json.Marshal(struct { + Kind PermissionPromptRequestKind `json:"kind"` + alias + }{ + Kind: r.Kind(), + alias: alias(r), + }) +} + func (r PermissionPromptRequestHook) MarshalJSON() ([]byte, error) { type alias PermissionPromptRequestHook return json.Marshal(struct { @@ -1893,6 +1973,7 @@ func (r *PermissionRequestedData) UnmarshalJSON(data []byte) error { PromptRequest json.RawMessage `json:"promptRequest,omitempty"` RequestID string `json:"requestId"` ResolvedByHook *bool `json:"resolvedByHook,omitempty"` + RiskAssessment any `json:"riskAssessment,omitempty"` } var raw rawPermissionRequestedData if err := json.Unmarshal(data, &raw); err != nil { @@ -1914,6 +1995,7 @@ func (r *PermissionRequestedData) UnmarshalJSON(data []byte) error { } r.RequestID = raw.RequestID r.ResolvedByHook = raw.ResolvedByHook + r.RiskAssessment = raw.RiskAssessment return nil } diff --git a/go/rpc/zsession_events.go b/go/rpc/zsession_events.go index 44f49948f..05c8fd548 100644 --- a/go/rpc/zsession_events.go +++ b/go/rpc/zsession_events.go @@ -81,24 +81,27 @@ const ( SessionEventTypeExitPlanModeRequested SessionEventType = "exit_plan_mode.requested" SessionEventTypeExternalToolCompleted SessionEventType = "external_tool.completed" SessionEventTypeExternalToolRequested SessionEventType = "external_tool.requested" - SessionEventTypeHookEnd SessionEventType = "hook.end" - SessionEventTypeHookProgress SessionEventType = "hook.progress" - SessionEventTypeHookStart SessionEventType = "hook.start" - SessionEventTypeMCPAppToolCallComplete SessionEventType = "mcp_app.tool_call_complete" - SessionEventTypeMCPHeadersRefreshCompleted SessionEventType = "mcp.headers_refresh_completed" - SessionEventTypeMCPHeadersRefreshRequired SessionEventType = "mcp.headers_refresh_required" - SessionEventTypeMCPOauthCompleted SessionEventType = "mcp.oauth_completed" - SessionEventTypeMCPOauthRequired SessionEventType = "mcp.oauth_required" - SessionEventTypeMCPPromptsListChanged SessionEventType = "mcp.prompts.list_changed" - SessionEventTypeMCPResourcesListChanged SessionEventType = "mcp.resources.list_changed" - SessionEventTypeMCPToolsListChanged SessionEventType = "mcp.tools.list_changed" - SessionEventTypeModelCallFailure SessionEventType = "model.call_failure" - SessionEventTypeModelCallStart SessionEventType = "model.call_start" - SessionEventTypePendingMessagesModified SessionEventType = "pending_messages.modified" - SessionEventTypePermissionCompleted SessionEventType = "permission.completed" - SessionEventTypePermissionRequested SessionEventType = "permission.requested" - SessionEventTypeSamplingCompleted SessionEventType = "sampling.completed" - SessionEventTypeSamplingRequested SessionEventType = "sampling.requested" + // Experimental: SessionEventTypeFactoryRunUpdated identifies an experimental event that may + // change or be removed. + SessionEventTypeFactoryRunUpdated SessionEventType = "factory.run_updated" + SessionEventTypeHookEnd SessionEventType = "hook.end" + SessionEventTypeHookProgress SessionEventType = "hook.progress" + SessionEventTypeHookStart SessionEventType = "hook.start" + SessionEventTypeMCPAppToolCallComplete SessionEventType = "mcp_app.tool_call_complete" + SessionEventTypeMCPHeadersRefreshCompleted SessionEventType = "mcp.headers_refresh_completed" + SessionEventTypeMCPHeadersRefreshRequired SessionEventType = "mcp.headers_refresh_required" + SessionEventTypeMCPOauthCompleted SessionEventType = "mcp.oauth_completed" + SessionEventTypeMCPOauthRequired SessionEventType = "mcp.oauth_required" + SessionEventTypeMCPPromptsListChanged SessionEventType = "mcp.prompts.list_changed" + SessionEventTypeMCPResourcesListChanged SessionEventType = "mcp.resources.list_changed" + SessionEventTypeMCPToolsListChanged SessionEventType = "mcp.tools.list_changed" + SessionEventTypeModelCallFailure SessionEventType = "model.call_failure" + SessionEventTypeModelCallStart SessionEventType = "model.call_start" + SessionEventTypePendingMessagesModified SessionEventType = "pending_messages.modified" + SessionEventTypePermissionCompleted SessionEventType = "permission.completed" + SessionEventTypePermissionRequested SessionEventType = "permission.requested" + SessionEventTypeSamplingCompleted SessionEventType = "sampling.completed" + SessionEventTypeSamplingRequested SessionEventType = "sampling.requested" // Experimental: SessionEventTypeSessionAutoModeResolved identifies an experimental event // that may change or be removed. SessionEventTypeSessionAutoModeResolved SessionEventType = "session.auto_mode_resolved" @@ -128,6 +131,7 @@ const ( SessionEventTypeSessionCompactionComplete SessionEventType = "session.compaction_complete" SessionEventTypeSessionCompactionStart SessionEventType = "session.compaction_start" SessionEventTypeSessionContextChanged SessionEventType = "session.context_changed" + SessionEventTypeSessionContextCleared SessionEventType = "session.context_cleared" SessionEventTypeSessionCustomAgentsUpdated SessionEventType = "session.custom_agents_updated" SessionEventTypeSessionCustomNotification SessionEventType = "session.custom_notification" SessionEventTypeSessionError SessionEventType = "session.error" @@ -214,6 +218,7 @@ type AssistantReasoningData struct { Content string `json:"content"` // Unique identifier for this reasoning block ReasoningID string `json:"reasoningId"` + Rte *bool `json:"rte,omitempty"` } func (*AssistantReasoningData) sessionEventData() {} @@ -223,6 +228,10 @@ func (*AssistantReasoningData) Type() SessionEventType { return SessionEventType type AssistantMessageData struct { // Provider's completion / response identifier; shared across all chunks of a single API call. Used to group multi-chunk assistant utterances. APICallID *string `json:"apiCallId,omitempty"` + // Total messages the model call's response was split into, one per reasoning boundary. Absent for a single-message response; the last chunk is the one where chunkIndex is chunkCount - 1. + ChunkCount *int64 `json:"chunkCount,omitempty"` + // Zero-based position of this message within its model call's response. Absent when the response was not split into chunks. + ChunkIndex *int64 `json:"chunkIndex,omitempty"` // Provider-agnostic citations linking spans of this message's content to the sources that support them. Experimental; only populated when citation emission is enabled. // Experimental: Citations is part of an experimental API and may change or be removed. Citations *Citations `json:"citations,omitempty"` @@ -253,6 +262,7 @@ type AssistantMessageData struct { ReasoningWireField *string `json:"reasoningWireField,omitempty"` // GitHub request tracing ID (x-github-request-id header) for correlating with server-side logs RequestID *string `json:"requestId,omitempty"` + Rte *bool `json:"rte,omitempty"` // Neutral provider-tagged server-side tool-use payload (tool search, advisor) for verbatim round-tripping ServerTools *AssistantMessageServerTools `json:"serverTools,omitempty"` // Copilot service request ID (x-copilot-service-request-id header) for CAPI log correlation @@ -269,18 +279,36 @@ func (*AssistantMessageData) Type() SessionEventType { return SessionEventTypeAs // Auto Intent resolution: the concrete model the session settled on for the first prompt of an auto-mode session, and why. Lets SDK clients render the chosen model and the full reason it was picked. The core selection fields (chosenModel/reasoningBucket/categoryScores) are stable; the routing-analytics fields (predictedLabel/confidence/candidateModels) mirror the upstream intent service and may evolve, hence the event's experimental stability. // Experimental: SessionAutoModeResolvedData is part of an experimental API and may change or be removed. type SessionAutoModeResolvedData struct { + // Models offered to the router for this resolution + AvailableModels []string `json:"availableModels,omitzero"` // Ordered candidate model list the router returned, when not a fallback CandidateModels []string `json:"candidateModels,omitzero"` // Per-category classifier scores (0-1) behind the bucket: the granular HYDRA capability scores (reasoning, code_gen, debugging, tool_use), or the binary needs_reasoning/no_reasoning scores when HYDRA didn't run. Lets clients show a breakdown rather than just the bucket. CategoryScores map[string]float64 `json:"categoryScores,omitzero"` // The concrete model the session will use after any intent refinement ChosenModel string `json:"chosenModel"` + // The chosen model's score shortfall relative to the top candidate + ChosenShortfall *float64 `json:"chosenShortfall,omitempty"` // Classifier confidence for the predicted label, when available Confidence *float64 `json:"confidence,omitempty"` + // End-to-end client wait time for the router request in milliseconds + EndToEndLatencyMs *float64 `json:"endToEndLatencyMs,omitempty"` + // Whether the router fell back to the standard Auto selection + Fallback *bool `json:"fallback,omitempty"` + // Server-provided reason for falling back, when available + FallbackReason *string `json:"fallbackReason,omitempty"` + // Whether the routed prompt contained an image + HasImage *bool `json:"hasImage,omitempty"` // The predicted classifier label (e.g. `needs_reasoning`), when available PredictedLabel *string `json:"predictedLabel,omitempty"` // Coarse request-difficulty bucket, for explaining why a model was chosen ("picked X because this looks like high-reasoning work") ReasoningBucket *AutoModeResolvedReasoningBucket `json:"reasoningBucket,omitempty"` + // Server-reported router processing time in milliseconds + RouterLatencyMs *float64 `json:"routerLatencyMs,omitempty"` + // The routing method the server applied, when Auto Intent ran + RoutingMethod *string `json:"routingMethod,omitempty"` + // Whether a sticky model choice overrode the router result + StickyOverride *bool `json:"stickyOverride,omitempty"` } func (*SessionAutoModeResolvedData) sessionEventData() {} @@ -356,12 +384,18 @@ func (*SessionBinaryAssetData) Type() SessionEventType { return SessionEventType type SessionCompactionStartData struct { // Token count from non-system messages (user, assistant, tool) at compaction start ConversationTokens *int64 `json:"conversationTokens,omitempty"` + // Total context tokens (system + conversation + tool definitions) at compaction start, when known + CurrentTokens *int64 `json:"currentTokens,omitempty"` // Model identifier used for compaction, when known Model *string `json:"model,omitempty"` // Token count from system message(s) at compaction start SystemTokens *int64 `json:"systemTokens,omitempty"` + // Model context window token limit the compaction is targeting, when known + TokenLimit *int64 `json:"tokenLimit,omitempty"` // Token count from tool definitions at compaction start ToolDefinitionsTokens *int64 `json:"toolDefinitionsTokens,omitempty"` + // What initiated this compaction, when known + Trigger *CompactionTrigger `json:"trigger,omitempty"` } func (*SessionCompactionStartData) sessionEventData() {} @@ -369,6 +403,19 @@ func (*SessionCompactionStartData) Type() SessionEventType { return SessionEventTypeSessionCompactionStart } +// Context-cleared details emitted when the host clears the conversation (the session.history.clearContext RPC / Session.clearContextMessages) +type SessionContextClearedData struct { + // Optional initial message set after clearing + InitialMessage *string `json:"initialMessage,omitempty"` + // Number of conversation messages that were cleared + MessagesCleared int64 `json:"messagesCleared"` +} + +func (*SessionContextClearedData) sessionEventData() {} +func (*SessionContextClearedData) Type() SessionEventType { + return SessionEventTypeSessionContextCleared +} + // Conversation compaction results including success status, metrics, and optional error details type SessionCompactionCompleteData struct { // Checkpoint snapshot number created for recovery @@ -403,10 +450,14 @@ type SessionCompactionCompleteData struct { SummaryContent *string `json:"summaryContent,omitempty"` // Token count from system message(s) after compaction SystemTokens *int64 `json:"systemTokens,omitempty"` + // Model context window token limit the compaction was targeting, when known + TokenLimit *int64 `json:"tokenLimit,omitempty"` // Number of tokens removed during compaction TokensRemoved *int64 `json:"tokensRemoved,omitempty"` // Token count from tool definitions after compaction ToolDefinitionsTokens *int64 `json:"toolDefinitionsTokens,omitempty"` + // What initiated this compaction, when known + Trigger *CompactionTrigger `json:"trigger,omitempty"` } func (*SessionCompactionCompleteData) sessionEventData() {} @@ -598,22 +649,26 @@ func (*PendingMessagesModifiedData) Type() SessionEventType { return SessionEventTypePendingMessagesModified } -// Enterprise managed-settings resolution: the effective managed settings the session applied and where they came from, so SDK clients can show users what is enterprise-managed and by which authority. Fires whenever managed policy is (re)applied — at session start, on resume, and on account switch. This is an ephemeral live snapshot (delivered to subscribers but not persisted to the session event log), because at session start it resolves before `session.start` is emitted; for a session-independent pull, use the SDK `getManagedSettings()` API, which returns the identical payload. Managed settings have a single authoritative source, so the highest-authority present layer (server > device) wins wholesale; `bypassPermissionsDisabled` is deny-wins across layers. Marked experimental while the managed-settings surface stabilizes. +// Enterprise managed-settings resolution: the effective managed settings the session applied and which channels contributed, so SDK clients can show users what is enterprise-managed. Fires whenever managed policy is (re)applied — at session start, on resume, and on account switch. This is an ephemeral live snapshot (delivered to subscribers but not persisted to the session event log), because at session start it resolves before `session.start` is emitted. Device values take precedence over server values per ordinary key, while permissions compose restrictively across device, server, and SDK-client layers. The account-scoped `getManagedSettings()` API does not include session-local client injection. Marked experimental while the managed-settings surface stabilizes. // Experimental: SessionManagedSettingsResolvedData is part of an experimental API and may change or be removed. type SessionManagedSettingsResolvedData struct { // Whether enterprise policy disables bypass-permissions ("yolo") mode for this session. Deny-wins across layers, and forced on when `failClosed` is true. BypassPermissionsDisabled bool `json:"bypassPermissionsDisabled"` - // Whether the device (MDM/plist/registry/file) managed-settings layer was present + // Whether a session-local permissions layer injected by the SDK host was present + ClientManaged *bool `json:"clientManaged,omitempty"` + // Whether an actual device MDM/plist/registry/file managed-settings layer was present DeviceManaged bool `json:"deviceManaged"` // Whether managed policy could not be determined (e.g. a failed server fetch) and the session fell back to the fail-closed restriction. When true, restrictions such as disabling bypass-permissions are enforced even though `settings` may be absent. FailClosed bool `json:"failClosed"` // The setting keys under enterprise management in the effective managed settings (e.g. `model`, `enabledPlugins`, `permissions`). Empty when no managed settings are in force. ManagedKeys []string `json:"managedKeys"` + // Whether at least two managed sources supplied permission allowlists, so enforcement intersects them and the flattened settings payload omits `permissions.allow`. + PermissionsAllowIntersected *bool `json:"permissionsAllowIntersected,omitempty"` // Whether the server (account/org) managed-settings layer was present ServerManaged bool `json:"serverManaged"` // The effective (resolved) managed settings values, so clients can render exactly what is enforced. Absent when no managed policy is in force. Settings any `json:"settings,omitempty"` - // Which channel supplied the effective managed settings (the winning layer), or `none` when no policy is in force + // Channel summary: `server`, `device`, or `client` when exactly one channel contributed; `mixed` when multiple channels contributed; otherwise `none`. Consult the per-channel booleans for exact provenance. Source ManagedSettingsResolvedSource `json:"source"` } @@ -622,6 +677,17 @@ func (*SessionManagedSettingsResolvedData) Type() SessionEventType { return SessionEventTypeSessionManagedSettingsResolved } +// Ephemeral invalidation signal for a changed factory run. +// Experimental: FactoryRunUpdatedData is part of an experimental API and may change or be removed. +type FactoryRunUpdatedData struct { + // Monotonic revision now available for the run. + Revision int64 `json:"revision"` + RunID string `json:"runId"` +} + +func (*FactoryRunUpdatedData) sessionEventData() {} +func (*FactoryRunUpdatedData) Type() SessionEventType { return SessionEventTypeFactoryRunUpdated } + // Ephemeral progress update from a running hook process type HookProgressData struct { // Human-readable progress message from the hook process @@ -733,6 +799,7 @@ type ModelCallFailureData struct { ReasoningEffort *string `json:"reasoningEffort,omitempty"` // Content-free structural summary of the failing request. Contains only counts and shape flags (no prompt content), so it is safe for unrestricted telemetry. Populated only for client-error (4xx) failures. RequestFingerprint *ModelCallFailureRequestFingerprint `json:"requestFingerprint,omitempty"` + Rte *bool `json:"rte,omitempty"` // Copilot service request ID (x-copilot-service-request-id header) for CAPI log correlation ServiceRequestID *string `json:"serviceRequestId,omitempty"` // Where the failed model call originated @@ -797,6 +864,9 @@ type AssistantUsageData struct { APICallID *string `json:"apiCallId,omitempty"` // API endpoint used for this model call, matching CAPI supported_endpoints vocabulary APIEndpoint *AssistantUsageAPIEndpoint `json:"apiEndpoint,omitempty"` + // Number of tools available to the model for this call + // Internal: AvailableToolCount is part of the SDK's internal API surface and is not intended for external use. + AvailableToolCount *int64 `json:"availableToolCount,omitempty"` // Updated prompt-cache expiration for this model call. Present only when the call establishes or refreshes known cache state. CacheExpiresAt *time.Time `json:"cacheExpiresAt,omitempty"` // Number of tokens read from prompt cache @@ -818,10 +888,15 @@ type AssistantUsageData struct { Initiator *string `json:"initiator,omitempty"` // Number of input tokens consumed InputTokens *int64 `json:"inputTokens,omitempty"` + // Coarse classification of the interaction that produced this call, mirroring the session's per-request agent context (e.g. `conversation-agent`, `conversation-subagent`, `conversation-sampling`, `conversation-background`, `conversation-compaction`, `conversation-user`). Non-billing; lets consumers attribute a model call to a call class (e.g. sub-agent/sidekick) independently of the billing initiator. Absent when the runtime did not classify the request. + InteractionType *string `json:"interactionType,omitempty"` // Average inter-token latency in milliseconds. Only available for streaming requests InterTokenLatencyMs *float64 `json:"interTokenLatencyMs,omitempty"` // Model identifier used for this API call Model string `json:"model"` + // Number of tool calls returned by the model + // Internal: NumToolCalls is part of the SDK's internal API surface and is not intended for external use. + NumToolCalls *int64 `json:"numToolCalls,omitempty"` // Number of output tokens produced OutputTokens *int64 `json:"outputTokens,omitempty"` // Parent tool call ID when this usage originates from a sub-agent @@ -836,10 +911,17 @@ type AssistantUsageData struct { ReasoningEffort *string `json:"reasoningEffort,omitempty"` // Number of output tokens used for reasoning (e.g., chain-of-thought) ReasoningTokens *int64 `json:"reasoningTokens,omitempty"` + Rte *bool `json:"rte,omitempty"` // Copilot service request ID (x-copilot-service-request-id header) for CAPI log correlation ServiceRequestID *string `json:"serviceRequestId,omitempty"` // Time to first token in milliseconds. Only available for streaming requests TimeToFirstTokenMs *float64 `json:"timeToFirstTokenMs,omitempty"` + // Tool-call counts keyed by tool name + // Internal: ToolCounts is part of the SDK's internal API surface and is not intended for external use. + ToolCounts map[string]int64 `json:"toolCounts,omitzero"` + // Number of tokens used by tool definitions for this call + // Internal: ToolTokenCount is part of the SDK's internal API surface and is not intended for external use. + ToolTokenCount *int64 `json:"toolTokenCount,omitempty"` } func (*AssistantUsageData) sessionEventData() {} @@ -926,6 +1008,9 @@ func (*AssistantTurnRetryData) Type() SessionEventType { return SessionEventType type ModelCallStartData struct { // Model identifier used for this API call, when known Model *string `json:"model,omitempty"` + // Previous response or interaction identifier included in the model request, when present + // Internal: PreviousResponseID is part of the SDK's internal API surface and is not intended for external use. + PreviousResponseID *string `json:"previousResponseId,omitempty"` // Identifier of the assistant turn that initiated the model call TurnID string `json:"turnId"` } @@ -935,7 +1020,7 @@ func (*ModelCallStartData) Type() SessionEventType { return SessionEventTypeMode // Model change details including previous and new model identifiers type SessionModelChangeData struct { - // Reason the change happened, when not user-initiated. Currently `"rate_limit_auto_switch"` for changes triggered by the auto-mode-switch rate-limit recovery path. UI clients can use this to render contextual copy. + // Reason the change happened, when not user-initiated. `"rate_limit_auto_switch"` for changes triggered by the auto-mode-switch rate-limit recovery path, or `"refusal_fallback"` when the active model declined a request (content refusal) and the runtime switched to the configured refusal-fallback model. UI clients can use this to render contextual copy. Cause *string `json:"cause,omitempty"` // Context tier after the model change; null explicitly clears a previously selected tier ContextTier *ContextTier `json:"contextTier,omitempty"` @@ -1157,7 +1242,7 @@ type SessionMCPServerStatusChangedData struct { Error *string `json:"error,omitempty"` // Name of the MCP server whose status changed ServerName string `json:"serverName"` - // Connection status: connected, failed, needs-auth, pending, disabled, or not_configured + // Connection status: connected, failed, needs-auth, pending, disabled, stopped, or not_configured Status MCPServerStatus `json:"status"` } @@ -1213,7 +1298,7 @@ type UserMessageData struct { NativeDocumentPathFallbackPaths []string `json:"nativeDocumentPathFallbackPaths,omitzero"` // Parent agent task ID for background telemetry correlated to this user turn ParentAgentTaskID *string `json:"parentAgentTaskId,omitempty"` - // Origin of this message, used for timeline filtering (e.g., "skill-pdf" for skill-injected messages that should be hidden from the user) + // Origin of this message, used for timeline filtering and attribution (e.g., `skill-pdf` for hidden skill injection or `agent-` for an inter-agent prompt) Source *string `json:"source,omitempty"` // Normalized document MIME types that were sent natively instead of through tagged_files XML SupportedNativeDocumentMIMETypes []string `json:"supportedNativeDocumentMimeTypes,omitzero"` @@ -1247,6 +1332,8 @@ type PermissionRequestedData struct { RequestID string `json:"requestId"` // When true, this permission was already resolved by a permissionRequest hook and requires no client action ResolvedByHook *bool `json:"resolvedByHook,omitempty"` + // Neutral risk metadata supplied by the tool host. Consumers may display this value but must not use it to bypass the permission decision. + RiskAssessment any `json:"riskAssessment,omitempty"` } func (*PermissionRequestedData) sessionEventData() {} @@ -1438,6 +1525,8 @@ type SessionScheduleCreatedData struct { ID int64 `json:"id"` // Interval between ticks in milliseconds (relative-interval schedules) IntervalMs *int64 `json:"intervalMs,omitempty"` + // Who created the schedule (`user` or `model`). Persisted so a resumed session keeps gating non-user schedules from firing skills that opted out of model invocation. Absent on entries created before this field existed; a missing origin fails closed (treated the same as a non-user origin), so such a schedule may not resolve a `disable-model-invocation` skill. + Origin *ScheduleOrigin `json:"origin,omitempty"` // Prompt text that gets enqueued on every tick Prompt string `json:"prompt"` // Whether the schedule re-arms after each tick (`/every`) or fires once (`/after`) @@ -1508,6 +1597,8 @@ type SessionStartData struct { CopilotVersion string `json:"copilotVersion"` // When set, identifies a parent session whose context this session continues — e.g., a detached headless rem-agent run launched on the parent's interactive shutdown. Telemetry from this session is reported under the parent's session_id. DetachedFromSpawningParentSessionID *string `json:"detachedFromSpawningParentSessionId,omitempty"` + // Per-session GitHub MCP override persisted for cold resume + GitHubMCPToolConfig *GitHubMCPToolConfig `json:"githubMcpToolConfig,omitempty"` // Identifier of the software producing the events (e.g., "copilot-agent") Producer string `json:"producer"` // Reasoning effort level used for model calls, if applicable (e.g. "none", "low", "medium", "high", "xhigh", "max") @@ -1580,7 +1671,7 @@ type SessionResumeData struct { Context *WorkingDirectoryContext `json:"context,omitempty"` // Context tier currently selected at resume time; null when no tier is active ContextTier *ContextTier `json:"contextTier,omitempty"` - // When true, tool calls and permission requests left in flight by the previous session lifetime remain pending after resume and the agentic loop awaits their results. User sends are queued behind the pending work until all such requests reach a terminal state. When false (the default), any such tool calls and permission requests are immediately marked as interrupted on resume. + // When true, tool calls and permission requests left in flight by the previous session lifetime remain pending after resume and the agentic loop awaits their results. User sends are queued behind the pending work until all such requests reach a terminal state. When false or omitted, pending work is normally marked as interrupted unless the resume passively joined live work owned by another client; sessionWasActive distinguishes that case. ContinuePendingWork *bool `json:"continuePendingWork,omitempty"` // Total number of persisted events in the session at the time of resume EventCount int64 `json:"eventCount"` @@ -1598,7 +1689,7 @@ type SessionResumeData struct { SelectedModel *string `json:"selectedModel,omitempty"` // Session limits currently configured at resume time; null when no limits are active SessionLimits *SessionLimitsConfig `json:"sessionLimits,omitempty"` - // True when this resume attached to a session that the runtime already had running in-memory (for example, an extension joining a session another client was actively driving). False (or omitted) for cold resumes — the runtime had to reconstitute the session from its persisted event log. + // True when this resume passively joined a session that already had live work running in the runtime - an agent turn, a native queue run, a queued resume continuation, or an in-flight send (for example, an extension joining a session another client was actively driving). False (or omitted) when the session had no live work or when the resume explicitly abandoned pending work, including cold resumes and suspended sessions that remain resident in memory. SessionWasActive *bool `json:"sessionWasActive,omitempty"` // Output verbosity level used for model calls, if applicable (e.g. "low", "medium", "high") Verbosity *Verbosity `json:"verbosity,omitempty"` @@ -1791,6 +1882,8 @@ type SubagentCompletedData struct { AgentDisplayName string `json:"agentDisplayName"` // Internal name of the sub-agent AgentName string `json:"agentName"` + // Whether the sub-agent was torn down by cancellation - its own abort, or an ancestor being killed - instead of finishing its work. Cancellation is not a failure, so the run still reports completion; this distinguishes a torn-down sub-agent from one that ran to the end. + Cancelled *bool `json:"cancelled,omitempty"` // Wall-clock duration of the sub-agent execution in milliseconds DurationMs *int64 `json:"durationMs,omitempty"` // Model used by the sub-agent @@ -1861,6 +1954,8 @@ func (*SystemNotificationData) Type() SessionEventType { return SessionEventType type SystemMessageData struct { // The system or developer prompt text sent as model input Content string `json:"content"` + // Logical interaction identifier for the model run receiving this prompt + InteractionID *string `json:"interactionId,omitempty"` // Metadata about the prompt template and its construction Metadata *SystemMessageMetadata `json:"metadata,omitempty"` // Optional name identifier for the message source @@ -1874,7 +1969,13 @@ func (*SystemMessageData) Type() SessionEventType { return SessionEventTypeSyste // Task completion notification with summary from the agent type SessionTaskCompleteData struct { - // Whether the tool call succeeded. False when validation failed (e.g., invalid arguments) + // Active autopilot objective ID evaluated by the completion reviewer + ObjectiveID *int64 `json:"objectiveId,omitempty"` + // Semantic completion decision. Absent on legacy events and invalid tool calls + Outcome *TaskCompletionOutcome `json:"outcome,omitempty"` + // Label-safe runtime rationale for the completion decision (e.g. a cancellation or pause/resume downgrade), when one applies. Reviewer-authored rationale is intentionally omitted here because this event has no IFC label channel; the reviewer's findings remain available through its own labeled sub-agent events + Reason *string `json:"reason,omitempty"` + // Whether the task was accepted as complete. False when validation failed or completion was rejected or blocked by the reviewer Success *bool `json:"success,omitempty"` // Summary of the completed task, provided by the agent Summary *string `json:"summary,omitempty"` @@ -1901,6 +2002,7 @@ type ToolExecutionCompleteData struct { ParentToolCallID *string `json:"parentToolCallId,omitempty"` // Tool execution result on success Result *ToolExecutionCompleteResult `json:"result,omitempty"` + Rte *bool `json:"rte,omitempty"` // Whether this tool execution ran inside a sandbox container Sandboxed *bool `json:"sandboxed,omitempty"` // Whether the tool execution completed successfully @@ -1948,6 +2050,7 @@ type ToolExecutionStartData struct { // Tool call ID of the parent tool invocation when this event originates from a sub-agent // Deprecated: ParentToolCallID is deprecated. ParentToolCallID *string `json:"parentToolCallId,omitempty"` + Rte *bool `json:"rte,omitempty"` // Shell-tool path hints derived from the command at start time for shell tools (bash/powershell/local_shell). Produced by the same shell-aware extractor as PermissionRequestShell.possiblePaths, so it is present even when the command is auto-approved and no permission request fires. Absent for non-shell tools. ShellToolInfo *ToolExecutionStartShellToolInfo `json:"shellToolInfo,omitempty"` // Unique identifier for this tool call @@ -2082,6 +2185,8 @@ type SessionContextChangedData struct { HeadCommit *string `json:"headCommit,omitempty"` // Hosting platform type of the repository (github or ado) HostType *WorkingDirectoryContextHostType `json:"hostType,omitempty"` + // Set on the immediate preliminary event of a working-directory change, before the git context is resolved. A settled follow-up event (enriched with git context, or cwd-only for a non-repository) is always emitted afterward, so observers may defer to it. Absent on standalone/final events (e.g. relay context changes). + PendingGitContext *bool `json:"pendingGitContext,omitempty"` // Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) Repository *string `json:"repository,omitempty"` // Raw host string from the git remote URL (e.g. "github.com", "mycompany.ghe.com", "dev.azure.com") @@ -2447,6 +2552,26 @@ type ExtensionsLoadedExtension struct { Status ExtensionsLoadedExtensionStatus `json:"status"` } +// A declared phase shown in a factory permission prompt. +type FactoryPermissionPhase struct { + // Optional phase detail + Detail *string `json:"detail,omitempty"` + // Phase title + Title string `json:"title"` +} + +// Per-session configuration for the built-in GitHub MCP server +type GitHubMCPToolConfig struct { + // Additional GitHub MCP tools requested by the session + AdditionalTools []string `json:"additionalTools,omitzero"` + // Additional GitHub MCP toolsets requested by the session + AdditionalToolsets []string `json:"additionalToolsets,omitzero"` + // Whether to use the read-write endpoint and request all toolsets + EnableAllTools *bool `json:"enableAllTools,omitempty"` + // Whether to request the GitHub MCP insiders build + EnableInsidersMode *bool `json:"enableInsidersMode,omitempty"` +} + // Repository context for the handed-off session type HandoffRepository struct { // Git branch name, if applicable @@ -2539,7 +2664,7 @@ type MCPServersLoadedServer struct { PluginVersion *string `json:"pluginVersion,omitempty"` // Configuration source: user, workspace, plugin, or builtin Source *MCPServerSource `json:"source,omitempty"` - // Connection status: connected, failed, needs-auth, pending, disabled, or not_configured + // Connection status: connected, failed, needs-auth, pending, disabled, stopped, or not_configured Status MCPServerStatus `json:"status"` // Transport mechanism: stdio, http, sse (deprecated), or memory (in-process MCP server) Transport *MCPServerTransport `json:"transport,omitempty"` @@ -2566,6 +2691,10 @@ type ModelCallFailureRequestFingerprint struct { // Auto-approval judge information attached to a permission request. Present (non-null) only when the session's allow-all mode is "auto"; its absence means auto mode was off and the judge did not evaluate the request. The `recommendation` conveys the judge's disposition for this request. // Experimental: PermissionAutoApproval is part of an experimental API and may change or be removed. type PermissionAutoApproval struct { + // Classified cause of an `error` recommendation. Absent for every other recommendation. + FailureReason *AutoApprovalJudgeFailureReason `json:"failureReason,omitempty"` + // Model id that produced the recommendation, when the judge was consulted and reported one. Absent for `excluded` (the judge was not consulted) and for failures that occurred before a model was selected. + Model *string `json:"model,omitempty"` // Human-readable reason for the judge's recommendation, when available. Reason *string `json:"reason,omitempty"` // The auto-approval safety judge's outcome for this request. @@ -2601,6 +2730,8 @@ type PermissionPromptRequestCommands struct { FullCommandText string `json:"fullCommandText"` // Human-readable description of what the command intends to do Intention string `json:"intention"` + // Whether managed policy requires a human response and forbids host auto-approval + ManagedApprovalRequired *bool `json:"managedApprovalRequired,omitempty"` // Tool call ID that triggered this permission request ToolCallID *string `json:"toolCallId,omitempty"` // Optional warning message about risks of running this command @@ -2668,6 +2799,46 @@ func (PermissionPromptRequestExtensionPermissionAccess) Kind() PermissionPromptR return PermissionPromptRequestKindExtensionPermissionAccess } +// Factory run or authoring permission prompt +type PermissionPromptRequestFactory struct { + // Canonical key used for scoped factory approvals + ApprovalKey string `json:"approvalKey"` + // Auto-approval judge information for this request; present only when auto mode is enabled. + // Experimental: AutoApproval is part of an experimental API and may change or be removed. + AutoApproval *PermissionAutoApproval `json:"autoApproval,omitempty"` + // Whether this factory is eligible for persistent approval + CanPersistApproval bool `json:"canPersistApproval"` + DeclaredMaxAiCredits *float64 `json:"declaredMaxAiCredits,omitempty"` + DeclaredMaxConcurrentSubagents *int64 `json:"declaredMaxConcurrentSubagents,omitempty"` + DeclaredMaxTotalSubagents *int64 `json:"declaredMaxTotalSubagents,omitempty"` + DeclaredTimeoutSeconds *float64 `json:"declaredTimeoutSeconds,omitempty"` + // Factory description + Description string `json:"description"` + // Whether managed policy requires a human response and forbids host auto-approval + ManagedApprovalRequired *bool `json:"managedApprovalRequired,omitempty"` + // Effective AI-credit limit; omitted means unlimited + MaxAiCredits *float64 `json:"maxAiCredits,omitempty"` + // Effective concurrent-subagent limit; omitted means unlimited + MaxConcurrentSubagents *int64 `json:"maxConcurrentSubagents,omitempty"` + // Effective total-subagent limit; omitted means unlimited + MaxTotalSubagents *int64 `json:"maxTotalSubagents,omitempty"` + // Factory name + Name string `json:"name"` + // Factory operation, either run or author + Operation FactoryPermissionOperation `json:"operation"` + // Declared factory phases + Phases []FactoryPermissionPhase `json:"phases"` + // Effective active-time limit in seconds; omitted means unlimited + TimeoutSeconds *float64 `json:"timeoutSeconds,omitempty"` + // Tool call ID that triggered this permission request + ToolCallID *string `json:"toolCallId,omitempty"` +} + +func (PermissionPromptRequestFactory) permissionPromptRequest() {} +func (PermissionPromptRequestFactory) Kind() PermissionPromptRequestKind { + return PermissionPromptRequestKindFactory +} + // Hook confirmation permission prompt type PermissionPromptRequestHook struct { // Auto-approval judge information for this request; present only when auto mode is enabled. @@ -2761,6 +2932,8 @@ type PermissionPromptRequestRead struct { AutoApproval *PermissionAutoApproval `json:"autoApproval,omitempty"` // Human-readable description of why the file is being read Intention string `json:"intention"` + // Whether managed policy requires a human response and forbids host auto-approval + ManagedApprovalRequired *bool `json:"managedApprovalRequired,omitempty"` // Path of the file or directory being read Path string `json:"path"` // Tool call ID that triggered this permission request @@ -2779,6 +2952,10 @@ type PermissionPromptRequestURL struct { AutoApproval *PermissionAutoApproval `json:"autoApproval,omitempty"` // Human-readable description of why the URL is being accessed Intention string `json:"intention"` + // Whether managed policy requires a human response and forbids host auto-approval + ManagedApprovalRequired *bool `json:"managedApprovalRequired,omitempty"` + // Immediately preceding URL when this prompt is for a redirect target + RedirectedFrom *string `json:"redirectedFrom,omitempty"` // True when this URL fetch is requesting to bypass the sandbox network policy: either the model set requestSandboxBypass: true, or the tool re-issued the request as an interactive bypass after the network policy denied the approved URL (host opted in via sandbox.allowBypass). This is a request, not a grant: the fetch runs only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI. RequestSandboxBypass *bool `json:"requestSandboxBypass,omitempty"` // Model-provided justification for the sandbox-bypass request. Only meaningful when requestSandboxBypass is true. @@ -2807,6 +2984,8 @@ type PermissionPromptRequestWrite struct { FileName string `json:"fileName"` // Human-readable description of the intended file change Intention string `json:"intention"` + // Whether managed policy requires a human response and forbids host auto-approval + ManagedApprovalRequired *bool `json:"managedApprovalRequired,omitempty"` // Complete new file contents for newly created files NewFileContents *string `json:"newFileContents,omitempty"` // Tool call ID that triggered this permission request @@ -2822,6 +3001,7 @@ func (PermissionPromptRequestWrite) Kind() PermissionPromptRequestKind { type PermissionRequest interface { permissionRequest() Kind() PermissionRequestKind + RequiresManagedApproval() bool } type RawPermissionRequest struct { @@ -2838,6 +3018,8 @@ func (r RawPermissionRequest) Kind() PermissionRequestKind { type PermissionRequestCustomTool struct { // Arguments to pass to the custom tool Args any `json:"args,omitempty"` + // When true, managed policy requires an explicit user decision and automatic approval must be bypassed. + ManagedApprovalRequired *bool `json:"managedApprovalRequired,omitempty"` // Tool call ID that triggered this permission request ToolCallID *string `json:"toolCallId,omitempty"` // Description of what the custom tool does @@ -2855,6 +3037,8 @@ func (PermissionRequestCustomTool) Kind() PermissionRequestKind { type PermissionRequestExtensionManagement struct { // Name of the extension being managed ExtensionName *string `json:"extensionName,omitempty"` + // When true, managed policy requires an explicit user decision and automatic approval must be bypassed. + ManagedApprovalRequired *bool `json:"managedApprovalRequired,omitempty"` // The extension management operation (scaffold, reload) Operation string `json:"operation"` // Tool call ID that triggered this permission request @@ -2872,6 +3056,8 @@ type PermissionRequestExtensionPermissionAccess struct { Capabilities []string `json:"capabilities"` // Name of the extension requesting permission access ExtensionName string `json:"extensionName"` + // When true, managed policy requires an explicit user decision and automatic approval must be bypassed. + ManagedApprovalRequired *bool `json:"managedApprovalRequired,omitempty"` // Tool call ID that triggered this permission request ToolCallID *string `json:"toolCallId,omitempty"` } @@ -2881,10 +3067,49 @@ func (PermissionRequestExtensionPermissionAccess) Kind() PermissionRequestKind { return PermissionRequestKindExtensionPermissionAccess } +// Factory run or authoring permission request +type PermissionRequestFactory struct { + // Canonical key used for scoped factory approvals + ApprovalKey string `json:"approvalKey"` + // Whether this factory is eligible for persistent approval + CanPersistApproval bool `json:"canPersistApproval"` + DeclaredMaxAiCredits *float64 `json:"declaredMaxAiCredits,omitempty"` + DeclaredMaxConcurrentSubagents *int64 `json:"declaredMaxConcurrentSubagents,omitempty"` + DeclaredMaxTotalSubagents *int64 `json:"declaredMaxTotalSubagents,omitempty"` + DeclaredTimeoutSeconds *float64 `json:"declaredTimeoutSeconds,omitempty"` + // Factory description + Description string `json:"description"` + // When true, managed policy requires an explicit user decision and automatic approval must be bypassed. + ManagedApprovalRequired *bool `json:"managedApprovalRequired,omitempty"` + // Effective AI-credit limit; omitted means unlimited + MaxAiCredits *float64 `json:"maxAiCredits,omitempty"` + // Effective concurrent-subagent limit; omitted means unlimited + MaxConcurrentSubagents *int64 `json:"maxConcurrentSubagents,omitempty"` + // Effective total-subagent limit; omitted means unlimited + MaxTotalSubagents *int64 `json:"maxTotalSubagents,omitempty"` + // Factory name + Name string `json:"name"` + // Factory operation, either run or author + Operation FactoryPermissionOperation `json:"operation"` + // Declared factory phases + Phases []FactoryPermissionPhase `json:"phases"` + // Effective active-time limit in seconds; omitted means unlimited + TimeoutSeconds *float64 `json:"timeoutSeconds,omitempty"` + // Tool call ID that triggered this permission request + ToolCallID *string `json:"toolCallId,omitempty"` +} + +func (PermissionRequestFactory) permissionRequest() {} +func (PermissionRequestFactory) Kind() PermissionRequestKind { + return PermissionRequestKindFactory +} + // Hook confirmation permission request type PermissionRequestHook struct { // Optional message from the hook explaining why confirmation is needed HookMessage *string `json:"hookMessage,omitempty"` + // When true, managed policy requires an explicit user decision and automatic approval must be bypassed. + ManagedApprovalRequired *bool `json:"managedApprovalRequired,omitempty"` // Arguments of the tool call being gated ToolArgs any `json:"toolArgs,omitempty"` // Tool call ID that triggered this permission request @@ -2902,6 +3127,8 @@ func (PermissionRequestHook) Kind() PermissionRequestKind { type PermissionRequestMCP struct { // Arguments to pass to the MCP tool Args any `json:"args,omitempty"` + // When true, managed policy requires an explicit user decision and automatic approval must be bypassed. + ManagedApprovalRequired *bool `json:"managedApprovalRequired,omitempty"` // Whether this MCP tool is read-only (no side effects) ReadOnly bool `json:"readOnly"` // Name of the MCP server providing the tool @@ -2929,6 +3156,8 @@ type PermissionRequestMemory struct { Direction *PermissionRequestMemoryDirection `json:"direction,omitempty"` // The fact being stored or voted on Fact string `json:"fact"` + // When true, managed policy requires an explicit user decision and automatic approval must be bypassed. + ManagedApprovalRequired *bool `json:"managedApprovalRequired,omitempty"` // Reason for the vote (vote only) Reason *string `json:"reason,omitempty"` // Topic or subject of the memory (store only) @@ -2946,6 +3175,8 @@ func (PermissionRequestMemory) Kind() PermissionRequestKind { type PermissionRequestRead struct { // Human-readable description of why the file is being read Intention string `json:"intention"` + // Whether managed policy requires a human response and forbids host auto-approval + ManagedApprovalRequired *bool `json:"managedApprovalRequired,omitempty"` // Path of the file or directory being read Path string `json:"path"` // True when the model has requested to run this search outside the sandbox (it set requestSandboxBypass: true and the host opted in via sandbox.allowBypass). This is a request, not a grant: the search runs unsandboxed only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI. @@ -2967,12 +3198,16 @@ type PermissionRequestShell struct { CanOfferSessionApproval bool `json:"canOfferSessionApproval"` // Parsed command identifiers found in the command text Commands []PermissionRequestShellCommand `json:"commands"` + // Parsed command segments, including arguments, used for managed policy matching + CommandSegments []PermissionRequestShellCommandSegment `json:"commandSegments,omitzero"` // The complete shell command text to be executed FullCommandText string `json:"fullCommandText"` // Whether the command includes a file write redirection (e.g., > or >>) HasWriteFileRedirection bool `json:"hasWriteFileRedirection"` // Human-readable description of what the command intends to do Intention string `json:"intention"` + // Whether managed policy requires a human response and forbids host auto-approval + ManagedApprovalRequired *bool `json:"managedApprovalRequired,omitempty"` // File paths that may be read or written by the command PossiblePaths []string `json:"possiblePaths"` // URLs that may be accessed by the command @@ -2996,6 +3231,10 @@ func (PermissionRequestShell) Kind() PermissionRequestKind { type PermissionRequestURL struct { // Human-readable description of why the URL is being accessed Intention string `json:"intention"` + // Whether managed policy requires a human response and forbids host auto-approval + ManagedApprovalRequired *bool `json:"managedApprovalRequired,omitempty"` + // Immediately preceding URL when this request is for a redirect target + RedirectedFrom *string `json:"redirectedFrom,omitempty"` // True when this URL fetch is requesting to bypass the sandbox network policy: either the model set requestSandboxBypass: true, or the tool re-issued the request as an interactive bypass after the network policy denied the approved URL (host opted in via sandbox.allowBypass). This is a request, not a grant: the fetch runs only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI. RequestSandboxBypass *bool `json:"requestSandboxBypass,omitempty"` // Model-provided justification for the sandbox-bypass request. Only meaningful when requestSandboxBypass is true. @@ -3021,6 +3260,8 @@ type PermissionRequestWrite struct { FileName string `json:"fileName"` // Human-readable description of the intended file change Intention string `json:"intention"` + // Whether managed policy requires a human response and forbids host auto-approval + ManagedApprovalRequired *bool `json:"managedApprovalRequired,omitempty"` // Complete new file contents for newly created files NewFileContents *string `json:"newFileContents,omitempty"` // True when a built-in file tool (apply_patch / str_replace_editor) asked to write a path the sandbox filesystem policy would block, and the host opted in via sandbox.allowBypass. This is a request, not a grant: the write happens unsandboxed only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI. @@ -3044,6 +3285,14 @@ type PermissionRequestShellCommand struct { ReadOnly bool `json:"readOnly"` } +// A parsed shell command segment used for argument-aware managed policy matching. +type PermissionRequestShellCommandSegment struct { + // Full text of this command segment, including arguments + FullCommandText string `json:"fullCommandText"` + // Command identifier (e.g., executable name) + Identifier string `json:"identifier"` +} + // A URL that may be accessed by a command in a shell permission request. type PermissionRequestShellPossibleURL struct { // URL that may be accessed by the command @@ -3326,6 +3575,8 @@ type ShutdownTokenDetail struct { type SkillsLoadedSkill struct { // Optional freeform hint describing the skill's expected arguments, from the `argument-hint` frontmatter field ArgumentHint *string `json:"argumentHint,omitempty"` + // Canonical slash command name used to invoke the skill, without the leading '/' + CommandName *string `json:"commandName,omitempty"` // Description of what the skill does Description string `json:"description"` // Whether the skill is currently enabled @@ -3398,6 +3649,35 @@ func (SystemNotificationAgentIdle) Type() SystemNotificationType { return SystemNotificationTypeAgentIdle } +// System notification metadata for a factory execution attempt that reached a terminal state. +type SystemNotificationFactoryCompleted struct { + // Execution attempt that reached this terminal state. + Attempt int64 `json:"attempt"` + // Consumed AI usage in nano-AIU. + ConsumedNanoAiu int64 `json:"consumedNanoAiu"` + // Subagents consumed by the run across all attempts. + ConsumedSubagents int64 `json:"consumedSubagents"` + // Accumulated active execution time in milliseconds. + ElapsedMs int64 `json:"elapsedMs"` + // Persisted factory name. + FactoryName string `json:"factoryName"` + // Machine-readable terminal failure details, when present. + Failure any `json:"failure,omitempty"` + // Bounded prompt-safe preview of the completed result. + ResultPreview *string `json:"resultPreview,omitempty"` + // Actionable run_factory resume guidance for a resource-limit failure. + RetryGuidance *string `json:"retryGuidance,omitempty"` + // Factory run identifier. + RunID string `json:"runId"` + // Terminal status reached by this execution attempt. + Status SystemNotificationFactoryCompletedStatus `json:"status"` +} + +func (SystemNotificationFactoryCompleted) systemNotification() {} +func (SystemNotificationFactoryCompleted) Type() SystemNotificationType { + return SystemNotificationTypeFactoryCompleted +} + // System notification metadata for an instruction file discovered during tool access, including source, trigger file, and tool. type SystemNotificationInstructionDiscovered struct { // Human-readable label for the timeline (e.g., 'AGENTS.md from packages/billing/') @@ -3460,6 +3740,17 @@ func (SystemNotificationShellDetachedCompleted) Type() SystemNotificationType { return SystemNotificationTypeShellDetachedCompleted } +// System notification metadata from an external host that does not match a runtime-owned notification kind. +type SystemNotificationUnclassified struct { + // Opaque metadata supplied by the external host, when present. + Metadata any `json:"metadata,omitempty"` +} + +func (SystemNotificationUnclassified) systemNotification() {} +func (SystemNotificationUnclassified) Type() SystemNotificationType { + return SystemNotificationTypeUnclassified +} + // A content block within a tool result, which may be text, terminal output, image, audio, or a resource type ToolExecutionCompleteContent interface { toolExecutionCompleteContent() @@ -3722,6 +4013,9 @@ type ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone struct { // Shell-aware path hints for a shell tool's command, captured at start time so consumers can snapshot a file's pre-image before the tool runs. type ToolExecutionStartShellToolInfo struct { + // The command with a redundant leading `cd` into the working directory removed, present only when there was one to remove. Computed with the same routine the shell driver applies before spawning, so a surface that renders this shows the text that actually runs. Consumers that display it should keep the original tool arguments available on demand. + // Experimental: DisplayCommand is part of an experimental API and may change or be removed. + DisplayCommand *string `json:"displayCommand,omitempty"` // Whether the command includes a file write redirection (e.g., > or >>). HasWriteFileRedirection bool `json:"hasWriteFileRedirection"` // File paths the command may read or write, derived from the command at start time. Produced by the same shell-aware extractor as PermissionRequestShell.possiblePaths, so it is present even when the command is auto-approved and no permission request fires. @@ -3778,6 +4072,8 @@ type WorkingDirectoryContext struct { HeadCommit *string `json:"headCommit,omitempty"` // Hosting platform type of the repository (github or ado) HostType *WorkingDirectoryContextHostType `json:"hostType,omitempty"` + // Set on the immediate preliminary event of a working-directory change, before the git context is resolved. A settled follow-up event (enriched with git context, or cwd-only for a non-repository) is always emitted afterward, so observers may defer to it. Absent on standalone/final events (e.g. relay context changes). + PendingGitContext *bool `json:"pendingGitContext,omitempty"` // Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) Repository *string `json:"repository,omitempty"` // Raw host string from the git remote URL (e.g. "github.com", "mycompany.ghe.com", "dev.azure.com") @@ -3808,6 +4104,23 @@ const ( AssistantUsageAPIEndpointWsResponses AssistantUsageAPIEndpoint = "ws:/responses" ) +// Why the auto-approval judge produced no usable recommendation. Present only alongside an `error` recommendation, where the human-readable reason is a fixed string and therefore cannot distinguish these cases. Intended to make a judge failure reportable by a consumer that has no access to the host's logs. +// Experimental: AutoApprovalJudgeFailureReason is part of an experimental API and may change or be removed. +type AutoApprovalJudgeFailureReason string + +const ( + // The judge model call was cancelled before it returned. + AutoApprovalJudgeFailureReasonAbort AutoApprovalJudgeFailureReason = "abort" + // The judge model call completed but returned no content. + AutoApprovalJudgeFailureReasonEmptyResponse AutoApprovalJudgeFailureReason = "empty_response" + // The judge model call failed (for example a transport, authentication, or rate-limit error). + AutoApprovalJudgeFailureReasonModelError AutoApprovalJudgeFailureReason = "model_error" + // The judge model replied, but the reply carried no ALLOW/DENY verdict. + AutoApprovalJudgeFailureReasonParseError AutoApprovalJudgeFailureReason = "parse_error" + // The judge model call exceeded its deadline. + AutoApprovalJudgeFailureReasonTimeout AutoApprovalJudgeFailureReason = "timeout" +) + // Outcome of the auto-approval safety judge for a permission request. Present only when auto mode is enabled; its absence means the judge did not evaluate the request (auto mode was off). // Experimental: AutoApprovalRecommendation is part of an experimental API and may change or be removed. type AutoApprovalRecommendation string @@ -3916,6 +4229,22 @@ const ( CitationProviderOpenai CitationProvider = "openai" ) +// What initiated a conversation compaction +type CompactionTrigger string + +const ( + // Compaction forced by a context-limit model response (e.g. HTTP 413) before retrying the request. + CompactionTriggerContextLimitRetry CompactionTrigger = "context_limit_retry" + // User-requested compaction, e.g. the /compact command or the history.compact API. + CompactionTriggerManual CompactionTrigger = "manual" + // Emergency compaction triggered by high process memory usage. + CompactionTriggerMemoryPressure CompactionTrigger = "memory_pressure" + // Compaction requested while switching to a model with a smaller context window. + CompactionTriggerModelSwitch CompactionTrigger = "model_switch" + // Background compaction started automatically because context utilization crossed the background threshold. + CompactionTriggerThreshold CompactionTrigger = "threshold" +) + // The user action: "accept" (submitted form), "decline" (explicitly refused), or "cancel" (dismissed) type ElicitationCompletedAction string @@ -3987,6 +4316,16 @@ const ( ExtensionsLoadedExtensionStatusStarting ExtensionsLoadedExtensionStatus = "starting" ) +// Operation gated by a factory permission request. +type FactoryPermissionOperation string + +const ( + // Authoring a factory, which writes JavaScript into a session-scoped extension and loads it. + FactoryPermissionOperationAuthor FactoryPermissionOperation = "author" + // Running a registered factory, which spends subagents, active time, and AI credits under the approved limits. + FactoryPermissionOperationRun FactoryPermissionOperation = "run" +) + // Origin type of the session being handed off type HandoffSourceType string @@ -4021,15 +4360,19 @@ const ( ManagedSettingsEnforcedEscalationUnrestrictedURLs ManagedSettingsEnforcedEscalation = "unrestricted_urls" ) -// Which channel supplied the effective enterprise managed settings (highest-authority present layer wins wholesale) +// Summary of which managed-settings channels contributed to the effective session policy. Use the per-channel booleans for exact provenance. type ManagedSettingsResolvedSource string const ( - // Device-level MDM policy discovered from plist/registry/file (lower authority). + // Only session-local SDK-host injection contributed. + ManagedSettingsResolvedSourceClient ManagedSettingsResolvedSource = "client" + // Only the device MDM/plist/registry/file channel contributed. ManagedSettingsResolvedSourceDevice ManagedSettingsResolvedSource = "device" - // No managed policy is in force (no layer contributed). + // More than one channel contributed. Ordinary keys resolve device over server per key, while permissions compose restrictively across all present layers. + ManagedSettingsResolvedSourceMixed ManagedSettingsResolvedSource = "mixed" + // No managed policy is in force (no channel contributed). ManagedSettingsResolvedSourceNone ManagedSettingsResolvedSource = "none" - // Account/org policy self-fetched from the GitHub managed-settings endpoint (higher authority). + // Only the server/account channel contributed. ManagedSettingsResolvedSourceServer ManagedSettingsResolvedSource = "server" ) @@ -4175,6 +4518,7 @@ const ( PermissionPromptRequestKindCustomTool PermissionPromptRequestKind = "custom-tool" PermissionPromptRequestKindExtensionManagement PermissionPromptRequestKind = "extension-management" PermissionPromptRequestKindExtensionPermissionAccess PermissionPromptRequestKind = "extension-permission-access" + PermissionPromptRequestKindFactory PermissionPromptRequestKind = "factory" PermissionPromptRequestKindHook PermissionPromptRequestKind = "hook" PermissionPromptRequestKindMCP PermissionPromptRequestKind = "mcp" PermissionPromptRequestKindMemory PermissionPromptRequestKind = "memory" @@ -4203,6 +4547,7 @@ const ( PermissionRequestKindCustomTool PermissionRequestKind = "custom-tool" PermissionRequestKindExtensionManagement PermissionRequestKind = "extension-management" PermissionRequestKindExtensionPermissionAccess PermissionRequestKind = "extension-permission-access" + PermissionRequestKindFactory PermissionRequestKind = "factory" PermissionRequestKindHook PermissionRequestKind = "hook" PermissionRequestKindMCP PermissionRequestKind = "mcp" PermissionRequestKindMemory PermissionRequestKind = "memory" @@ -4278,6 +4623,16 @@ const ( PlanChangedOperationUpdate PlanChangedOperation = "update" ) +// Who created the schedule: `user` (an explicit user action such as `/every` or `/after`) or `model` (the agent via the `manage_schedule` tool). Gates whether a scheduled skill that opted out of model invocation may fire: only user-created schedules may. +type ScheduleOrigin string + +const ( + // The schedule was created by the agent via the `manage_schedule` tool. + ScheduleOriginModel ScheduleOrigin = "model" + // The schedule was created by an explicit user action, such as `/every` or `/after`. + ScheduleOriginUser ScheduleOrigin = "user" +) + // User action selected for an exhausted session limit. type SessionLimitsExhaustedResponseAction string @@ -4324,16 +4679,44 @@ const ( SystemNotificationAgentCompletedStatusFailed SystemNotificationAgentCompletedStatus = "failed" ) +// Terminal status reached by a factory execution attempt. +type SystemNotificationFactoryCompletedStatus string + +const ( + // The factory was cancelled. + SystemNotificationFactoryCompletedStatusCancelled SystemNotificationFactoryCompletedStatus = "cancelled" + // The factory completed successfully. + SystemNotificationFactoryCompletedStatusCompleted SystemNotificationFactoryCompletedStatus = "completed" + // The factory failed. + SystemNotificationFactoryCompletedStatusError SystemNotificationFactoryCompletedStatus = "error" + // The factory was halted. + SystemNotificationFactoryCompletedStatusHalted SystemNotificationFactoryCompletedStatus = "halted" +) + // Type discriminator for SystemNotification. type SystemNotificationType string const ( SystemNotificationTypeAgentCompleted SystemNotificationType = "agent_completed" SystemNotificationTypeAgentIdle SystemNotificationType = "agent_idle" + SystemNotificationTypeFactoryCompleted SystemNotificationType = "factory_completed" SystemNotificationTypeInstructionDiscovered SystemNotificationType = "instruction_discovered" SystemNotificationTypeNewInboxMessage SystemNotificationType = "new_inbox_message" SystemNotificationTypeShellCompleted SystemNotificationType = "shell_completed" SystemNotificationTypeShellDetachedCompleted SystemNotificationType = "shell_detached_completed" + SystemNotificationTypeUnclassified SystemNotificationType = "unclassified" +) + +// Semantic result of evaluating a task completion request +type TaskCompletionOutcome string + +const ( + // Completion cannot proceed without intervention; the active objective is paused when one is identified. + TaskCompletionOutcomeBlocked TaskCompletionOutcome = "blocked" + // The completion request was accepted and the objective is complete. + TaskCompletionOutcomeCompleted TaskCompletionOutcome = "completed" + // The completion request was rejected because more work or validation remains. + TaskCompletionOutcomeContinue TaskCompletionOutcome = "continue" ) // Theme variant this icon is intended for diff --git a/go/session.go b/go/session.go index c4e742e90..600a4bbeb 100644 --- a/go/session.go +++ b/go/session.go @@ -67,6 +67,7 @@ type Session struct { toolHandlersM sync.RWMutex permissionHandler PermissionHandlerFunc permissionMux sync.RWMutex + managedSettings bool mcpAuthHandler MCPAuthHandler mcpAuthMu sync.RWMutex userInputHandler UserInputHandler @@ -365,10 +366,16 @@ func canvasResultError(err error) error { } // newSession creates a new session wrapper with the given session ID and client. -func newSession(sessionID string, client *jsonrpc2.Client, workspacePath string) *Session { +func newSession( + sessionID string, + client *jsonrpc2.Client, + workspacePath string, + managedSettings bool, +) *Session { s := &Session{ SessionID: sessionID, workspacePath: workspacePath, + managedSettings: managedSettings, client: client, clientSessionAPIs: &rpc.ClientSessionAPIHandlers{}, handlers: make([]sessionHandler, 0), @@ -779,6 +786,16 @@ func (s *Session) handleHooksInvoke(hookType string, rawInput json.RawMessage) ( } return hooks.OnUserPromptSubmitted(input, invocation) + case "userPromptTransformed": + if hooks.OnUserPromptTransformed == nil { + return nil, nil + } + var input UserPromptTransformedHookInput + if err := json.Unmarshal(rawInput, &input); err != nil { + return nil, fmt.Errorf("invalid hook input: %w", err) + } + return hooks.OnUserPromptTransformed(input, invocation) + case "sessionStart": if hooks.OnSessionStart == nil { return nil, nil @@ -808,6 +825,17 @@ func (s *Session) handleHooksInvoke(hookType string, rawInput json.RawMessage) ( return nil, fmt.Errorf("invalid hook input: %w", err) } return hooks.OnErrorOccurred(input, invocation) + + case "agentStop": + if hooks.OnAgentStop == nil { + return nil, nil + } + var input AgentStopHookInput + if err := json.Unmarshal(rawInput, &input); err != nil { + return nil, fmt.Errorf("invalid hook input: %w", err) + } + return hooks.OnAgentStop(input, invocation) + default: return nil, nil } @@ -1564,6 +1592,20 @@ func (s *Session) executeToolAndRespond(requestID, toolName, toolCallID string, if result.Error != "" { rpcResult.Error = &result.Error } + if result.SessionLog != "" { + rpcResult.SessionLog = &result.SessionLog + } + for _, b := range result.BinaryResultsForLLM { + entry := rpc.ExternalToolTextResultForLlmBinaryResultsForLlm{ + Data: b.Data, + MIMEType: b.MIMEType, + Type: rpc.ExternalToolTextResultForLlmBinaryResultsForLlmType(b.Type), + } + if b.Description != "" { + entry.Description = &b.Description + } + rpcResult.BinaryResultsForLlm = append(rpcResult.BinaryResultsForLlm, entry) + } s.RPC.Tools.HandlePendingToolCall(ctx, &rpc.HandlePendingToolCallRequest{ RequestID: requestID, Result: rpcResult, @@ -1582,11 +1624,13 @@ func (s *Session) executePermissionAndRespond(requestID string, permissionReques }() invocation := PermissionInvocation{ - SessionID: s.SessionID, + SessionID: s.SessionID, + ManagedSettingsEnabled: s.managedSettings, } decision, err := handler(permissionRequest, invocation) if err != nil { + log.Printf("permission handler failed: session_id=%s request_id=%s error=%v", s.SessionID, requestID, err) s.RPC.Permissions.HandlePendingPermissionRequest(context.Background(), &rpc.PermissionDecisionRequest{ RequestID: requestID, Result: &rpc.PermissionDecisionUserNotAvailable{}, @@ -1602,6 +1646,10 @@ func (s *Session) executePermissionAndRespond(requestID string, permissionReques }) return } + // Unwrap any attribution so decisionContext travels as a sibling of result, + // not nested inside it. The suppression and send logic below operates on the + // underlying decision. + decision, decisionContext := splitAttribution(decision) if _, ok := decision.(*rpc.PermissionDecisionNoResult); ok { return } @@ -1610,8 +1658,9 @@ func (s *Session) executePermissionAndRespond(requestID string, permissionReques } s.RPC.Permissions.HandlePendingPermissionRequest(context.Background(), &rpc.PermissionDecisionRequest{ - RequestID: requestID, - Result: decision, + RequestID: requestID, + Result: decision, + DecisionContext: decisionContext, }) } @@ -1735,7 +1784,7 @@ func (s *Session) Abort(ctx context.Context) error { // SetModelOptions configures optional parameters for SetModel. type SetModelOptions struct { - // ReasoningEffort sets the reasoning effort level for the new model (e.g., "low", "medium", "high", "xhigh"). + // ReasoningEffort sets the reasoning effort level for the new model (e.g., "low", "medium", "high", "xhigh", "max"). ReasoningEffort *string // ReasoningSummary sets the reasoning summary mode for the new model. // Use ReasoningSummaryNone to suppress summary output regardless of whether reasoning is enabled. diff --git a/go/session_event_serialization_test.go b/go/session_event_serialization_test.go index bd47fdfbe..ee9258b22 100644 --- a/go/session_event_serialization_test.go +++ b/go/session_event_serialization_test.go @@ -189,3 +189,69 @@ func TestRawSessionEventDataWithNilRawMarshalsAsNull(t *testing.T) { t.Fatalf("expected missing raw data to marshal as null, got %v", serialized["data"]) } } + +func TestManagedSettingsResolvedProvenanceRoundTrips(t *testing.T) { + sources := []ManagedSettingsResolvedSource{ + ManagedSettingsResolvedSourceServer, + ManagedSettingsResolvedSourceDevice, + ManagedSettingsResolvedSourceClient, + ManagedSettingsResolvedSourceMixed, + ManagedSettingsResolvedSourceNone, + } + expectedSources := []string{"server", "device", "client", "mixed", "none"} + for i, source := range sources { + if string(source) != expectedSources[i] { + t.Fatalf("expected source %q, got %q", expectedSources[i], source) + } + } + + clientManaged := true + resolved := SessionManagedSettingsResolvedData{ + BypassPermissionsDisabled: true, + ClientManaged: &clientManaged, + DeviceManaged: false, + FailClosed: false, + ManagedKeys: []string{"permissions"}, + ServerManaged: false, + Source: ManagedSettingsResolvedSourceClient, + } + data, err := json.Marshal(resolved) + if err != nil { + t.Fatalf("failed to marshal managed settings resolution: %v", err) + } + + var serialized map[string]any + if err := json.Unmarshal(data, &serialized); err != nil { + t.Fatalf("failed to inspect managed settings resolution: %v", err) + } + if serialized["source"] != "client" || serialized["clientManaged"] != true { + t.Fatalf("expected client provenance, got %v", serialized) + } + + var roundTripped SessionManagedSettingsResolvedData + if err := json.Unmarshal(data, &roundTripped); err != nil { + t.Fatalf("failed to round-trip managed settings resolution: %v", err) + } + if roundTripped.Source != ManagedSettingsResolvedSourceClient || + roundTripped.ClientManaged == nil || + !*roundTripped.ClientManaged { + t.Fatalf("expected client provenance to round-trip, got %#v", roundTripped) + } + + resolved.Source = ManagedSettingsResolvedSourceMixed + resolved.ClientManaged = nil + data, err = json.Marshal(resolved) + if err != nil { + t.Fatalf("failed to marshal mixed managed settings resolution: %v", err) + } + serialized = nil + if err := json.Unmarshal(data, &serialized); err != nil { + t.Fatalf("failed to inspect mixed managed settings resolution: %v", err) + } + if serialized["source"] != "mixed" { + t.Fatalf("expected mixed provenance, got %v", serialized["source"]) + } + if _, ok := serialized["clientManaged"]; ok { + t.Fatalf("expected absent clientManaged to be omitted, got %v", serialized) + } +} diff --git a/go/session_fs_provider.go b/go/session_fs_provider.go index d2227d629..0f653f1a0 100644 --- a/go/session_fs_provider.go +++ b/go/session_fs_provider.go @@ -62,6 +62,36 @@ type SessionFSSqliteProvider interface { SqliteExists() (bool, error) } +// SessionFSSqliteTransactionProvider is an optional interface that a +// [SessionFSSqliteProvider] may also implement to support atomic transactions. +type SessionFSSqliteTransactionProvider interface { + // SqliteTransaction executes statements atomically against the provider's + // per-session database, applying busy handling to every statement and rolling + // the whole batch back if any statement fails. It returns one result per + // statement, in the same order. + // + // Return a [*SessionFSSqliteTransactionFailure] to classify the failure for + // the runtime; any other error is reported as + // [rpc.SessionFSSqliteTransactionErrorClassFatal]. + SqliteTransaction(statements []rpc.SessionFSSqliteTransactionStatement) ([]SessionFSSqliteQueryResult, error) +} + +// SessionFSSqliteTransactionFailure classifies a SQLite transaction failure for +// the runtime. Return it from [SessionFSSqliteTransactionProvider.SqliteTransaction] with +// [rpc.SessionFSSqliteTransactionErrorClassBusyOrLocked] when SQLite reported +// BUSY or LOCKED before commit and the transaction was rolled back, so the +// runtime knows the call is safe to retry. +type SessionFSSqliteTransactionFailure struct { + // Class is the failure classification reported to the runtime. + Class rpc.SessionFSSqliteTransactionErrorClass + // Message describes the failure. + Message string +} + +func (e *SessionFSSqliteTransactionFailure) Error() string { + return e.Message +} + // SessionFSSqliteQueryResult holds the result of a SQLite query execution. // Same shape as the generated RPC type but without the Error field, // since providers signal errors by returning a Go error. @@ -217,17 +247,50 @@ func (a *sessionFSAdapter) SqliteQuery(request *rpc.SessionFSSqliteQueryRequest) RowsAffected: 0, }, nil } - var wireRowid *int64 - if result.LastInsertRowid != nil { - rowid := *result.LastInsertRowid - wireRowid = &rowid + wireResult := toWireSqliteQueryResult(*result) + return &wireResult, nil +} + +func (a *sessionFSAdapter) SqliteTransaction(request *rpc.SessionFSSqliteTransactionRequest) (*rpc.SessionFSSqliteTransactionResult, error) { + sp, ok := a.provider.(SessionFSSqliteTransactionProvider) + if !ok { + return &rpc.SessionFSSqliteTransactionResult{ + Results: []rpc.SessionFSSqliteQueryResult{}, + Error: &rpc.SessionFSSqliteTransactionError{ + ErrorClass: rpc.SessionFSSqliteTransactionErrorClassFatal, + Message: "SQLite is not supported by this session filesystem provider", + }, + }, nil + } + results, err := sp.SqliteTransaction(request.Statements) + if err != nil { + return &rpc.SessionFSSqliteTransactionResult{ + Results: []rpc.SessionFSSqliteQueryResult{}, + Error: toSessionFSSqliteTransactionError(err), + }, nil + } + wireResults := make([]rpc.SessionFSSqliteQueryResult, 0, len(results)) + for _, result := range results { + wireResults = append(wireResults, toWireSqliteQueryResult(result)) + } + return &rpc.SessionFSSqliteTransactionResult{Results: wireResults}, nil +} + +func toWireSqliteQueryResult(result SessionFSSqliteQueryResult) rpc.SessionFSSqliteQueryResult { + columns := result.Columns + if columns == nil { + columns = []string{} + } + rows := result.Rows + if rows == nil { + rows = []map[string]any{} } - return &rpc.SessionFSSqliteQueryResult{ - Columns: result.Columns, - Rows: result.Rows, + return rpc.SessionFSSqliteQueryResult{ + Columns: columns, + Rows: rows, RowsAffected: result.RowsAffected, - LastInsertRowid: wireRowid, - }, nil + LastInsertRowid: result.LastInsertRowid, + } } func (a *sessionFSAdapter) SqliteExists(request *rpc.SessionFSSqliteExistsRequest) (*rpc.SessionFSSqliteExistsResult, error) { @@ -250,3 +313,17 @@ func toSessionFSError(err error) *rpc.SessionFSError { msg := err.Error() return &rpc.SessionFSError{Code: code, Message: &msg} } + +func toSessionFSSqliteTransactionError(err error) *rpc.SessionFSSqliteTransactionError { + var failure *SessionFSSqliteTransactionFailure + if errors.As(err, &failure) { + return &rpc.SessionFSSqliteTransactionError{ + ErrorClass: failure.Class, + Message: failure.Message, + } + } + return &rpc.SessionFSSqliteTransactionError{ + ErrorClass: rpc.SessionFSSqliteTransactionErrorClassFatal, + Message: err.Error(), + } +} diff --git a/go/session_test.go b/go/session_test.go index d34c34233..9c5f4df8c 100644 --- a/go/session_test.go +++ b/go/session_test.go @@ -1098,6 +1098,63 @@ func TestSession_PostToolUseFailureHook(t *testing.T) { }) } +func TestSession_AgentStopHook(t *testing.T) { + session, cleanup := newTestSession() + defer cleanup() + + var captured AgentStopHookInput + session.registerHooks(&SessionHooks{ + OnAgentStop: func(input AgentStopHookInput, invocation HookInvocation) (*AgentStopHookOutput, error) { + captured = input + if invocation.SessionID != session.SessionID { + t.Errorf("expected invocation session ID %q, got %q", session.SessionID, invocation.SessionID) + } + return &AgentStopHookOutput{ + Decision: "block", + Reason: "finish the remaining work", + }, nil + }, + }) + + raw := json.RawMessage(`{ + "sessionId": "sess-1", + "timestamp": 1700000000, + "cwd": "/work", + "stopReason": "end_turn", + "transcriptPath": "/tmp/transcript.jsonl", + "stop_hook_active": true + }`) + output, err := session.handleHooksInvoke("agentStop", raw) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if captured.SessionID != "sess-1" { + t.Errorf("expected sessionId 'sess-1', got %q", captured.SessionID) + } + if captured.StopReason != "end_turn" { + t.Errorf("expected stopReason 'end_turn', got %q", captured.StopReason) + } + if captured.TranscriptPath != "/tmp/transcript.jsonl" { + t.Errorf("expected transcriptPath '/tmp/transcript.jsonl', got %q", captured.TranscriptPath) + } + if !captured.StopHookActive { + t.Error("expected StopHookActive to be true") + } + if !captured.Timestamp.Equal(time.UnixMilli(1700000000)) { + t.Errorf("expected timestamp %v, got %v", time.UnixMilli(1700000000), captured.Timestamp) + } + if captured.WorkingDirectory != "/work" { + t.Errorf("expected WorkingDirectory '/work', got %q", captured.WorkingDirectory) + } + out, ok := output.(*AgentStopHookOutput) + if !ok { + t.Fatalf("expected *AgentStopHookOutput, got %T", output) + } + if out.Decision != "block" || out.Reason != "finish the remaining work" { + t.Errorf("unexpected output: %#v", out) + } +} + func TestSession_HookForwardCompatibility(t *testing.T) { t.Run("unknown hook type returns nil without error when known hooks are registered", func(t *testing.T) { session, cleanup := newTestSession() diff --git a/go/toolset_test.go b/go/toolset_test.go index f8c38ef20..270d5b757 100644 --- a/go/toolset_test.go +++ b/go/toolset_test.go @@ -229,6 +229,24 @@ func TestApplyConfigDefaultsForMode_emptyDefaultsTelemetryFalse(t *testing.T) { } } +func TestApplyConfigDefaultsForMode_emptyDefaultsExperimentalModeFalse(t *testing.T) { + c := NewClient(&ClientOptions{Mode: ModeEmpty, BaseDirectory: t.TempDir()}) + cfg := &SessionConfig{} + c.applyConfigDefaultsForMode(cfg) + if cfg.EnableExperimentalMode == nil || *cfg.EnableExperimentalMode != false { + t.Errorf("expected experimental mode default false in empty mode, got %v", cfg.EnableExperimentalMode) + } +} + +func TestApplyConfigDefaultsForMode_copilotCliLeavesExperimentalModeNil(t *testing.T) { + c := NewClient(&ClientOptions{Mode: ModeCopilotCli}) + cfg := &SessionConfig{} + c.applyConfigDefaultsForMode(cfg) + if cfg.EnableExperimentalMode != nil { + t.Errorf("non-empty mode must not default experimental mode") + } +} + func TestApplyConfigDefaultsForMode_emptyHonorsCallerTelemetry(t *testing.T) { c := NewClient(&ClientOptions{Mode: ModeEmpty, BaseDirectory: t.TempDir()}) trueVal := true @@ -276,6 +294,9 @@ func TestApplyConfigDefaultsForMode_emptyDefaultsGranularFlags(t *testing.T) { if cfg.Memory == nil || cfg.Memory.Enabled != false { t.Errorf("expected Memory.Enabled=false in empty mode, got %v", cfg.Memory) } + if cfg.CustomAgentsLocalOnly == nil || !*cfg.CustomAgentsLocalOnly { + t.Errorf("expected CustomAgentsLocalOnly=true in empty mode, got %v", cfg.CustomAgentsLocalOnly) + } } func TestApplyConfigDefaultsForMode_emptyHonorsCallerGranularFlags(t *testing.T) { @@ -291,6 +312,7 @@ func TestApplyConfigDefaultsForMode_emptyHonorsCallerGranularFlags(t *testing.T) EnableSessionStore: &trueVal, EnableSkills: &trueVal, Memory: &MemoryConfiguration{Enabled: true}, + CustomAgentsLocalOnly: &falseVal, } c.applyConfigDefaultsForMode(cfg) if *cfg.SkipEmbeddingRetrieval != false { @@ -317,6 +339,9 @@ func TestApplyConfigDefaultsForMode_emptyHonorsCallerGranularFlags(t *testing.T) if cfg.Memory == nil || cfg.Memory.Enabled != true { t.Errorf("caller-supplied Memory must win") } + if cfg.CustomAgentsLocalOnly == nil || *cfg.CustomAgentsLocalOnly { + t.Errorf("caller-supplied CustomAgentsLocalOnly must win") + } } func TestApplyConfigDefaultsForMode_copilotCliLeavesGranularFlagsNil(t *testing.T) { @@ -344,6 +369,25 @@ func TestApplyConfigDefaultsForMode_copilotCliLeavesGranularFlagsNil(t *testing. if cfg.Memory != nil { t.Errorf("non-empty mode must not default Memory") } + if cfg.CustomAgentsLocalOnly != nil { + t.Errorf("non-empty mode must not default CustomAgentsLocalOnly") + } +} + +func TestApplyResumeDefaultsForMode_customAgentsLocalOnly(t *testing.T) { + c := NewClient(&ClientOptions{Mode: ModeEmpty, BaseDirectory: t.TempDir()}) + + cfg := &ResumeSessionConfig{} + c.applyResumeDefaultsForMode(cfg) + if cfg.CustomAgentsLocalOnly == nil || !*cfg.CustomAgentsLocalOnly { + t.Errorf("expected CustomAgentsLocalOnly=true in empty mode, got %v", cfg.CustomAgentsLocalOnly) + } + + cfg = &ResumeSessionConfig{CustomAgentsLocalOnly: Bool(false)} + c.applyResumeDefaultsForMode(cfg) + if cfg.CustomAgentsLocalOnly == nil || *cfg.CustomAgentsLocalOnly { + t.Errorf("caller-supplied CustomAgentsLocalOnly must win") + } } func TestApplyConfigDefaultsForMode_emptyDefaultsMCPOAuthTokenStorage(t *testing.T) { @@ -372,3 +416,21 @@ func TestApplyConfigDefaultsForMode_copilotCliLeavesMCPOAuthTokenStorageEmpty(t t.Errorf("non-empty mode must not default MCPOAuthTokenStorage, got %q", cfg.MCPOAuthTokenStorage) } } + +func TestApplyResumeDefaultsForMode_emptyDefaultsExperimentalModeFalse(t *testing.T) { + c := NewClient(&ClientOptions{Mode: ModeEmpty, BaseDirectory: t.TempDir()}) + cfg := &ResumeSessionConfig{} + c.applyResumeDefaultsForMode(cfg) + if cfg.EnableExperimentalMode == nil || *cfg.EnableExperimentalMode != false { + t.Errorf("expected experimental mode default false in empty mode, got %v", cfg.EnableExperimentalMode) + } +} + +func TestApplyResumeDefaultsForMode_copilotCliLeavesExperimentalModeNil(t *testing.T) { + c := NewClient(&ClientOptions{Mode: ModeCopilotCli}) + cfg := &ResumeSessionConfig{} + c.applyResumeDefaultsForMode(cfg) + if cfg.EnableExperimentalMode != nil { + t.Errorf("non-empty mode must not default experimental mode") + } +} diff --git a/go/types.go b/go/types.go index d97fab911..2241d2b5f 100644 --- a/go/types.go +++ b/go/types.go @@ -129,6 +129,11 @@ type ClientOptions struct { // location. // Ignored when connecting to an existing runtime via [URIConnection]. BaseDirectory string + // BuiltinPluginDirectories contains absolute paths to trusted plugin + // directories bundled by the host. When non-empty, Start replaces the + // runtime's complete trusted built-in plugin directory set before sessions + // can be created. + BuiltinPluginDirectories []string // LogLevel for the runtime. When empty (the default), the runtime // uses its own default level; the SDK does not pass --log-level. // Recognized values: "none", "error", "warning", "info", "debug", "all". @@ -375,9 +380,51 @@ type PermissionHandlerFunc func(request PermissionRequest, invocation Permission // PermissionInvocation provides context about a permission request type PermissionInvocation struct { - SessionID string + SessionID string + ManagedSettingsEnabled bool } +// PermissionDecisionContext describes how and where a permission decision was +// reached. Attach it to a decision with [NewAttributedPermissionResult] so the runtime +// can attribute auto-approval telemetry to the responding surface. It is +// informational only and never changes permission behavior. +// +// Experimental: PermissionDecisionContext is part of an experimental API and +// may change or be removed. +type PermissionDecisionContext = rpc.PermissionDecisionContext + +// PermissionDecisionOutcome describes the disposition of a permission request +// as observed by the responding client. +type PermissionDecisionOutcome = rpc.PermissionDecisionOutcome + +const ( + PermissionDecisionOutcomeAutoApproved = rpc.PermissionDecisionOutcomeAutoApproved + PermissionDecisionOutcomeAutopilotDenied = rpc.PermissionDecisionOutcomeAutopilotDenied + PermissionDecisionOutcomePromptedUser = rpc.PermissionDecisionOutcomePromptedUser +) + +// PermissionDecisionSource identifies the controlled reason or actor +// responsible for a permission response. +type PermissionDecisionSource = rpc.PermissionDecisionSource + +const ( + PermissionDecisionSourceHostPolicy = rpc.PermissionDecisionSourceHostPolicy + PermissionDecisionSourceHumanResponse = rpc.PermissionDecisionSourceHumanResponse + PermissionDecisionSourceJudgeRecommendation = rpc.PermissionDecisionSourceJudgeRecommendation + PermissionDecisionSourceUnattendedFallback = rpc.PermissionDecisionSourceUnattendedFallback +) + +// PermissionDecisionSurface identifies the client surface that submitted a +// permission response. +type PermissionDecisionSurface = rpc.PermissionDecisionSurface + +const ( + PermissionDecisionSurfaceCopilotApp = rpc.PermissionDecisionSurfaceCopilotApp + PermissionDecisionSurfacePromptMode = rpc.PermissionDecisionSurfacePromptMode + PermissionDecisionSurfaceSDK = rpc.PermissionDecisionSurfaceSDK + PermissionDecisionSurfaceTui = rpc.PermissionDecisionSurfaceTui +) + // MCPAuthWwwAuthenticateParams contains parsed parameters from an MCP server's WWW-Authenticate response. type MCPAuthWwwAuthenticateParams struct { ResourceMetadataURL *string `json:"resourceMetadataUrl,omitempty"` @@ -678,6 +725,46 @@ type UserPromptSubmittedHookOutput struct { // UserPromptSubmittedHandler handles user-prompt-submitted hook invocations type UserPromptSubmittedHandler func(input UserPromptSubmittedHookInput, invocation HookInvocation) (*UserPromptSubmittedHookOutput, error) +// UserPromptTransformedHookInput is the input for a user-prompt-transformed hook. +type UserPromptTransformedHookInput struct { + SessionID string `json:"sessionId"` + Timestamp time.Time `json:"-"` + WorkingDirectory string `json:"cwd"` + Prompt string `json:"prompt"` + TransformedPrompt string `json:"transformedPrompt"` +} + +// MarshalJSON implements json.Marshaler, emitting Timestamp as Unix milliseconds. +func (h UserPromptTransformedHookInput) MarshalJSON() ([]byte, error) { + type alias UserPromptTransformedHookInput + return json.Marshal(&struct { + Timestamp int64 `json:"timestamp"` + alias + }{Timestamp: h.Timestamp.UnixMilli(), alias: alias(h)}) +} + +// UnmarshalJSON implements json.Unmarshaler, parsing Timestamp from Unix milliseconds. +func (h *UserPromptTransformedHookInput) UnmarshalJSON(data []byte) error { + type alias UserPromptTransformedHookInput + aux := &struct { + Timestamp int64 `json:"timestamp"` + *alias + }{alias: (*alias)(h)} + if err := json.Unmarshal(data, aux); err != nil { + return err + } + h.Timestamp = time.UnixMilli(aux.Timestamp) + return nil +} + +// UserPromptTransformedHookOutput is the output for a user-prompt-transformed hook. +type UserPromptTransformedHookOutput struct { + ModifiedTransformedPrompt *string `json:"modifiedTransformedPrompt,omitempty"` +} + +// UserPromptTransformedHandler handles user-prompt-transformed hook invocations. +type UserPromptTransformedHandler func(input UserPromptTransformedHookInput, invocation HookInvocation) (*UserPromptTransformedHookOutput, error) + // SessionStartHookInput is the input for a session-start hook type SessionStartHookInput struct { SessionID string `json:"sessionId"` @@ -806,6 +893,48 @@ type ErrorOccurredHookOutput struct { // ErrorOccurredHandler handles error-occurred hook invocations type ErrorOccurredHandler func(input ErrorOccurredHookInput, invocation HookInvocation) (*ErrorOccurredHookOutput, error) +// AgentStopHookInput is the input for an agent-stop hook. +type AgentStopHookInput struct { + SessionID string `json:"sessionId"` + Timestamp time.Time `json:"-"` + WorkingDirectory string `json:"cwd"` + StopReason string `json:"stopReason,omitempty"` + TranscriptPath string `json:"transcriptPath,omitempty"` + StopHookActive bool `json:"stop_hook_active,omitempty"` +} + +// MarshalJSON implements json.Marshaler, emitting Timestamp as Unix milliseconds. +func (h AgentStopHookInput) MarshalJSON() ([]byte, error) { + type alias AgentStopHookInput + return json.Marshal(&struct { + Timestamp int64 `json:"timestamp"` + alias + }{Timestamp: h.Timestamp.UnixMilli(), alias: alias(h)}) +} + +// UnmarshalJSON implements json.Unmarshaler, parsing Timestamp from Unix milliseconds. +func (h *AgentStopHookInput) UnmarshalJSON(data []byte) error { + type alias AgentStopHookInput + aux := &struct { + Timestamp int64 `json:"timestamp"` + *alias + }{alias: (*alias)(h)} + if err := json.Unmarshal(data, aux); err != nil { + return err + } + h.Timestamp = time.UnixMilli(aux.Timestamp) + return nil +} + +// AgentStopHookOutput is the output for an agent-stop hook. +type AgentStopHookOutput struct { + Decision string `json:"decision,omitempty"` + Reason string `json:"reason,omitempty"` +} + +// AgentStopHandler handles agent-stop hook invocations. +type AgentStopHandler func(input AgentStopHookInput, invocation HookInvocation) (*AgentStopHookOutput, error) + // PreMCPToolCallHookInput is the input for a pre-mcp-tool-call hook type PreMCPToolCallHookInput struct { SessionID string `json:"sessionId"` @@ -856,14 +985,16 @@ type HookInvocation struct { // SessionHooks configures hook handlers for a session type SessionHooks struct { - OnPreToolUse PreToolUseHandler - OnPostToolUse PostToolUseHandler - OnPostToolUseFailure PostToolUseFailureHandler - OnUserPromptSubmitted UserPromptSubmittedHandler - OnSessionStart SessionStartHandler - OnSessionEnd SessionEndHandler - OnErrorOccurred ErrorOccurredHandler - OnPreMCPToolCall PreMCPToolCallHandler + OnPreToolUse PreToolUseHandler + OnPostToolUse PostToolUseHandler + OnPostToolUseFailure PostToolUseFailureHandler + OnUserPromptSubmitted UserPromptSubmittedHandler + OnUserPromptTransformed UserPromptTransformedHandler + OnSessionStart SessionStartHandler + OnSessionEnd SessionEndHandler + OnErrorOccurred ErrorOccurredHandler + OnAgentStop AgentStopHandler + OnPreMCPToolCall PreMCPToolCallHandler } // MCPServerConfig is implemented by MCP server configuration types. @@ -950,8 +1081,8 @@ type CustomAgentConfig struct { // falling back to the parent session model if unavailable. Model string `json:"model,omitempty"` // ReasoningEffort is the reasoning effort level for this agent's model. - // When empty, no per-agent override is sent and the backend chooses its - // default. The parent session effort is not inherited. + // When empty, the runtime resolves model configuration, then inherits the + // parent effort only for the same model. ReasoningEffort string `json:"reasoningEffort,omitempty"` } @@ -1097,6 +1228,18 @@ func (e ExpConfigEntry) MarshalJSON() ([]byte, error) { return json.Marshal(w) } +// GitHubMCPToolConfig configures the built-in GitHub MCP server. +// +// DisableFormDeferral only applies to the built-in GitHub MCP server and only +// has an effect when MCP Apps and form-backed GitHub tools are enabled. +type GitHubMCPToolConfig struct { + EnableAllTools *bool `json:"enableAllTools,omitempty"` + AdditionalToolsets []string `json:"additionalToolsets,omitempty"` + AdditionalTools []string `json:"additionalTools,omitempty"` + EnableInsidersMode *bool `json:"enableInsidersMode,omitempty"` + DisableFormDeferral *bool `json:"disableFormDeferral,omitempty"` +} + // SessionConfig configures a new session type SessionConfig struct { // SessionID is an optional custom session ID @@ -1107,7 +1250,7 @@ type SessionConfig struct { // Model to use for this session Model string // ReasoningEffort level for models that support it. - // Valid values: "low", "medium", "high", "xhigh" + // Valid values: "low", "medium", "high", "xhigh", "max" // Only applies to models where capabilities.supports.reasoningEffort is true. ReasoningEffort string // ReasoningSummary mode for models that support configurable reasoning summaries. @@ -1119,13 +1262,9 @@ type SessionConfig struct { // ConfigDirectory overrides the default configuration directory location. // When specified, the session will use this directory for storing config and state. ConfigDirectory string - // EnableConfigDiscovery, when non-nil, controls automatic discovery of MCP server configurations - // (e.g. .mcp.json, .vscode/mcp.json) and skill directories from the working directory - // and merges them with any explicitly provided MCPServers and SkillDirectories, with - // explicit values taking precedence on name collision. + // EnableConfigDiscovery enables runtime discovery of supported configuration. + // Explicitly supplied configuration takes precedence over discovered values. // Nil leaves the runtime default unchanged; use Bool(false) to explicitly disable discovery. - // Custom instruction files (.github/copilot-instructions.md, AGENTS.md, etc.) are - // always loaded from the working directory regardless of this setting. EnableConfigDiscovery *bool // SkipEmbeddingRetrieval, when non-nil, controls embedding-based retrieval // for this session. Use in multitenant deployments to prevent cross-session @@ -1190,6 +1329,9 @@ type SessionConfig struct { // WorkingDirectory is the working directory for the session. // Tool operations will be relative to this directory. WorkingDirectory string + // AdditionalDirectories are directories the agent may access beyond WorkingDirectory. + // Relative paths are resolved against WorkingDirectory. Re-supply them when resuming. + AdditionalDirectories []string // Streaming enables streaming of assistant message and reasoning chunks. // When non-nil and true, assistant.message_delta and assistant.reasoning_delta // events with deltaContent are sent as the response is generated. @@ -1230,11 +1372,18 @@ type SessionConfig struct { // Experimental: EnableCitations is part of an experimental model capability // surface and may change or be removed in future SDK or CLI releases. EnableCitations *bool + // EnableFileChangeTracking opts in to capturing file changes from the first + // turn for session rewind and cumulative session diff. + EnableFileChangeTracking *bool // SessionLimits applies limits to this session's current accounting window. // // Experimental: SessionLimits is part of an experimental runtime accounting // surface and may change or be removed in future SDK or CLI releases. SessionLimits *rpc.SessionLimitsConfig + // EnableExperimentalMode controls whether the session enables experimental + // features. When nil, it defaults to false in [ModeEmpty]; otherwise the + // runtime decides. + EnableExperimentalMode *bool // SkipCustomInstructions, when non-nil, controls whether the runtime loads // custom instruction files. See also [ClientOptions.Mode] = [ModeEmpty]. SkipCustomInstructions *bool @@ -1276,6 +1425,10 @@ type SessionConfig struct { InstructionDirectories []string // DisabledSkills is a list of skill names to disable DisabledSkills []string + // DisabledMCPServers is a list of exact MCP server names to disable for this session. + // Disabled servers are not started or authenticated on create or cold resume. + // A resident resume cannot stop servers that are already running. + DisabledMCPServers []string // InfiniteSessions configures infinite sessions for persistent workspaces and automatic compaction. // When enabled (default), sessions automatically manage context limits and persist state. InfiniteSessions *InfiniteSessionConfig @@ -1336,6 +1489,10 @@ type SessionConfig struct { // cause MCP servers to register UI-enabled tool variants the consumer cannot // display. EnableMCPApps bool + // GitHubMCPToolConfig configures the built-in GitHub MCP server. + // DisableFormDeferral only applies to that server and only has an effect + // when MCP Apps and form-backed GitHub tools are enabled. + GitHubMCPToolConfig *GitHubMCPToolConfig // GitHubToken is an optional per-session GitHub token used for authentication. // When provided, the session authenticates as the token's owner instead of // using the global client-level auth. @@ -1390,6 +1547,51 @@ type SessionConfig struct { // be set; if omitted, the runtime is expected to reject session creation // (fail-closed). Unset behaves exactly as before. EnableManagedSettings *bool + // ManagedSettings supplies host-injected enterprise managed settings for + // the session. Unlike EnableManagedSettings (which asks the runtime to + // self-fetch account/org and device policy), this provides the managed + // policy directly. The runtime validates it with the same + // managed-permission parser it uses for fetched policy and composes it + // restrictively with any self-fetched (server) and device-managed (MDM) + // layers. It is startup-only and not persisted: re-supply it on resume, + // where it replaces the prior injected layer (omitting it clears the + // layer). It may be combined with EnableManagedSettings. Requires a runtime + // whose RPC schema includes managedSettings. + ManagedSettings *ManagedSettings +} + +// ManagedSettings is host-injected enterprise managed settings for a session. +// The first supported contract is permissions-only; unknown sibling keys are +// rejected by the runtime. Serialized on the wire as managedSettings. +type ManagedSettings struct { + // Permissions is the managed permission policy for the session. + Permissions *ManagedSettingsPermissions `json:"permissions,omitempty"` +} + +// DisableBypassPermissionsMode is the managed bypass-permissions policy. +type DisableBypassPermissionsMode = rpc.DisableBypassPermissionsMode + +const ( + // DisableBypassPermissionsModeDisable turns off bypass-permissions mode. + DisableBypassPermissionsModeDisable = rpc.DisableBypassPermissionsModeDisable +) + +// ManagedSettingsPermissions is the permissions-only managed policy injected +// via ManagedSettings. Rule strings use the same vocabulary the runtime +// accepts for fetched managed policy (e.g. "Read(**)", "Shell(git push *)"); +// malformed rules are rejected by the runtime at session creation. +type ManagedSettingsPermissions struct { + // DisableBypassPermissionsMode, when set to "disable", turns off + // bypass-permissions ("yolo") mode for the session. Deny-wins: no other + // layer can re-enable it. + DisableBypassPermissionsMode DisableBypassPermissionsMode `json:"disableBypassPermissionsMode,omitempty"` + // Deny lists operations that must always be denied. Unioned across layers. + Deny []string `json:"deny,omitzero"` + // Ask lists operations that must prompt for approval. Unioned across layers. + Ask []string `json:"ask,omitzero"` + // Allow lists operations permitted without prompting. Every declared allow + // list across managed layers must admit an operation for it to be allowed. + Allow []string `json:"allow,omitzero"` } // ToolDefer controls whether a tool may be deferred (loaded lazily via tool @@ -1409,6 +1611,11 @@ type Tool struct { Parameters map[string]any `json:"parameters,omitzero"` OverridesBuiltInTool bool `json:"overridesBuiltInTool,omitempty"` SkipPermission bool `json:"skipPermission,omitempty"` + // IsTerminal reports that a successful call to this tool ends the agent + // turn: the runtime halts instead of feeding the result back to the model + // for another round. A failed call leaves the loop running so the model can + // read the error and retry. + IsTerminal bool `json:"isTerminal,omitempty"` // Defer controls whether the tool may be deferred (loaded lazily via tool // search) rather than always pre-loaded. When empty, the runtime decides. Defer ToolDefer `json:"defer,omitempty"` @@ -1632,11 +1839,19 @@ type ResumeSessionConfig struct { // Experimental: EnableCitations is part of an experimental model capability // surface and may change or be removed in future SDK or CLI releases. EnableCitations *bool + // EnableFileChangeTracking opts in to capturing file changes for session + // rewind and cumulative session diff when the resumed session has a valid + // baseline. Earlier untracked changes cannot be reconstructed. + EnableFileChangeTracking *bool // SessionLimits applies limits to this session's current accounting window. // // Experimental: SessionLimits is part of an experimental runtime accounting // surface and may change or be removed in future SDK or CLI releases. SessionLimits *rpc.SessionLimitsConfig + // EnableExperimentalMode controls whether the session enables experimental + // features. When nil, it defaults to false in [ModeEmpty]; otherwise the + // runtime decides. + EnableExperimentalMode *bool // SkipCustomInstructions, when non-nil, controls whether the runtime loads // custom instruction files. See also [ClientOptions.Mode] = [ModeEmpty]. SkipCustomInstructions *bool @@ -1654,7 +1869,7 @@ type ResumeSessionConfig struct { // Only non-nil fields are applied over the runtime-resolved capabilities. ModelCapabilities *rpc.ModelCapabilitiesOverride // ReasoningEffort level for models that support it. - // Valid values: "low", "medium", "high", "xhigh" + // Valid values: "low", "medium", "high", "xhigh", "max" ReasoningEffort string // ReasoningSummary mode for models that support configurable reasoning summaries. // Use ReasoningSummaryNone to suppress summary output regardless of whether reasoning is enabled. @@ -1676,15 +1891,14 @@ type ResumeSessionConfig struct { // WorkingDirectory is the working directory for the session. // Tool operations will be relative to this directory. WorkingDirectory string + // AdditionalDirectories are directories the agent may access beyond WorkingDirectory. + // Relative paths are resolved against WorkingDirectory. Re-supply them when resuming. + AdditionalDirectories []string // ConfigDirectory overrides the default configuration directory location. ConfigDirectory string - // EnableConfigDiscovery, when non-nil, controls automatic discovery of MCP server configurations - // (e.g. .mcp.json, .vscode/mcp.json) and skill directories from the working directory - // and merges them with any explicitly provided MCPServers and SkillDirectories, with - // explicit values taking precedence on name collision. + // EnableConfigDiscovery enables runtime discovery of supported configuration. + // Explicitly supplied configuration takes precedence over discovered values. // Nil leaves the runtime default unchanged; use Bool(false) to explicitly disable discovery. - // Custom instruction files (.github/copilot-instructions.md, AGENTS.md, etc.) are - // always loaded from the working directory regardless of this setting. EnableConfigDiscovery *bool // SkipEmbeddingRetrieval, when non-nil, controls embedding-based retrieval // for this session. Use in multitenant deployments to prevent cross-session @@ -1748,6 +1962,10 @@ type ResumeSessionConfig struct { InstructionDirectories []string // DisabledSkills is a list of skill names to disable DisabledSkills []string + // DisabledMCPServers is a list of exact MCP server names to disable for this session. + // Disabled servers are not started or authenticated on create or cold resume. + // A resident resume cannot stop servers that are already running. + DisabledMCPServers []string // InfiniteSessions configures infinite sessions for persistent workspaces and automatic compaction. InfiniteSessions *InfiniteSessionConfig // LargeOutput configures handling of large tool outputs. When a tool produces @@ -1804,6 +2022,10 @@ type ResumeSessionConfig struct { // Experimental: EnableMCPApps is part of an experimental wire-protocol // surface (SEP-1865) and may change or be removed in a future release. EnableMCPApps bool + // GitHubMCPToolConfig configures the built-in GitHub MCP server. + // DisableFormDeferral only applies to that server and only has an effect + // when MCP Apps and form-backed GitHub tools are enabled. + GitHubMCPToolConfig *GitHubMCPToolConfig // Canvases declares canvases this session provides. Sent over the wire on // `session.resume`. See SessionConfig.Canvases. Canvases []CanvasDeclaration @@ -1837,6 +2059,11 @@ type ResumeSessionConfig struct { // SessionConfig.EnableManagedSettings. Re-supply on resume so the runtime // re-applies the managed-settings self-fetch after a CLI process restart. EnableManagedSettings *bool + // ManagedSettings re-injects host-provided managed settings on resume. See + // SessionConfig.ManagedSettings. It must be re-supplied on resume: it + // replaces the prior injected layer, and omitting it clears that layer so + // warm and cold resume behave identically. + ManagedSettings *ManagedSettings } // ProviderTokenArgs carries the context passed to a [BearerTokenProvider] callback @@ -1966,7 +2193,8 @@ type CapiSessionOptions struct { // AzureProviderOptions contains Azure-specific provider configuration type AzureProviderOptions struct { - // APIVersion is the Azure API version. Defaults to "2024-10-21". + // APIVersion is the Azure API version. When empty, the runtime uses the GA + // versionless v1 route. APIVersion string `json:"apiVersion,omitempty"` } @@ -2241,7 +2469,9 @@ type createSessionRequest struct { Models []ProviderModelConfig `json:"models,omitempty"` EnableSessionTelemetry *bool `json:"enableSessionTelemetry,omitempty"` EnableCitations *bool `json:"enableCitations,omitempty"` + EnableFileChangeTracking *bool `json:"enableFileChangeTracking,omitempty"` SessionLimits *rpc.SessionLimitsConfig `json:"sessionLimits,omitempty"` + IsExperimentalMode *bool `json:"isExperimentalMode,omitempty"` SkipCustomInstructions *bool `json:"skipCustomInstructions,omitempty"` CustomAgentsLocalOnly *bool `json:"customAgentsLocalOnly,omitempty"` CoauthorEnabled *bool `json:"coauthorEnabled,omitempty"` @@ -2253,6 +2483,7 @@ type createSessionRequest struct { RequestAutoModeSwitch *bool `json:"requestAutoModeSwitch,omitempty"` Hooks *bool `json:"hooks,omitempty"` WorkingDirectory string `json:"workingDirectory,omitempty"` + AdditionalDirectories []string `json:"additionalDirectories,omitempty"` Streaming *bool `json:"streaming,omitempty"` IncludeSubAgentStreamingEvents *bool `json:"includeSubAgentStreamingEvents,omitempty"` EnableGitHubTelemetryForwarding *bool `json:"enableGitHubTelemetryForwarding,omitempty"` @@ -2276,6 +2507,7 @@ type createSessionRequest struct { PluginDirectories []string `json:"pluginDirectories,omitempty"` InstructionDirectories []string `json:"instructionDirectories,omitempty"` DisabledSkills []string `json:"disabledSkills,omitempty"` + DisabledMCPServers *[]string `json:"disabledMcpServers,omitempty"` InfiniteSessions *InfiniteSessionConfig `json:"infiniteSessions,omitempty"` LargeOutput *LargeToolOutputConfig `json:"largeOutput,omitempty"` ToolSearch *ToolSearchConfig `json:"toolSearch,omitempty"` @@ -2283,6 +2515,7 @@ type createSessionRequest struct { Commands []wireCommand `json:"commands,omitempty"` RequestElicitation *bool `json:"requestElicitation,omitempty"` RequestMCPApps *bool `json:"requestMcpApps,omitempty"` + GitHubMCPToolConfig *GitHubMCPToolConfig `json:"githubMcpToolConfig,omitempty"` GitHubToken string `json:"gitHubToken,omitempty"` RemoteSession rpc.RemoteSessionMode `json:"remoteSession,omitempty"` Cloud *CloudSessionOptions `json:"cloud,omitempty"` @@ -2294,6 +2527,7 @@ type createSessionRequest struct { CanvasProvider *CanvasProviderIdentity `json:"canvasProvider,omitempty"` ExpAssignments *CopilotExpAssignmentResponse `json:"expAssignments,omitempty"` EnableManagedSettings *bool `json:"enableManagedSettings,omitempty"` + ManagedSettings *ManagedSettings `json:"managedSettings,omitempty"` Traceparent string `json:"traceparent,omitempty"` Tracestate string `json:"tracestate,omitempty"` } @@ -2331,7 +2565,9 @@ type resumeSessionRequest struct { Models []ProviderModelConfig `json:"models,omitempty"` EnableSessionTelemetry *bool `json:"enableSessionTelemetry,omitempty"` EnableCitations *bool `json:"enableCitations,omitempty"` + EnableFileChangeTracking *bool `json:"enableFileChangeTracking,omitempty"` SessionLimits *rpc.SessionLimitsConfig `json:"sessionLimits,omitempty"` + IsExperimentalMode *bool `json:"isExperimentalMode,omitempty"` SkipCustomInstructions *bool `json:"skipCustomInstructions,omitempty"` CustomAgentsLocalOnly *bool `json:"customAgentsLocalOnly,omitempty"` CoauthorEnabled *bool `json:"coauthorEnabled,omitempty"` @@ -2343,6 +2579,7 @@ type resumeSessionRequest struct { RequestAutoModeSwitch *bool `json:"requestAutoModeSwitch,omitempty"` Hooks *bool `json:"hooks,omitempty"` WorkingDirectory string `json:"workingDirectory,omitempty"` + AdditionalDirectories []string `json:"additionalDirectories,omitempty"` ConfigDir string `json:"configDir,omitempty"` EnableConfigDiscovery *bool `json:"enableConfigDiscovery,omitempty"` SkipEmbeddingRetrieval *bool `json:"skipEmbeddingRetrieval,omitempty"` @@ -2368,6 +2605,7 @@ type resumeSessionRequest struct { PluginDirectories []string `json:"pluginDirectories,omitempty"` InstructionDirectories []string `json:"instructionDirectories,omitempty"` DisabledSkills []string `json:"disabledSkills,omitempty"` + DisabledMCPServers *[]string `json:"disabledMcpServers,omitempty"` InfiniteSessions *InfiniteSessionConfig `json:"infiniteSessions,omitempty"` LargeOutput *LargeToolOutputConfig `json:"largeOutput,omitempty"` ToolSearch *ToolSearchConfig `json:"toolSearch,omitempty"` @@ -2375,6 +2613,7 @@ type resumeSessionRequest struct { Commands []wireCommand `json:"commands,omitempty"` RequestElicitation *bool `json:"requestElicitation,omitempty"` RequestMCPApps *bool `json:"requestMcpApps,omitempty"` + GitHubMCPToolConfig *GitHubMCPToolConfig `json:"githubMcpToolConfig,omitempty"` GitHubToken string `json:"gitHubToken,omitempty"` RemoteSession rpc.RemoteSessionMode `json:"remoteSession,omitempty"` Canvases []CanvasDeclaration `json:"canvases,omitempty"` @@ -2386,6 +2625,7 @@ type resumeSessionRequest struct { CanvasProvider *CanvasProviderIdentity `json:"canvasProvider,omitempty"` ExpAssignments *CopilotExpAssignmentResponse `json:"expAssignments,omitempty"` EnableManagedSettings *bool `json:"enableManagedSettings,omitempty"` + ManagedSettings *ManagedSettings `json:"managedSettings,omitempty"` Traceparent string `json:"traceparent,omitempty"` Tracestate string `json:"tracestate,omitempty"` } diff --git a/go/types_test.go b/go/types_test.go index a76ebaad4..4195464b3 100644 --- a/go/types_test.go +++ b/go/types_test.go @@ -5,6 +5,18 @@ import ( "testing" ) +func TestUserPromptTransformedHookOutput_PreservesEmptyReplacement(t *testing.T) { + data, err := json.Marshal(UserPromptTransformedHookOutput{ + ModifiedTransformedPrompt: String(""), + }) + if err != nil { + t.Fatalf("failed to marshal hook output: %v", err) + } + if string(data) != `{"modifiedTransformedPrompt":""}` { + t.Fatalf("expected empty replacement to be preserved, got %s", data) + } +} + func TestProviderConfig_JSONIncludesHeaders(t *testing.T) { config := ProviderConfig{ BaseURL: "https://example.com/provider", diff --git a/go/zsession_events.go b/go/zsession_events.go index 1d35a0a51..48ad42849 100644 --- a/go/zsession_events.go +++ b/go/zsession_events.go @@ -53,6 +53,7 @@ type ( AttachmentSelectionDetailsEnd = rpc.AttachmentSelectionDetailsEnd AttachmentSelectionDetailsStart = rpc.AttachmentSelectionDetailsStart AttachmentType = rpc.AttachmentType + AutoApprovalJudgeFailureReason = rpc.AutoApprovalJudgeFailureReason AutoApprovalRecommendation = rpc.AutoApprovalRecommendation AutoModeResolvedReasoningBucket = rpc.AutoModeResolvedReasoningBucket AutoModeSwitchCompletedData = rpc.AutoModeSwitchCompletedData @@ -85,6 +86,7 @@ type ( CommandsChangedData = rpc.CommandsChangedData CompactionCompleteCompactionTokensUsed = rpc.CompactionCompleteCompactionTokensUsed CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail = rpc.CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail + CompactionTrigger = rpc.CompactionTrigger ContextTier = rpc.ContextTier CustomAgentsUpdatedAgent = rpc.CustomAgentsUpdatedAgent ElicitationCompletedAction = rpc.ElicitationCompletedAction @@ -103,6 +105,9 @@ type ( ExtensionsLoadedExtensionStatus = rpc.ExtensionsLoadedExtensionStatus ExternalToolCompletedData = rpc.ExternalToolCompletedData ExternalToolRequestedData = rpc.ExternalToolRequestedData + FactoryPermissionOperation = rpc.FactoryPermissionOperation + FactoryPermissionPhase = rpc.FactoryPermissionPhase + FactoryRunUpdatedData = rpc.FactoryRunUpdatedData GitHubRepoRef = rpc.GitHubRepoRef HandoffRepository = rpc.HandoffRepository HandoffSourceType = rpc.HandoffSourceType @@ -165,6 +170,7 @@ type ( PermissionPromptRequestCustomTool = rpc.PermissionPromptRequestCustomTool PermissionPromptRequestExtensionManagement = rpc.PermissionPromptRequestExtensionManagement PermissionPromptRequestExtensionPermissionAccess = rpc.PermissionPromptRequestExtensionPermissionAccess + PermissionPromptRequestFactory = rpc.PermissionPromptRequestFactory PermissionPromptRequestHook = rpc.PermissionPromptRequestHook PermissionPromptRequestKind = rpc.PermissionPromptRequestKind PermissionPromptRequestMCP = rpc.PermissionPromptRequestMCP @@ -180,6 +186,7 @@ type ( PermissionRequestedData = rpc.PermissionRequestedData PermissionRequestExtensionManagement = rpc.PermissionRequestExtensionManagement PermissionRequestExtensionPermissionAccess = rpc.PermissionRequestExtensionPermissionAccess + PermissionRequestFactory = rpc.PermissionRequestFactory PermissionRequestHook = rpc.PermissionRequestHook PermissionRequestKind = rpc.PermissionRequestKind PermissionRequestMCP = rpc.PermissionRequestMCP @@ -189,6 +196,7 @@ type ( PermissionRequestRead = rpc.PermissionRequestRead PermissionRequestShell = rpc.PermissionRequestShell PermissionRequestShellCommand = rpc.PermissionRequestShellCommand + PermissionRequestShellCommandSegment = rpc.PermissionRequestShellCommandSegment PermissionRequestShellPossibleURL = rpc.PermissionRequestShellPossibleURL PermissionRequestURL = rpc.PermissionRequestURL PermissionRequestWrite = rpc.PermissionRequestWrite @@ -212,6 +220,7 @@ type ( ReasoningSummary = rpc.ReasoningSummary SamplingCompletedData = rpc.SamplingCompletedData SamplingRequestedData = rpc.SamplingRequestedData + ScheduleOrigin = rpc.ScheduleOrigin SessionAutoModeResolvedData = rpc.SessionAutoModeResolvedData SessionAutopilotObjectiveChangedData = rpc.SessionAutopilotObjectiveChangedData SessionBackgroundTasksChangedData = rpc.SessionBackgroundTasksChangedData @@ -225,6 +234,7 @@ type ( SessionCompactionCompleteData = rpc.SessionCompactionCompleteData SessionCompactionStartData = rpc.SessionCompactionStartData SessionContextChangedData = rpc.SessionContextChangedData + SessionContextClearedData = rpc.SessionContextClearedData SessionCustomAgentsUpdatedData = rpc.SessionCustomAgentsUpdatedData SessionCustomNotificationData = rpc.SessionCustomNotificationData SessionErrorData = rpc.SessionErrorData @@ -293,11 +303,15 @@ type ( SystemNotificationAgentCompletedStatus = rpc.SystemNotificationAgentCompletedStatus SystemNotificationAgentIdle = rpc.SystemNotificationAgentIdle SystemNotificationData = rpc.SystemNotificationData + SystemNotificationFactoryCompleted = rpc.SystemNotificationFactoryCompleted + SystemNotificationFactoryCompletedStatus = rpc.SystemNotificationFactoryCompletedStatus SystemNotificationInstructionDiscovered = rpc.SystemNotificationInstructionDiscovered SystemNotificationNewInboxMessage = rpc.SystemNotificationNewInboxMessage SystemNotificationShellCompleted = rpc.SystemNotificationShellCompleted SystemNotificationShellDetachedCompleted = rpc.SystemNotificationShellDetachedCompleted SystemNotificationType = rpc.SystemNotificationType + SystemNotificationUnclassified = rpc.SystemNotificationUnclassified + TaskCompletionOutcome = rpc.TaskCompletionOutcome ToolExecutionCompleteContent = rpc.ToolExecutionCompleteContent ToolExecutionCompleteContentAudio = rpc.ToolExecutionCompleteContentAudio ToolExecutionCompleteContentImage = rpc.ToolExecutionCompleteContentImage @@ -346,6 +360,7 @@ type ( UserToolSessionApprovalCustomTool = rpc.UserToolSessionApprovalCustomTool UserToolSessionApprovalExtensionManagement = rpc.UserToolSessionApprovalExtensionManagement UserToolSessionApprovalExtensionPermissionAccess = rpc.UserToolSessionApprovalExtensionPermissionAccess + UserToolSessionApprovalFactory = rpc.UserToolSessionApprovalFactory UserToolSessionApprovalKind = rpc.UserToolSessionApprovalKind UserToolSessionApprovalMCP = rpc.UserToolSessionApprovalMCP UserToolSessionApprovalMemory = rpc.UserToolSessionApprovalMemory @@ -359,6 +374,7 @@ type ( // Session-event constants are generated in the rpc package and re-exported here for source compatibility. const ( + AbortReasonAutopilotCreditLimit = rpc.AbortReasonAutopilotCreditLimit AbortReasonRemoteCommand = rpc.AbortReasonRemoteCommand AbortReasonUserAbort = rpc.AbortReasonUserAbort AbortReasonUserInitiated = rpc.AbortReasonUserInitiated @@ -386,6 +402,11 @@ const ( AttachmentTypeGitHubTreeComparison = rpc.AttachmentTypeGitHubTreeComparison AttachmentTypeGitHubURL = rpc.AttachmentTypeGitHubURL AttachmentTypeSelection = rpc.AttachmentTypeSelection + AutoApprovalJudgeFailureReasonAbort = rpc.AutoApprovalJudgeFailureReasonAbort + AutoApprovalJudgeFailureReasonEmptyResponse = rpc.AutoApprovalJudgeFailureReasonEmptyResponse + AutoApprovalJudgeFailureReasonModelError = rpc.AutoApprovalJudgeFailureReasonModelError + AutoApprovalJudgeFailureReasonParseError = rpc.AutoApprovalJudgeFailureReasonParseError + AutoApprovalJudgeFailureReasonTimeout = rpc.AutoApprovalJudgeFailureReasonTimeout AutoApprovalRecommendationApprove = rpc.AutoApprovalRecommendationApprove AutoApprovalRecommendationError = rpc.AutoApprovalRecommendationError AutoApprovalRecommendationExcluded = rpc.AutoApprovalRecommendationExcluded @@ -413,6 +434,11 @@ const ( CitationProviderAnthropic = rpc.CitationProviderAnthropic CitationProviderClient = rpc.CitationProviderClient CitationProviderOpenai = rpc.CitationProviderOpenai + CompactionTriggerContextLimitRetry = rpc.CompactionTriggerContextLimitRetry + CompactionTriggerManual = rpc.CompactionTriggerManual + CompactionTriggerMemoryPressure = rpc.CompactionTriggerMemoryPressure + CompactionTriggerModelSwitch = rpc.CompactionTriggerModelSwitch + CompactionTriggerThreshold = rpc.CompactionTriggerThreshold ContextTierDefault = rpc.ContextTierDefault ContextTierLongContext = rpc.ContextTierLongContext ElicitationCompletedActionAccept = rpc.ElicitationCompletedActionAccept @@ -433,6 +459,8 @@ const ( ExtensionsLoadedExtensionStatusFailed = rpc.ExtensionsLoadedExtensionStatusFailed ExtensionsLoadedExtensionStatusRunning = rpc.ExtensionsLoadedExtensionStatusRunning ExtensionsLoadedExtensionStatusStarting = rpc.ExtensionsLoadedExtensionStatusStarting + FactoryPermissionOperationAuthor = rpc.FactoryPermissionOperationAuthor + FactoryPermissionOperationRun = rpc.FactoryPermissionOperationRun HandoffSourceTypeLocal = rpc.HandoffSourceTypeLocal HandoffSourceTypeRemote = rpc.HandoffSourceTypeRemote ManagedSettingsEnforcedActionBypassPermissionsBlocked = rpc.ManagedSettingsEnforcedActionBypassPermissionsBlocked @@ -441,7 +469,9 @@ const ( ManagedSettingsEnforcedEscalationAutoApproval = rpc.ManagedSettingsEnforcedEscalationAutoApproval ManagedSettingsEnforcedEscalationUnrestrictedPaths = rpc.ManagedSettingsEnforcedEscalationUnrestrictedPaths ManagedSettingsEnforcedEscalationUnrestrictedURLs = rpc.ManagedSettingsEnforcedEscalationUnrestrictedURLs + ManagedSettingsResolvedSourceClient = rpc.ManagedSettingsResolvedSourceClient ManagedSettingsResolvedSourceDevice = rpc.ManagedSettingsResolvedSourceDevice + ManagedSettingsResolvedSourceMixed = rpc.ManagedSettingsResolvedSourceMixed ManagedSettingsResolvedSourceNone = rpc.ManagedSettingsResolvedSourceNone ManagedSettingsResolvedSourceServer = rpc.ManagedSettingsResolvedSourceServer MCPHeadersRefreshCompletedOutcomeHeaders = rpc.MCPHeadersRefreshCompletedOutcomeHeaders @@ -467,6 +497,7 @@ const ( MCPServerStatusNeedsAuth = rpc.MCPServerStatusNeedsAuth MCPServerStatusNotConfigured = rpc.MCPServerStatusNotConfigured MCPServerStatusPending = rpc.MCPServerStatusPending + MCPServerStatusStopped = rpc.MCPServerStatusStopped MCPServerTransportHTTP = rpc.MCPServerTransportHTTP MCPServerTransportMemory = rpc.MCPServerTransportMemory MCPServerTransportSSE = rpc.MCPServerTransportSSE @@ -491,6 +522,7 @@ const ( PermissionPromptRequestKindCustomTool = rpc.PermissionPromptRequestKindCustomTool PermissionPromptRequestKindExtensionManagement = rpc.PermissionPromptRequestKindExtensionManagement PermissionPromptRequestKindExtensionPermissionAccess = rpc.PermissionPromptRequestKindExtensionPermissionAccess + PermissionPromptRequestKindFactory = rpc.PermissionPromptRequestKindFactory PermissionPromptRequestKindHook = rpc.PermissionPromptRequestKindHook PermissionPromptRequestKindMCP = rpc.PermissionPromptRequestKindMCP PermissionPromptRequestKindMemory = rpc.PermissionPromptRequestKindMemory @@ -504,6 +536,7 @@ const ( PermissionRequestKindCustomTool = rpc.PermissionRequestKindCustomTool PermissionRequestKindExtensionManagement = rpc.PermissionRequestKindExtensionManagement PermissionRequestKindExtensionPermissionAccess = rpc.PermissionRequestKindExtensionPermissionAccess + PermissionRequestKindFactory = rpc.PermissionRequestKindFactory PermissionRequestKindHook = rpc.PermissionRequestKindHook PermissionRequestKindMCP = rpc.PermissionRequestKindMCP PermissionRequestKindMemory = rpc.PermissionRequestKindMemory @@ -534,6 +567,8 @@ const ( ReasoningSummaryConcise = rpc.ReasoningSummaryConcise ReasoningSummaryDetailed = rpc.ReasoningSummaryDetailed ReasoningSummaryNone = rpc.ReasoningSummaryNone + ScheduleOriginModel = rpc.ScheduleOriginModel + ScheduleOriginUser = rpc.ScheduleOriginUser SessionEventTypeAbort = rpc.SessionEventTypeAbort SessionEventTypeAssistantIdle = rpc.SessionEventTypeAssistantIdle SessionEventTypeAssistantIntent = rpc.SessionEventTypeAssistantIntent @@ -562,6 +597,7 @@ const ( SessionEventTypeExitPlanModeRequested = rpc.SessionEventTypeExitPlanModeRequested SessionEventTypeExternalToolCompleted = rpc.SessionEventTypeExternalToolCompleted SessionEventTypeExternalToolRequested = rpc.SessionEventTypeExternalToolRequested + SessionEventTypeFactoryRunUpdated = rpc.SessionEventTypeFactoryRunUpdated SessionEventTypeHookEnd = rpc.SessionEventTypeHookEnd SessionEventTypeHookProgress = rpc.SessionEventTypeHookProgress SessionEventTypeHookStart = rpc.SessionEventTypeHookStart @@ -593,6 +629,7 @@ const ( SessionEventTypeSessionCompactionComplete = rpc.SessionEventTypeSessionCompactionComplete SessionEventTypeSessionCompactionStart = rpc.SessionEventTypeSessionCompactionStart SessionEventTypeSessionContextChanged = rpc.SessionEventTypeSessionContextChanged + SessionEventTypeSessionContextCleared = rpc.SessionEventTypeSessionContextCleared SessionEventTypeSessionCustomAgentsUpdated = rpc.SessionEventTypeSessionCustomAgentsUpdated SessionEventTypeSessionCustomNotification = rpc.SessionEventTypeSessionCustomNotification SessionEventTypeSessionError = rpc.SessionEventTypeSessionError @@ -670,12 +707,21 @@ const ( SystemMessageRoleSystem = rpc.SystemMessageRoleSystem SystemNotificationAgentCompletedStatusCompleted = rpc.SystemNotificationAgentCompletedStatusCompleted SystemNotificationAgentCompletedStatusFailed = rpc.SystemNotificationAgentCompletedStatusFailed + SystemNotificationFactoryCompletedStatusCancelled = rpc.SystemNotificationFactoryCompletedStatusCancelled + SystemNotificationFactoryCompletedStatusCompleted = rpc.SystemNotificationFactoryCompletedStatusCompleted + SystemNotificationFactoryCompletedStatusError = rpc.SystemNotificationFactoryCompletedStatusError + SystemNotificationFactoryCompletedStatusHalted = rpc.SystemNotificationFactoryCompletedStatusHalted SystemNotificationTypeAgentCompleted = rpc.SystemNotificationTypeAgentCompleted SystemNotificationTypeAgentIdle = rpc.SystemNotificationTypeAgentIdle + SystemNotificationTypeFactoryCompleted = rpc.SystemNotificationTypeFactoryCompleted SystemNotificationTypeInstructionDiscovered = rpc.SystemNotificationTypeInstructionDiscovered SystemNotificationTypeNewInboxMessage = rpc.SystemNotificationTypeNewInboxMessage SystemNotificationTypeShellCompleted = rpc.SystemNotificationTypeShellCompleted SystemNotificationTypeShellDetachedCompleted = rpc.SystemNotificationTypeShellDetachedCompleted + SystemNotificationTypeUnclassified = rpc.SystemNotificationTypeUnclassified + TaskCompletionOutcomeBlocked = rpc.TaskCompletionOutcomeBlocked + TaskCompletionOutcomeCompleted = rpc.TaskCompletionOutcomeCompleted + TaskCompletionOutcomeContinue = rpc.TaskCompletionOutcomeContinue ToolExecutionCompleteContentResourceLinkIconThemeDark = rpc.ToolExecutionCompleteContentResourceLinkIconThemeDark ToolExecutionCompleteContentResourceLinkIconThemeLight = rpc.ToolExecutionCompleteContentResourceLinkIconThemeLight ToolExecutionCompleteContentTypeAudio = rpc.ToolExecutionCompleteContentTypeAudio @@ -700,6 +746,7 @@ const ( UserToolSessionApprovalKindCustomTool = rpc.UserToolSessionApprovalKindCustomTool UserToolSessionApprovalKindExtensionManagement = rpc.UserToolSessionApprovalKindExtensionManagement UserToolSessionApprovalKindExtensionPermissionAccess = rpc.UserToolSessionApprovalKindExtensionPermissionAccess + UserToolSessionApprovalKindFactory = rpc.UserToolSessionApprovalKindFactory UserToolSessionApprovalKindMCP = rpc.UserToolSessionApprovalKindMCP UserToolSessionApprovalKindMemory = rpc.UserToolSessionApprovalKindMemory UserToolSessionApprovalKindRead = rpc.UserToolSessionApprovalKindRead diff --git a/java/README.md b/java/README.md index e7a34bca7..e64e22d81 100644 --- a/java/README.md +++ b/java/README.md @@ -32,14 +32,14 @@ Replace `${copilot.sdk.version}` with the latest release from Maven Central. com.github copilot-sdk-java - 1.0.5-01 + 1.0.11-preview.2 ``` ### Gradle ```groovy -implementation 'com.github:copilot-sdk-java:1.0.8-preview.0-01' +implementation 'com.github:copilot-sdk-java:1.0.11-preview.2' ``` #### Snapshot Builds @@ -58,7 +58,7 @@ Snapshot builds of the next development version are published to Maven Central S com.github copilot-sdk-java - 1.0.9-preview.0-SNAPSHOT + 1.0.12-preview.2-SNAPSHOT ``` @@ -67,7 +67,53 @@ Snapshot builds of the next development version are published to Maven Central S Replace `${copilot.sdk.version}` with the latest release from Maven Central. ```groovy -implementation 'com.github:copilot-sdk-java:1.0.8-preview.0-01-SNAPSHOT' +implementation 'com.github:copilot-sdk-java:1.0.12-preview.2-SNAPSHOT' +``` + +## In-process mode (experimental) + +The SDK supports running the Copilot runtime **in-process** as a native library instead of spawning a separate CLI process. This eliminates process management overhead and simplifies deployment. In-process mode is currently experimental and only supported on **linux-x64**. + +Because in-process mode is experimental, see the [Using experimental APIs](#using-experimental-apis) section for how to opt in. + +### Additional dependency + +Add both the SDK and the platform-specific native runtime to your project: + +```xml + + + + com.github + copilot-sdk-java + ${copilot.version} + + + + com.github + copilot-sdk-java-runtime + ${copilot.version} + linux-x64 + + + + net.java.dev.jna + jna + 5.19.1 + + +``` + +### Usage + +Configure the client to use the in-process connection: + +```java +CopilotClientOptions options = new CopilotClientOptions() + .setConnection(RuntimeConnection.forInProcess()); + +CopilotClient client = new CopilotClient(options); +client.start().get(); ``` ## Quick Start @@ -120,16 +166,48 @@ public class CopilotSDK { } ``` +When targeting MCP tools configured through `setMcpServers(...)`, remember the +runtime tool name is `-`. For `setAvailableTools(...)` +and `setExcludedTools(...)`, prefer the source-qualified filter form +`mcp:-`. For `CustomAgentConfig.setTools(...)` and +`DefaultAgentConfig.setExcludedTools(...)`, use `-` +directly. + +`CopilotClientOptions.setCwd(...)` sets the runtime process working directory, which otherwise inherits the current process working directory. `SessionConfig.setWorkingDirectory(...)` sets the session working directory, which otherwise defaults to the runtime process working directory. + +## Permission Handling + +`PermissionHandler.APPROVE_ALL` approves requests when managed settings are disabled. When `enableManagedSettings` is true, it completes exceptionally. Custom handlers can inspect `request.getManagedApprovalRequired()` for human-facing confirmation logic. + +When handling `PermissionRequestedEvent` directly, convert its generated event value with `PermissionRequest.fromJsonValue(event.getData().permissionRequest())` to access the typed metadata. + +Custom handlers must check managed approval before applying kind-specific automatic decisions: + +```java +import java.util.concurrent.CompletableFuture; + +import com.github.copilot.rpc.PermissionHandler; +import com.github.copilot.rpc.PermissionRequestResult; + +PermissionHandler handler = (request, invocation) -> { + if (Boolean.TRUE.equals(request.getManagedApprovalRequired())) { + return CompletableFuture.completedFuture(PermissionRequestResult.noResult()); + } + + return CompletableFuture.completedFuture(PermissionRequestResult.approveOnce()); +}; +``` + ## Try it with JBang You can run the SDK without setting up a full Java project, by using [JBang](https://www.jbang.dev/). -See the full source of [`jbang-example.java`](jbang-example.java) for a complete example with more features like session idle handling and usage info events. +See the full source of [`jbang-example.java`](sdk/jbang-example.java) for a complete example with more features like session idle handling and usage info events. Or run it directly from the repository: ```bash -jbang https://github.com/github/copilot-sdk/blob/main/java/jbang-example.java +jbang https://github.com/github/copilot-sdk/blob/main/java/sdk/jbang-example.java ``` ## Annotation-based tools and `ToolInvocation` context @@ -232,6 +310,10 @@ Chain fluent modifiers to set tool options: For design context and decision rationale, see [ADR-006](docs/adr/adr-006-tool-definition-inline.md). +## Session Store + +`enableSessionStore` on `SessionConfig` enables the cross-session store for search and retrieval across sessions. When unset in the default `CopilotClientMode.COPILOT_CLI` mode, the runtime default applies (enabled). In `CopilotClientMode.EMPTY` mode, defaults to disabled. + ## Memory Sessions can opt into persistent memory, allowing the agent to read and write memory across turns. Memory is configured per session and applies to both `createSession` and `resumeSession`. @@ -386,7 +468,7 @@ The gate also applies to individual methods annotated with `@CopilotExperimental ### Development Setup -Requires JDK 25 or later for development. The following steps validate the artifact built with JDK 25 runs on both 25 and 17, preserving the MR-JAR behavior. +Requires JDK 25 or later and a supported [Node.js version](../nodejs/README.md#prerequisites) for development. The following steps validate the artifact built with JDK 25 runs on both 25 and 17, preserving the MR-JAR behavior. ```bash # Clone the repository @@ -407,4 +489,4 @@ mvn jacoco:prepare-agent@wire-up-coverage-instrumentation antrun:run@print-test- ## License -MIT — see [LICENSE](LICENSE) for details. +MIT — see [LICENSE](sdk/LICENSE) for details. diff --git a/java/copilot-native/pom.xml b/java/copilot-native/pom.xml new file mode 100644 index 000000000..ef4ec02ab --- /dev/null +++ b/java/copilot-native/pom.xml @@ -0,0 +1,269 @@ + + + + 4.0.0 + + + com.github + copilot-sdk-java-parent + 1.0.12-preview.2-SNAPSHOT + ../pom.xml + + + com.github + copilot-sdk-java-runtime + jar + + GitHub Copilot SDK :: Java :: Native Runtime + Native runtime binaries for the GitHub Copilot Java SDK, published as per-platform classifier JARs + https://github.com/github/copilot-sdk + + + scm:git:https://github.com/github/copilot-sdk.git + scm:git:https://github.com/github/copilot-sdk.git + https://github.com/github/copilot-sdk + HEAD + + + + + ${project.basedir}/../.. + + linux-x64 + ${project.build.directory}/native-staging + + false + + + + + + + src/main/resources + true + + + + + + org.codehaus.mojo + exec-maven-plugin + + + fetch-native-linux-x64 + generate-resources + + exec + + + node + + ${project.basedir}/scripts/fetch-native.mjs + ${copilot.sdk.root} + ${copilot.native.staging} + ${copilot.native.classifier} + + + + + test-fetch-native + test + + exec + + + node + + --test + ${project.basedir}/scripts/fetch-native.test.mjs + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + jar-linux-x64 + package + + jar + + + ${copilot.native.classifier} + ${copilot.native.staging}/${copilot.native.classifier} + + .version + + + + + + empty-javadoc-jar + package + + jar + + + javadoc + ${project.basedir}/src/main/javadoc + + + + + empty-sources-jar + package + + jar + + + sources + ${project.basedir}/src/main/java + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + verify-native-jars + package + + run + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.sonatype.central + central-publishing-maven-plugin + true + + central + true + + + + + + + + + skip-native-download + + + copilot.native.skip.download + true + + + + + + org.codehaus.mojo + exec-maven-plugin + + true + + + + org.apache.maven.plugins + maven-jar-plugin + + + jar-linux-x64 + none + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + verify-native-jars + none + + + + + + + + diff --git a/java/copilot-native/scripts/fetch-native.mjs b/java/copilot-native/scripts/fetch-native.mjs new file mode 100644 index 000000000..7b68f0406 --- /dev/null +++ b/java/copilot-native/scripts/fetch-native.mjs @@ -0,0 +1,134 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +/** + * Downloads the `runtime.node` native binary for a single platform classifier + * and stages it for packaging into a classifier JAR. + * + * Steps: + * 1. Read the pinned version and the SHA-512 `integrity` value for + * `@github/copilot-` from `nodejs/package-lock.json`. + * 2. `npm pack` that exact version into the staging directory. + * 3. Verify the downloaded tarball against the `integrity` value. + * 4. Extract `package/prebuilds//runtime.node` to + * `//native//runtime.node`. + * 5. Extract `package/copilot` (or `package/copilot.exe` on Windows) to + * `//native//copilot`. + * 6. Write `//native//platform.properties`. + * + * Usage: node fetch-native.mjs + */ + +import { createHash } from 'node:crypto'; +import { execFileSync } from 'node:child_process'; +import fs from 'node:fs'; +import path from 'node:path'; + +const [repoRoot, stagingDir, classifier] = process.argv.slice(2); + +if (!repoRoot || !stagingDir || !classifier) { + console.error('Usage: node fetch-native.mjs '); + process.exit(1); +} + +const lockPath = path.join(repoRoot, 'nodejs', 'package-lock.json'); +const packageName = `@github/copilot-${classifier}`; +const lock = JSON.parse(fs.readFileSync(lockPath, 'utf8')); +const entry = lock.packages?.[`node_modules/${packageName}`]; + +if (!entry?.version || !entry?.integrity) { + console.error(`Could not find version/integrity for ${packageName} in ${lockPath}`); + process.exit(1); +} + +const { version, integrity } = entry; +if (!integrity.startsWith('sha512-')) { + console.error(`Unsupported integrity algorithm for ${packageName}: ${integrity}`); + process.exit(1); +} + +const outDir = path.join(stagingDir, classifier); +const resourceDir = path.join(outDir, 'native', classifier); +const runtimePath = path.join(resourceDir, 'runtime.node'); +const isWindows = classifier.startsWith('win32'); +const cliTarballMember = isWindows ? 'package/copilot.exe' : 'package/copilot'; +const cliFilename = isWindows ? 'copilot.exe' : 'copilot'; +const cliPath = path.join(resourceDir, cliFilename); +const platformPropertiesPath = path.join(resourceDir, 'platform.properties'); +const expectedPlatformProperties = `classifier=${classifier}\nversion=${version}\n`; +const stampPath = path.join(outDir, '.version'); + +// Idempotence: skip the download only when every required staged artifact +// matches the package identity recorded in the stamp. +if ( + fs.existsSync(runtimePath) && + fs.existsSync(cliPath) && + fs.existsSync(platformPropertiesPath) && + fs.existsSync(stampPath) +) { + const stampLines = fs.readFileSync(stampPath, 'utf8').trim().split('\n'); + const stampVersion = stampLines[0] || ''; + const stampIntegrity = stampLines[1] || ''; + const stampRuntimeDigest = stampLines[2] || ''; + const stampCliDigest = stampLines[3] || ''; + const currentRuntimeDigest = digestFile(runtimePath); + const currentCliDigest = digestFile(cliPath); + const currentPlatformProperties = fs.readFileSync(platformPropertiesPath, 'utf8'); + if ( + stampVersion === version && + stampIntegrity === integrity && + stampRuntimeDigest === currentRuntimeDigest && + stampCliDigest === currentCliDigest && + currentPlatformProperties === expectedPlatformProperties + ) { + console.log(`${packageName}@${version} already staged at ${runtimePath}`); + process.exit(0); + } +} + +fs.rmSync(outDir, { recursive: true, force: true }); +fs.mkdirSync(resourceDir, { recursive: true }); + +console.log(`Downloading ${packageName}@${version} ...`); +const packOutput = execFileSync('npm', ['pack', `${packageName}@${version}`, '--pack-destination', outDir], { + encoding: 'utf8', + shell: process.platform === 'win32', +}); +const tarballName = packOutput.trim().split('\n').pop().trim(); +const tarballPath = path.join(outDir, tarballName); + +const actual = `sha512-${createHash('sha512').update(fs.readFileSync(tarballPath)).digest('base64')}`; +if (actual !== integrity) { + console.error(`Integrity verification failed for ${tarballPath}`); + console.error(` expected: ${integrity}`); + console.error(` actual: ${actual}`); + process.exit(1); +} +console.log(`Integrity verified (${integrity.slice(0, 20)}...).`); + +const memberPath = `package/prebuilds/${classifier}/runtime.node`; +execFileSync('tar', ['-xzf', tarballPath, '-C', outDir, memberPath], { stdio: 'inherit' }); +fs.renameSync(path.join(outDir, memberPath), runtimePath); + +// Extract the copilot CLI executable (necessary-and-sufficient runtime artifact invariant: +// host_start needs both runtime.node and the copilot CLI from the same package version). +execFileSync('tar', ['-xzf', tarballPath, '-C', outDir, cliTarballMember], { stdio: 'inherit' }); +fs.renameSync(path.join(outDir, cliTarballMember), cliPath); +if (!isWindows) { + fs.chmodSync(cliPath, 0o755); +} + +fs.rmSync(path.join(outDir, 'package'), { recursive: true, force: true }); +fs.rmSync(tarballPath, { force: true }); + +fs.writeFileSync(platformPropertiesPath, expectedPlatformProperties); +const runtimeDigest = digestFile(runtimePath); +const cliDigest = digestFile(cliPath); +fs.writeFileSync(stampPath, `${version}\n${integrity}\n${runtimeDigest}\n${cliDigest}\n`); + +console.log(`Staged ${runtimePath}`); + +function digestFile(filePath) { + return `sha512-${createHash('sha512').update(fs.readFileSync(filePath)).digest('base64')}`; +} diff --git a/java/copilot-native/scripts/fetch-native.test.mjs b/java/copilot-native/scripts/fetch-native.test.mjs new file mode 100644 index 000000000..a80cd0387 --- /dev/null +++ b/java/copilot-native/scripts/fetch-native.test.mjs @@ -0,0 +1,124 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'node:assert/strict'; +import { createHash } from 'node:crypto'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { spawnSync } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; +import test from 'node:test'; + +const classifier = 'linux-x64'; +const version = '1.0.79'; +const integrity = 'sha512-test-integrity'; +const runtimeContent = 'runtime content'; +const cliContent = 'cli content'; +const scriptPath = fileURLToPath(new URL('./fetch-native.mjs', import.meta.url)); + +test('missing CLI does not use incremental fast path', (t) => { + const fixture = createFixture(t); + fs.rmSync(fixture.cliPath); + + const result = runScript(fixture); + + assertRestagingAttempted(fixture, result); +}); + +test('stale CLI does not use incremental fast path', (t) => { + const fixture = createFixture(t); + fs.writeFileSync(fixture.cliPath, 'stale CLI content'); + + const result = runScript(fixture); + + assertRestagingAttempted(fixture, result); +}); + +test('missing platform metadata does not use incremental fast path', (t) => { + const fixture = createFixture(t); + fs.rmSync(fixture.platformPropertiesPath); + + const result = runScript(fixture); + + assertRestagingAttempted(fixture, result); +}); + +test('complete matching artifacts use incremental fast path', (t) => { + const fixture = createFixture(t); + + const result = runScript(fixture); + + assert.equal(result.status, 0, result.stderr); + assert.match(result.stdout, /already staged/); + assert.equal(fs.existsSync(fixture.npmMarkerPath), false); +}); + +function createFixture(t) { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'fetch-native-test-')); + t.after(() => fs.rmSync(root, { recursive: true, force: true })); + + const repoRoot = path.join(root, 'repo'); + const stagingDir = path.join(root, 'staging'); + const resourceDir = path.join(stagingDir, classifier, 'native', classifier); + const fakeBinDir = path.join(root, 'bin'); + const npmMarkerPath = path.join(root, 'npm-invoked'); + fs.mkdirSync(path.join(repoRoot, 'nodejs'), { recursive: true }); + fs.mkdirSync(resourceDir, { recursive: true }); + fs.mkdirSync(fakeBinDir); + + fs.writeFileSync( + path.join(repoRoot, 'nodejs', 'package-lock.json'), + JSON.stringify({ + packages: { + [`node_modules/@github/copilot-${classifier}`]: { version, integrity }, + }, + }), + ); + + const runtimePath = path.join(resourceDir, 'runtime.node'); + const cliPath = path.join(resourceDir, 'copilot'); + const platformPropertiesPath = path.join(resourceDir, 'platform.properties'); + fs.writeFileSync(runtimePath, runtimeContent); + fs.writeFileSync(cliPath, cliContent); + fs.writeFileSync(platformPropertiesPath, `classifier=${classifier}\nversion=${version}\n`); + fs.writeFileSync( + path.join(stagingDir, classifier, '.version'), + `${version}\n${integrity}\n${digest(runtimeContent)}\n${digest(cliContent)}\n`, + ); + + const fakeNpmPath = path.join(fakeBinDir, 'npm'); + fs.writeFileSync(fakeNpmPath, '#!/bin/sh\nprintf invoked > \"$FETCH_NATIVE_NPM_MARKER\"\nexit 42\n'); + fs.chmodSync(fakeNpmPath, 0o755); + + return { + repoRoot, + stagingDir, + fakeBinDir, + npmMarkerPath, + runtimePath, + cliPath, + platformPropertiesPath, + }; +} + +function runScript(fixture) { + return spawnSync(process.execPath, [scriptPath, fixture.repoRoot, fixture.stagingDir, classifier], { + encoding: 'utf8', + env: { + ...process.env, + PATH: `${fixture.fakeBinDir}${path.delimiter}${process.env.PATH}`, + FETCH_NATIVE_NPM_MARKER: fixture.npmMarkerPath, + }, + }); +} + +function assertRestagingAttempted(fixture, result) { + assert.notEqual(result.status, 0, 'The fake npm command should make restaging fail'); + assert.equal(fs.readFileSync(fixture.npmMarkerPath, 'utf8'), 'invoked'); +} + +function digest(content) { + return `sha512-${createHash('sha512').update(content).digest('base64')}`; +} diff --git a/java/copilot-native/src/main/java/.gitkeep b/java/copilot-native/src/main/java/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/java/copilot-native/src/main/javadoc/.gitkeep b/java/copilot-native/src/main/javadoc/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/java/copilot-native/src/main/resources/native/lib/copilot-runtime.properties b/java/copilot-native/src/main/resources/native/lib/copilot-runtime.properties new file mode 100644 index 000000000..0f3230898 --- /dev/null +++ b/java/copilot-native/src/main/resources/native/lib/copilot-runtime.properties @@ -0,0 +1,12 @@ +# Placeholder marker for the primary (classifier-less) artifact of +# com.github:copilot-sdk-java-runtime. +# +# The real native binaries ship in per-platform classifier JARs +# (e.g. copilot-sdk-java-runtime--linux-x64.jar) under +# native//runtime.node. This primary JAR exists only to satisfy +# Maven Central's requirement for a main artifact and intentionally contains +# no native binaries. +# +# This file is processed by Maven resource filtering. +placeholder=true +version=${project.version} diff --git a/java/docs/adr/adr-007-native-bundling-strategy.md b/java/docs/adr/adr-007-native-bundling-strategy.md index 1322e199e..f561842fe 100644 --- a/java/docs/adr/adr-007-native-bundling-strategy.md +++ b/java/docs/adr/adr-007-native-bundling-strategy.md @@ -1,85 +1,109 @@ -# ADR-007: Native runtime bundling strategy — per-platform classifier JARs +# ADR-007: Native runtime bundling strategy: per-platform classifier JARs -## Context and Problem Statement +## Context and problem statement -The Copilot SDK for Java currently has no embedded runtime. It depends on an externally provided runtime process (see epic [#1917](https://github.com/github/copilot-sdk/issues/1917)). The ongoing Rust port of the `copilot-agent-runtime` repository is reaching the point where the runtime can be consumed as a native shared library without requiring a Node.js process, making it practical to embed the runtime directly in the SDK JAR. +The Copilot SDK for Java supports an experimental in-process connection that loads the Copilot agent runtime as a native shared library. The existing stdio, TCP, and URI connections remain the default behavior unless the user explicitly selects the in-process connection. ### The runtime artifact The artifact to be embedded is `runtime.node`, a Rust [`cdylib`](#references) produced by the `src/runtime` crate in `github/copilot-agent-runtime` using the [napi-rs](#references) build toolchain. Despite the `.node` file extension (a naming convention of napi-rs), this is an ordinary platform-specific shared library (`.so` on Linux, `.dylib` on macOS, `.dll` on Windows). It exposes two front doors built over the same internal engine: -- **[napi](#references) front door** — loaded by a Node.js process as a native addon (current CLI path). -- **[C ABI](#references) front door** — a fixed set of approximately 12 `extern "C"` lifecycle and transport entry points (`copilot_runtime_server_create`, `copilot_runtime_connection_open`, etc.) that any language can call in-process via [FFI](#references) ([JNA](#references) for Java, Python/cffi, C#/`DllImport`, Go/purego) **without a Node.js process**. All API methods travel as JSON-RPC data through this fixed transport; the export list never changes as the method set grows. +* **[napi](#references) front door**: loaded by a Node.js process as a native addon for the current CLI path. +* **[C ABI](#references) front door**: a fixed set of 5 `extern "C"` lifecycle and transport entry points that any language can call in-process via [FFI](#references) ([JNA](#references) for Java, Python/cffi, C#/`DllImport`, Go/purego). All API methods travel as JSON-RPC data through this fixed transport; the export list does not change as the method set grows. + + | Entry point | C signature | Purpose | + | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | `copilot_runtime_host_start` | `(const uint8_t* argv_json, size_t argv_json_len, const uint8_t* env_json, size_t env_json_len) → uint32_t` | Start the runtime host; `argv_json` is a JSON array (e.g., `["copilot","--embedded-host"]`), `env_json` is an optional JSON object of environment overrides. Returns a server handle (0 = failure). | + | `copilot_runtime_host_shutdown` | `(uint32_t server_id) → bool` | Shut down the runtime host identified by `server_id`. | + | `copilot_runtime_connection_open` | `(uint32_t server_id, void(*on_outbound)(void* user_data, const uint8_t* data, size_t len), void* user_data, const uint8_t* ext_source, size_t ext_source_len, const uint8_t* ext_name, size_t ext_name_len, const uint8_t* conn_token, size_t conn_token_len) → uint32_t` | Open a bidirectional connection on the server; registers the `on_outbound` callback for runtime→SDK data delivery. `ext_source`, `ext_name`, and `conn_token` are nullable metadata buffers. Returns a connection handle (0 = failure). | + | `copilot_runtime_connection_write` | `(uint32_t connection_id, const uint8_t* data, size_t len) → bool` | Write a JSON-RPC frame from the SDK into the runtime. The native side copies the buffer synchronously before returning. | + | `copilot_runtime_connection_close` | `(uint32_t connection_id) → bool` | Close a connection. | + + The outbound callback signature: `void on_outbound(void* user_data, const uint8_t* data, size_t len)` — invoked by native code (potentially on native threads) to deliver JSON-RPC responses and notifications back to the SDK. The `cli-native.node` addon — a separate, smaller artifact that provides ICU4X text segmentation, Win32 API wrappers, and terminal UI helpers — is a CLI-only artifact used by the Ink/React terminal interface. It is **not needed** by the Java SDK. ### Note on the active Rust migration -As of 2026-07, the `runtime.node` binary is being built up iteratively as TypeScript runtime code is ported into it. It is **not** being reduced; it is growing with each port PR. The `embedded_host.rs` module in the runtime currently spawns a short-lived child process to service method bodies not yet ported to Rust. This internal Node.js dependency shrinks with each port PR and is expected to disappear entirely when the migration completes. The C ABI surface and loading mechanism described in this ADR are stable regardless of migration progress. +As of 2026-08, the `runtime.node` binary is being built up iteratively as TypeScript runtime code is ported into it. It is **not** being reduced; it is growing with each port PR. The `embedded_host.rs` module currently starts a child `copilot --embedded-host` process to service method bodies not yet ported to Rust. + +The classifier JAR therefore contains a version-matched pair during the migration: + +* `runtime.node`: loaded into the Java process through JNA +* `copilot` or `copilot.exe`: started internally by `copilot_runtime_host_start` +* `platform.properties`: classifier and runtime version metadata + +The Java SDK does not independently spawn this child for JSON-RPC transport. The native runtime owns the transitional embedded-host process. The bundled CLI requirement disappears after the Rust migration is complete, while the C ABI and Java loading mechanism remain stable. ### Platform dimensions The runtime must be built for each unique combination of OS, CPU architecture, and (on Linux) C runtime variant. The build system in `github/copilot-agent-runtime` produces eight Rust target triples: -| Platform label | Rust triple | Constraint | -|---------------|-------------|------------| -| `linux-x64` | `x86_64-unknown-linux-gnu` | [glibc](#references) ≥ 2.28 (Debian 10+, Ubuntu 20.04+, RHEL 8+) | -| `linux-arm64` | `aarch64-unknown-linux-gnu` | glibc ≥ 2.28 | -| `linuxmusl-x64` | `x86_64-unknown-linux-musl` | dynamically links [musl libc](#references) (Alpine Linux) | -| `linuxmusl-arm64` | `aarch64-unknown-linux-musl` | dynamically links musl libc | -| `darwin-x64` | `x86_64-apple-darwin` | macOS, Intel | -| `darwin-arm64` | `aarch64-apple-darwin` | macOS, Apple Silicon | -| `win32-x64` | `x86_64-pc-windows-msvc` | [MSVC CRT](#references) statically linked (`+crt-static`) | -| `win32-arm64` | `aarch64-pc-windows-msvc` | MSVC CRT statically linked (`+crt-static`) | +| Platform label | Rust triple | Constraint | +| ----------------- | ---------------------------- | ---------------------------------------------------------------- | +| `linux-x64` | `x86_64-unknown-linux-gnu` | [glibc](#references) ≥ 2.28 (Debian 10+, Ubuntu 20.04+, RHEL 8+) | +| `linux-arm64` | `aarch64-unknown-linux-gnu` | glibc ≥ 2.28 | +| `linuxmusl-x64` | `x86_64-unknown-linux-musl` | dynamically links [musl libc](#references) (Alpine Linux) | +| `linuxmusl-arm64` | `aarch64-unknown-linux-musl` | dynamically links musl libc | +| `darwin-x64` | `x86_64-apple-darwin` | macOS, Intel | +| `darwin-arm64` | `aarch64-apple-darwin` | macOS, Apple Silicon | +| `win32-x64` | `x86_64-pc-windows-msvc` | [MSVC CRT](#references) statically linked (`+crt-static`) | +| `win32-arm64` | `aarch64-pc-windows-msvc` | MSVC CRT statically linked (`+crt-static`) | The GNU/Linux glibc minimum of 2.28 is enforced at build time via a Microsoft/vscode-linux-build-agent sysroot and verified post-build by `script/linux/verify-glibc-requirements.sh`. The musl binaries are **not** fully statically linked; they dynamically link musl libc (`-C target-feature=-crt-static` is explicitly set at build time). The **common case** (Windows × 2 + macOS × 2 + GNU/Linux × 2) requires **6 binaries**. Supporting Alpine Linux adds 2 more musl binaries for a total of **8**. -### Platform selection is 100% deterministic +### Platform selection -The correct binary can be selected at runtime without any heuristics, using only standard Java and OS APIs: +The loader selects a classifier at runtime using standard Java and OS APIs: -1. **OS**: `System.getProperty("os.name")` — distinguishes Windows, macOS, and Linux unambiguously. -2. **Architecture**: `System.getProperty("os.arch")` — `"amd64"` and `"x86_64"` both map to `x64`; `"aarch64"` and `"arm64"` both map to `arm64`. -3. **Linux libc variant**: Read the first 2 KB of `/proc/self/exe` and parse the [ELF](#references) PT_INTERP segment (the dynamic linker path). If the interpreter path contains `/ld-musl-` → musl; if it contains `/ld-linux-` → glibc. This requires no subprocess, no PATH lookup, and works inside containers. This is the same approach used by the `detect-libc` npm package (its primary, most reliable detection method). +1. **OS**: `System.getProperty("os.name")` distinguishes Windows, macOS, and Linux. +1. **Architecture**: `System.getProperty("os.arch")` maps `"amd64"`, `"x86_64"`, and `"x64"` to `x64`, and maps `"aarch64"` and `"arm64"` to `arm64`. +1. **Linux libc variant**: The loader reads the first 2 KB of `/proc/self/exe` and parses the [ELF](#references) PT_INTERP segment. An interpreter containing `/ld-musl-` selects musl, while `/ld-linux-` selects glibc. + +If the Linux executable cannot be read or its interpreter is not recognized, the implementation falls back to the GNU/Linux classifier for the detected architecture. Unsupported operating systems and architectures fail with `IllegalStateException`. ### Size baseline Measured from `github/copilot-agent-runtime` release `cli-1.0.69-2` (2026-07-06): -| Platform | `runtime.node` (uncompressed) | Compressed (~40% deflate) | -|----------|------------------------------|--------------------------| -| `linux-x64` | 64.7 MB | ~25.9 MB | -| `linux-arm64` | 55.5 MB | ~22.2 MB | -| `linuxmusl-x64` | 64.4 MB | ~25.8 MB | -| `linuxmusl-arm64` | 55.3 MB | ~22.1 MB | -| `darwin-x64` | 57.3 MB | ~22.9 MB | -| `darwin-arm64` | 48.1 MB | ~19.2 MB | -| `win32-x64` | 55.9 MB | ~22.4 MB | -| `win32-arm64` | 48.4 MB | ~19.4 MB | +| Platform | `runtime.node` (uncompressed) | Compressed (~40% deflate) | +| ----------------- | ----------------------------- | ------------------------- | +| `linux-x64` | 64.7 MB | ~25.9 MB | +| `linux-arm64` | 55.5 MB | ~22.2 MB | +| `linuxmusl-x64` | 64.4 MB | ~25.8 MB | +| `linuxmusl-arm64` | 55.3 MB | ~22.1 MB | +| `darwin-x64` | 57.3 MB | ~22.9 MB | +| `darwin-arm64` | 48.1 MB | ~19.2 MB | +| `win32-x64` | 55.9 MB | ~22.4 MB | +| `win32-arm64` | 48.4 MB | ~19.4 MB | + +The published Java SDK JAR (`copilot-sdk-java-1.0.6-preview.1.jar`) is currently **1.53 MB**. A future runtime-only monolithic JAR containing all 6 common-case native binaries would be approximately **132 MB** compressed; all 8 including musl would be approximately **180 MB** compressed. -The published Java SDK JAR (`copilot-sdk-java-1.0.6-preview.1.jar`) is currently **1.53 MB**. A monolithic JAR containing all 6 common-case native binaries would be approximately **132 MB** compressed; all 8 including musl would be approximately **180 MB** compressed. +These runtime-only estimates do not describe the current migration artifact. The current development `linux-x64` classifier JAR also contains the version-matched CLI executable and is approximately **152 MB compressed**. Its staged contents are approximately **133 MB** for `runtime.node` and **170 MB** for `copilot` before JAR compression. All native dependencies within the runtime (`rustls`/`aws-lc-rs` for TLS, `rusqlite` with `bundled` feature for SQLite, `zlib-rs` for compression) are statically compiled into the binary. There are no dependencies on system OpenSSL, libgit2, or libz. -## Considered Options +## Considered options -### Option 1: Monolithic JAR — all platform binaries in one artifact +### Option 1: Monolithic JAR with all platform binaries -All 6 (or 8) `runtime.node` binaries are bundled inside the single `copilot-sdk-java` artifact. At runtime the SDK extracts and loads the one matching the current platform; the remaining 5–7 are carried silently. +All 6 (or 8) platform artifact sets are bundled inside a single monolithic artifact. At runtime the SDK extracts and loads the one matching the current platform; the remaining 5–7 are carried silently. **Advantages:** + - Single `` in `pom.xml`; zero extra configuration for users. - Familiar pattern: [ONNX Runtime](#references) (`onnxruntime-1.21.0.jar`, **130 MB**, all platforms) demonstrates this is an accepted norm in the Java ML ecosystem. **Drawbacks:** + - Every user downloads every platform regardless of their target. A developer on Apple Silicon downloads 105+ MB of Linux and Windows binaries they will never use. - Build tooling (thin Docker layers, incremental CI caches, artifact registries) penalises large JARs. A single 132–180 MB JAR invalidates the entire cache whenever any platform's binary changes. - Maven's dependency resolution has no mechanism to supply platform-appropriate variants automatically; platform selection must happen entirely at runtime inside the JAR. - Conflicts with the principle that Maven artifacts should be reproducible and minimal. -### Option 2: Per-platform classifier JARs ([DJL](#references) style) +### Option 2: Per-platform classifier JARs A small, pure-Java coordination artifact (`copilot-sdk-java`, ~1.5 MB) is published alongside separate per-platform native artifacts differentiated by Maven classifier: @@ -94,7 +118,7 @@ com.github:copilot-sdk-java-runtime:VERSION:win32-x64 com.github:copilot-sdk-java-runtime:VERSION:win32-arm64 ``` -Each classifier JAR contains only the `runtime.node` binary for that platform (~19–26 MB compressed) plus a small `.properties` metadata file. The coordination artifact selects and loads the matching native at startup. +Each classifier JAR contains `runtime.node`, `platform.properties`, and, during the active Rust migration, the version-matched `copilot` or `copilot.exe` embedded-host executable. The coordination artifact selects and loads the matching native when the user selects the in-process connection. This is the same pattern used by DJL's PyTorch native artifacts (`pytorch-native-cpu-2.5.1-linux-x86_64.jar`, `pytorch-native-cpu-2.5.1-osx-aarch64.jar`, etc.), Netty's `netty-tcnative-boringssl-static` per-platform JARs, and others. @@ -105,39 +129,43 @@ Build tools can be configured to resolve the correct classifier automatically: - **Uber-jar builds**: include all classifiers; the coordination artifact picks the right one at runtime. **Advantages:** -- Default download is the tiny coordination artifact (~1.5 MB) plus one platform JAR (~20–26 MB compressed) — approximately **22–28 MB total** vs. 132–180 MB for a monolithic JAR. + +* The long-term runtime-only download is the coordination artifact plus one platform JAR instead of every platform binary. - Each platform JAR changes independently; CI caches and Docker layers for unchanged platforms are preserved across releases. - Users building for a single known platform (most production deployments) pay exactly the cost of that platform. - Follows well-established Maven ecosystem conventions; standard tooling ([os-maven-plugin](#references), Gradle variant resolution) handles classifier selection. - Aligns with DJL's proven distribution strategy for large native ML runtimes. **Drawbacks:** + - Requires publishing 6–8 additional Maven artifacts per release. - Users building portable über-JARs must explicitly include all classifiers they wish to support. - Slightly more complex `pom.xml` / `build.gradle` for users who need cross-platform packaging. -### Option 3: Download-on-demand (DJL thin placeholder style) +### Option 3: Download on demand The SDK ships a minimal placeholder that detects the current platform at runtime and downloads the correct `runtime.node` binary from a distribution endpoint (GitHub Releases or a CDN) on first use, caching it locally (e.g., `~/.copilot/runtime-cache/`). **Advantages:** + - Zero native binary content in any published Maven artifact; total download at `mvn install` is negligible. - Identical user experience to the current "externally provided runtime" model during the download, which most CLI users already accept. **Drawbacks:** + - Requires internet access on first run. Offline environments (air-gapped enterprise, CI without outbound HTTP) break silently or require manual pre-seeding. - Introduces a network dependency into an otherwise pure library artifact, which violates Maven Central's expectations for reproducible builds. - Adds an operational concern: distribution endpoint availability, CDN costs, URL stability across versions. - Makes JVM startup non-deterministic in latency (first run downloads 20–26 MB). - Cannot be pre-warmed by dependency management tooling; no `mvn dependency:resolve` analogue works for a runtime download. -## Decision Outcome +## Decision outcome -**Chosen: Option 2 — per-platform classifier JARs and Option 1 - monolithic jar. Use `maven-assembly-plugin` to allow the creation of the monolithic jar.** +**Chosen: Option 2, per-platform classifier JARs, with Option 1 available through a consumer-built monolithic JAR.** Consumers can use `maven-assembly-plugin` to merge the platform classifiers they need. ### Rationale -1. **User download cost matches actual need.** Most users run on one OS and architecture. Option 2 makes their download approximately 22–28 MB (coordination JAR + one platform JAR), versus 132–180 MB for Option 1 and an unbounded deferred network cost for Option 3. +1. **User download cost matches actual need.** Most users run on one OS and architecture. Option 2 avoids downloading every platform artifact. During the active migration, each classifier also carries the embedded-host executable and is larger than the runtime-only target. 2. **Proven ecosystem pattern.** DJL, Netty, and others have established the per-classifier pattern as the correct Maven idiom for large native binaries. Build tooling already knows how to handle it; users and framework integrations (Spring Boot, Quarkus, Micronaut) are familiar with it. @@ -145,15 +173,46 @@ The SDK ships a minimal placeholder that detects the current platform at runtime 4. **No operational dependencies.** Unlike Option 3, no external download service is required at runtime. The artifact is self-contained once resolved by Maven/Gradle. -5. **Size per platform is acceptable.** At ~20–26 MB compressed per platform, each classifier JAR is well within the range of routinely used native JARs in the Java ecosystem (DJL PyTorch osx-aarch64: 37 MB; ONNX Runtime per platform: ~20–30 MB before bundling). +5. **The distribution model remains valid as artifact size changes.** The current transitional classifier is large because it contains both the runtime and CLI. The classifier model still prevents users from downloading artifacts for unrelated platforms, and its size decreases when the embedded-host executable is no longer required. 6. **Option 3 remains composable.** A download-on-demand fallback can be layered on top of Option 2 for users who prefer it without changing the primary distribution model. The coordination artifact can attempt classpath lookup first, then fall back to a cached download if no matching classifier JAR is present. -7. See section [How can we do Option 2 and Option 1](#how-can-we-do-option-2-and-option-1) for more details. +7. See [How to support classifier and monolithic JARs](#how-to-support-classifier-and-monolithic-jars) for more details. + +### Transport selection and failure behavior + +Adding a classifier JAR does not change the client's connection automatically. Users opt in with: + +```java +CopilotClientOptions options = new CopilotClientOptions() + .setConnection(RuntimeConnection.forInProcess()); +``` + +The `COPILOT_SDK_DEFAULT_CONNECTION=inprocess` environment variable also selects the in-process connection when no explicit or legacy subprocess options override it. + +The selected connection is strict: + +* If the user selects in-process and native resolution or startup fails, `CopilotClient.start()` fails. +* The SDK does not silently retry with stdio or TCP. +* If the user does not select in-process, classifier JARs are ignored and the existing stdio, TCP, or URI behavior remains unchanged. + +### Runtime resolution order + +When the in-process connection is selected, the Java loader resolves `runtime.node` in this order: + +1. `COPILOT_CLI_PATH`: accept either a flat sibling `runtime.node` or the npm `prebuilds//runtime.node` layout. +1. Classpath resource: extract `native//runtime.node` and the bundled CLI from the classifier or monolithic JAR into a cache keyed by SDK version, native package version, and classifier. +1. PATH compatibility fallback: find `copilot` on `PATH` and accept a flat sibling `runtime.node`. + +If none succeeds, startup fails. The PATH fallback does not claim to support every npm or Homebrew installation layout. + +### Current platform scope + +The platform detector recognizes the 8 classifiers listed in this ADR. The current Maven packaging and documented experimental support publish only `linux-x64`. Additional classifier artifacts remain follow-up work. ## Binding technology: JNA over Panama FFM -A secondary decision within the scope of this ADR is *how* the coordination artifact calls the C ABI entry points once the correct `runtime.node` binary has been loaded. Two candidates were considered: [JNA](#references) and the [Foreign Function & Memory API](#references) (FFM, the product of [Project Panama](#references), final since Java 22 via [JEP 454](#references)). +A secondary decision within the scope of this ADR is _how_ the coordination artifact calls the C ABI entry points once the correct `runtime.node` binary has been loaded. Two candidates were considered: [JNA](#references) and the [Foreign Function & Memory API](#references) (FFM, the product of [Project Panama](#references), final since Java 22 via [JEP 454](#references)). **Chosen: JNA.** FFM was considered and deliberately deferred, for the following reasons: @@ -161,7 +220,7 @@ A secondary decision within the scope of this ADR is *how* the coordination arti 2. **Consumer-side configuration burden.** FFM downcalls and upcalls are restricted operations under the JDK's integrity-by-default direction ([JEP 472](#references)). An FFM-based SDK would require every consumer to grant native access explicitly — `--enable-native-access=` (or `ALL-UNNAMED` for classpath applications) on the launcher, or an `Enable-Native-Access` manifest attribute. JNA requires no consumer-side configuration today. For an SDK, this flag becomes every downstream application's problem and a predictable source of support issues. (JNA is on the same enforcement trajectory eventually, as it uses JNI internally; this consideration buys time, not immunity.) -3. **No realizable performance benefit.** FFM's principal advantage over JNA is the elimination of per-call reflective marshalling overhead. The C ABI surface here is a fixed set of ~12 entry points carrying JSON-RPC strings; JSON serialization/deserialization cost dominates the call path, and call frequency is bounded by agent-interaction rates rather than tight loops. The latency difference between JNA and FFM is expected to be unmeasurable in end-to-end SDK usage. This calculus would change only if the transport moved to a high-frequency or shared-memory framing model. +3. **No realizable performance benefit.** FFM's principal advantage over JNA is the elimination of per-call reflective marshalling overhead. The C ABI surface here is a fixed set of 5 entry points carrying JSON-RPC bytes; JSON serialization and deserialization cost dominates the call path, and call frequency is bounded by agent-interaction rates rather than tight loops. The latency difference between JNA and FFM is expected to be unmeasurable in end-to-end SDK usage. This calculus would change only if the transport moved to a high-frequency or shared-memory framing model. 4. **Upcall lifetime complexity.** The transport is bidirectional: the runtime delivers JSON-RPC responses and server-initiated requests back into Java from native threads. JNA's `Callback` mechanism handles foreign-thread attachment with well-established semantics. FFM upcall stubs require explicit `Arena` lifetime management, where a stub whose arena is closed while the Rust side still holds the function pointer results in a JVM crash. This shifts lifetime reasoning that JNA encapsulates onto the binding layer. @@ -171,110 +230,74 @@ A secondary decision within the scope of this ADR is *how* the coordination arti ### Preserving the FFM migration path -FFM is regarded as the likely eventual binding technology: the JEP 472 endgame applies enforcement pressure to JNA as well, and a ~12-function stable C ABI makes a future migration inexpensive. To keep that path open at low cost: +FFM is regarded as the likely eventual binding technology: the JEP 472 endgame applies enforcement pressure to JNA as well, and a 5-function stable C ABI makes a future migration inexpensive. To keep that path open at low cost: - The binding layer is abstracted behind a small internal interface (native load + downcall + upcall registration), so that an FFM implementation can be introduced later — for example, as a multi-release JAR selecting FFM on Java 22+ — without changes to the transport or API layers. - The decision should be revisited when (a) the SDK's minimum Java baseline moves past 17, or (b) JDK releases begin enforcing `--illegal-native-access=deny` by default, whichever comes first. -## How can we do Option 2 and Option 1 +## How to support classifier and monolithic JARs -## How it works: classpath resource convention + platform detection +### Classpath resource convention and platform detection -### 1. Each classifier JAR uses a well-known resource path +#### Each classifier JAR uses a well-known resource path -Each per-platform JAR (`copilot-sdk-java-runtime:VERSION:darwin-arm64`, etc.) places its binary under a deterministic path inside the JAR: +Each per-platform JAR places its artifacts under a deterministic path: ``` native/darwin-arm64/runtime.node native/darwin-arm64/platform.properties +native/darwin-arm64/copilot ``` -When `maven-assembly-plugin` creates the uber-jar, it unpacks all dependencies and merges them. The resulting uber-jar contains: +Windows classifiers use `copilot.exe`. The CLI entrypoint remains in the classifier while the runtime requires the transitional embedded host. + +When `maven-assembly-plugin` creates the uber-JAR, it unpacks all dependencies and merges them. The resulting uber-JAR contains the selected platforms: ``` com/github/copilot/sdk/... (Java classes) native/linux-x64/runtime.node +native/linux-x64/copilot native/linux-arm64/runtime.node +native/linux-arm64/copilot native/linuxmusl-x64/runtime.node +native/linuxmusl-x64/copilot native/linuxmusl-arm64/runtime.node +native/linuxmusl-arm64/copilot native/darwin-x64/runtime.node +native/darwin-x64/copilot native/darwin-arm64/runtime.node +native/darwin-arm64/copilot native/win32-x64/runtime.node +native/win32-x64/copilot.exe native/win32-arm64/runtime.node +native/win32-arm64/copilot.exe ``` -### 2. The coordination artifact selects at runtime via `getResourceAsStream` +#### The coordination artifact selects at runtime through the classloader -```java -public class NativeRuntimeLoader { - - public Path loadRuntime() { - String classifier = detectPlatformClassifier(); - String resourcePath = "native/" + classifier + "/runtime.node"; - - try (InputStream in = getClass().getClassLoader() - .getResourceAsStream(resourcePath)) { - if (in == null) { - throw new UnsupportedOperationException( - "No native runtime for platform: " + classifier); - } - Path cached = getCachePath(classifier); - if (!Files.exists(cached)) { - Files.createDirectories(cached.getParent()); - Files.copy(in, cached); - // Make executable on Unix - cached.toFile().setExecutable(true); - } - return cached; - } - } - - private String detectPlatformClassifier() { - String os = normalizeOs(System.getProperty("os.name")); - String arch = normalizeArch(System.getProperty("os.arch")); - String libc = "linux".equals(os) ? detectLinuxLibc() : ""; - - // Produces: "linux-x64", "linuxmusl-arm64", "darwin-arm64", "win32-x64", etc. - return (libc.isEmpty() ? os : os + libc) + "-" + arch; - } - - private String detectLinuxLibc() { - // Read ELF PT_INTERP from /proc/self/exe - // If interpreter contains "/ld-musl-" → "musl" - // Otherwise → "" (glibc is the default/unmarked case for "linux-") - // ... - } - - private Path getCachePath(String classifier) { - String version = getClass().getPackage().getImplementationVersion(); - return Path.of(System.getProperty("user.home"), - ".copilot", "runtime-cache", version, classifier, "runtime.node"); - } -} -``` +`NativeRuntimeLoader` detects the current classifier and requests `native//runtime.node`, `native//platform.properties`, and `native//copilot` from the classloader. It uses the native package version from `platform.properties` as part of the cache identity, writes each executable artifact to a unique sibling temporary file, forces the file contents to storage, and atomically publishes the completed file into `~/.copilot/runtime-cache////`. + +On non-Windows platforms, the loader makes the temporary CLI executable and verifies its executable status before atomic publication. A nonempty but non-executable cached CLI is repaired instead of being accepted as valid. -### 3. JNA loads from the extracted path +#### JNA loads from the extracted path Once extracted to a known filesystem path, JNA loads it directly: ```java -NativeLibrary lib = NativeLibrary.getInstance(extractedPath.toString()); -// Or via a mapped interface: -CopilotRuntime runtime = Native.load(extractedPath.toString(), CopilotRuntime.class); +CopilotRuntimeLibrary runtime = + Native.load(extractedPath.toString(), CopilotRuntimeLibrary.class); ``` -### Key insight: the same code works in both modes +#### The same code works in both modes -The beauty is that `getResourceAsStream("native/darwin-arm64/runtime.node")` works identically whether: +Classloader resource lookup works identically whether: -- The native lives in a **separate classifier JAR** on the classpath (normal dev dependency), OR -- It's been **merged into an uber-jar** by `maven-assembly-plugin` +* The native artifacts live in a separate classifier JAR on the classpath. +* The artifacts have been merged into an uber-JAR by `maven-assembly-plugin`. -The classloader doesn't care which JAR file the resource came from — it searches the entire classpath. This means **zero code changes** between the two consumption models. +The classloader searches the entire classpath, so the Java loading code does not change between the two consumption models. ---- - -## Assembly plugin configuration (consumer-side) +### Consumer-side assembly plugin configuration A consumer building a portable uber-jar would configure: @@ -298,7 +321,7 @@ With all classifier JARs declared as dependencies: copilot-sdk-java ${copilot.version} - + com.github copilot-sdk-java-runtime @@ -311,66 +334,63 @@ With all classifier JARs declared as dependencies: ${copilot.version} darwin-arm64 - + ``` ---- - -## Why this works cleanly +### Why this works cleanly -| Concern | How it's handled | -|---------|-----------------| -| No resource path collisions | Each platform has its own subdirectory (`native//`) | -| Extraction only happens once | Cached to `~/.copilot/runtime-cache///` | -| Works without uber-jar too | Same `getResourceAsStream` call — classloader finds it in the separate JAR | -| Subset selection | Consumer declares only the classifiers they need; missing platforms get a clear error at runtime | -| JNA loading | `NativeLibrary.getInstance(path)` loads from an absolute filesystem path after extraction — no JNA platform-detection magic needed | - -The pattern is identical to how DJL's `LibUtils.loadLibrary()` works — detect platform, construct resource path, extract if needed, load via absolute path. +| Concern | How it's handled | +| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| No resource path collisions | Each platform has its own subdirectory (`native//`) | +| Extraction only happens once | Cached to `~/.copilot/runtime-cache////` | +| Works without uber-JAR too | The classloader finds the same resource in a separate classifier JAR | +| Subset selection | Consumer declares only the classifiers they need; missing platforms get a clear error at runtime | +| JNA loading | `Native.load(path, interface)` loads from an absolute filesystem path after extraction | +The pattern follows DJL's `LibUtils.loadLibrary()` approach: detect the platform, construct the resource path, extract when needed, and load from an absolute path. ## Consequences -- A new Maven module (`copilot-sdk-java-runtime` or similar) is introduced to hold the per-platform native JARs. The existing `copilot-sdk-java` coordination artifact depends on it. -- The coordination artifact gains a platform detection and native loading component that: +* The `copilot-sdk-java-runtime` Maven module holds the per-platform classifier JARs. Users add the classifier for each platform they intend to run. +* Users selecting in-process mode also add JNA. The coordination artifact does not force native dependencies on users who keep the default subprocess connection. +* The coordination artifact includes platform detection and native loading code that: 1. Detects OS, architecture, and Linux libc variant deterministically as described above. 2. Locates the matching `runtime.node` binary on the classpath (via `getResourceAsStream` from the classifier JAR). - 3. Extracts the binary to a temporary or cached location (e.g., `~/.copilot/runtime-cache/`) if not already present. + 3. Extracts `runtime.node` and the transitional CLI entrypoint into `~/.copilot/runtime-cache/` if valid cached files are not already present. 4. Loads it via [JNA](#references) using the C ABI entry points, per the [binding technology decision](#binding-technology-jna-over-panama-ffm) above. The JNA-specific code is confined behind an internal binding interface to preserve a future FFM migration path. -- The release pipeline for `github/copilot-agent-runtime` must produce the per-platform `runtime.node` binaries as inputs to the Java SDK publish workflow. The per-platform `pkg-tarballs-` artifacts from the `publish-cli.yml` workflow are the authoritative source. -- Each release of `copilot-sdk-java` publishes 6 (or 8) classifier JARs to Maven Central alongside the coordination JAR. -- The version of the bundled `runtime.node` is recorded in the coordination JAR's manifest and queryable at runtime, enabling diagnostics and mismatch detection. -- `cli-native.node` is not bundled. It provides only terminal UI features (ICU4X text segmentation, Win32 APIs, OS desktop notifications) that are irrelevant to the Java SDK's programmatic API surface. +* The Java build fetches the pinned `@github/copilot-` npm package, verifies its SHA-512 integrity from `nodejs/package-lock.json`, and packages the version-matched runtime and CLI files. +* The current release work publishes the `linux-x64` classifier. The planned classifier set expands to the other detected platforms. +* `cli-native.node` is not bundled. It provides terminal UI features that are irrelevant to the Java SDK's programmatic API surface. ## Related work items -- https://github.com/github/copilot-sdk/issues/1917 — Epic: Embed Rust-based Copilot CLI Runtime and cease requiring Node.js -- https://devdiv.visualstudio.com/DevDiv/_workitems/edit/3028097 -- https://github.com/github/copilot-sdk/pull/1901 dotnet: in-process FFI runtime hosting (InProcess transport) -- https://github.com/github/copilot-sdk/pull/1915 Add in-process FFI transport for Rust and TypeScript SDKs +* https://github.com/github/copilot-sdk/issues/1917: Epic to embed the Rust-based Copilot CLI runtime +* https://devdiv.visualstudio.com/DevDiv/_workitems/edit/3028097 +* https://github.com/github/copilot-sdk/pull/1901: .NET in-process FFI runtime hosting +* https://github.com/github/copilot-sdk/pull/1915: In-process FFI transport for Rust and TypeScript SDKs ### References -| Term | Definition | Link | -|------|------------|------| -| **FFI** (Foreign Function Interface) | A mechanism by which code written in one language can call functions defined in another. In this ADR, Java calls into the Rust runtime shared library via JNA's FFI layer. | https://en.wikipedia.org/wiki/Foreign_function_interface | -| **JNA** (Java Native Access) | A Java library that provides easy access to native shared libraries without requiring the JNI boilerplate. Used here to call the `extern "C"` C ABI entry points exported by `runtime.node`. | https://github.com/java-native-access/jna | -| **napi-rs** | A Rust framework for building native Node.js addons using the Node-API (napi) stable ABI. Produces the `.node` file and generates TypeScript type declarations automatically. | https://napi.rs/ | -| **cdylib** | A Rust `crate-type` that produces a C-compatible dynamic shared library (`.so` / `.dylib` / `.dll`). Distinct from `dylib` (Rust-to-Rust only) and `staticlib`. | https://doc.rust-lang.org/reference/linkage.html | -| **napi (Node-API)** | A stable C ABI provided by Node.js for building native addons that remain binary-compatible across Node.js versions. `napi-rs` generates Rust code against this interface. | https://nodejs.org/api/n-api.html | -| **C ABI** (Application Binary Interface) | The low-level contract between a compiled binary and its callers: calling conventions, data type layouts, symbol naming. An `extern "C"` ABI uses C's conventions, making a library callable from any language that speaks C FFI. | https://en.wikipedia.org/wiki/Application_binary_interface | -| **ELF PT_INTERP** | A segment in an [ELF](https://man7.org/linux/man-pages/man5/elf.5.html) binary (the Linux/Unix executable format) that records the path of the dynamic linker/interpreter. On glibc systems this path is `/lib64/ld-linux-x86-64.so.2`; on musl systems it is `/lib/ld-musl-x86_64.so.1`. Inspecting it is the most reliable way to detect glibc vs. musl at runtime without executing a subprocess. | https://man7.org/linux/man-pages/man5/elf.5.html | -| **glibc** (GNU C Library) | The standard C runtime library on most mainstream Linux distributions (Debian, Ubuntu, RHEL, Fedora, SLES). Binaries linked against glibc require the same version or newer to be present at runtime. The `runtime.node` glibc build requires glibc ≥ 2.28. | https://www.gnu.org/software/libc/ | -| **musl libc** | An alternative C standard library optimised for static linking and used as the default libc on Alpine Linux. Not binary-compatible with glibc; a separate `runtime.node` build is required. | https://musl.libc.org/ | -| **MSVC CRT** (Microsoft Visual C++ Runtime) | The C runtime library shipped with Visual Studio. When compiled with `+crt-static` (as `runtime.node` is on Windows), it is statically linked into the binary and the end-user does not need to install the Visual C++ Redistributable. | https://learn.microsoft.com/en-us/cpp/c-runtime-library/c-run-time-library-reference | -| **Project Panama** | The OpenJDK project that produced the Foreign Function & Memory API as the modern, supported replacement for JNI-based native interop. | https://openjdk.org/projects/panama/ | -| **FFM** (Foreign Function & Memory API) | The `java.lang.foreign` API for calling native functions and managing native memory from Java, finalized in Java 22. Considered and deferred as the binding technology for this SDK; see [Binding technology](#binding-technology-jna-over-panama-ffm). | https://docs.oracle.com/en/java/javase/22/core/foreign-function-and-memory-api.html | -| **JEP 454** | The JDK Enhancement Proposal that finalized the FFM API in Java 22. | https://openjdk.org/jeps/454 | -| **JEP 472** | "Prepare to Restrict the Use of JNI" — part of the JDK's integrity-by-default direction under which native access (via JNI or FFM) requires explicit consumer opt-in (`--enable-native-access`). Drives both the FFM configuration-burden concern and the expectation that JNA itself will eventually require the same opt-in. | https://openjdk.org/jeps/472 | -| **DJL** (Deep Java Library) | Amazon's open-source Java framework for ML inference, used here as a reference for the per-platform classifier JAR distribution pattern. Its PyTorch native artifacts (`pytorch-native-cpu-*-.jar`) are the direct model for the proposed `copilot-sdk-java-runtime:VERSION:` artifacts. | https://djl.ai/ | -| **os-maven-plugin** | A Maven extension that detects the current OS and architecture and exposes them as properties (e.g., `${os.detected.classifier}`) so that `` values can be resolved at build time rather than hardcoded. | https://github.com/trustin/os-maven-plugin | -| **ONNX Runtime** | Microsoft's cross-platform ML inference runtime, used in this ADR as the size comparable for a monolithic all-platform JAR (~130 MB, Option 1). | https://onnxruntime.ai/ | +| Term | Definition | Link | +| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | +| **FFI** (Foreign Function Interface) | A mechanism by which code written in one language can call functions defined in another. In this ADR, Java calls into the Rust runtime shared library via JNA's FFI layer. | https://en.wikipedia.org/wiki/Foreign_function_interface | +| **JNA** (Java Native Access) | A Java library that provides easy access to native shared libraries without requiring the JNI boilerplate. Used here to call the `extern "C"` C ABI entry points exported by `runtime.node`. | https://github.com/java-native-access/jna | +| **napi-rs** | A Rust framework for building native Node.js addons using the Node-API (napi) stable ABI. Produces the `.node` file and generates TypeScript type declarations automatically. | https://napi.rs/ | +| **cdylib** | A Rust `crate-type` that produces a C-compatible dynamic shared library (`.so` / `.dylib` / `.dll`). Distinct from `dylib` (Rust-to-Rust only) and `staticlib`. | https://doc.rust-lang.org/reference/linkage.html | +| **napi (Node-API)** | A stable C ABI provided by Node.js for building native addons that remain binary-compatible across Node.js versions. `napi-rs` generates Rust code against this interface. | https://nodejs.org/api/n-api.html | +| **C ABI** (Application Binary Interface) | The low-level contract between a compiled binary and its callers: calling conventions, data type layouts, symbol naming. An `extern "C"` ABI uses C's conventions, making a library callable from any language that speaks C FFI. | https://en.wikipedia.org/wiki/Application_binary_interface | +| **ELF PT_INTERP** | A segment in an [ELF](https://man7.org/linux/man-pages/man5/elf.5.html) binary (the Linux/Unix executable format) that records the path of the dynamic linker/interpreter. On glibc systems this path is `/lib64/ld-linux-x86-64.so.2`; on musl systems it is `/lib/ld-musl-x86_64.so.1`. Inspecting it is the most reliable way to detect glibc vs. musl at runtime without executing a subprocess. | https://man7.org/linux/man-pages/man5/elf.5.html | +| **glibc** (GNU C Library) | The standard C runtime library on most mainstream Linux distributions (Debian, Ubuntu, RHEL, Fedora, SLES). Binaries linked against glibc require the same version or newer to be present at runtime. The `runtime.node` glibc build requires glibc ≥ 2.28. | https://www.gnu.org/software/libc/ | +| **musl libc** | An alternative C standard library optimised for static linking and used as the default libc on Alpine Linux. Not binary-compatible with glibc; a separate `runtime.node` build is required. | https://musl.libc.org/ | +| **MSVC CRT** (Microsoft Visual C++ Runtime) | The C runtime library shipped with Visual Studio. When compiled with `+crt-static` (as `runtime.node` is on Windows), it is statically linked into the binary and the end-user does not need to install the Visual C++ Redistributable. | https://learn.microsoft.com/en-us/cpp/c-runtime-library/c-run-time-library-reference | +| **Project Panama** | The OpenJDK project that produced the Foreign Function & Memory API as the modern, supported replacement for JNI-based native interop. | https://openjdk.org/projects/panama/ | +| **FFM** (Foreign Function & Memory API) | The `java.lang.foreign` API for calling native functions and managing native memory from Java, finalized in Java 22. Considered and deferred as the binding technology for this SDK; see [Binding technology](#binding-technology-jna-over-panama-ffm). | https://docs.oracle.com/en/java/javase/22/core/foreign-function-and-memory-api.html | +| **JEP 454** | The JDK Enhancement Proposal that finalized the FFM API in Java 22. | https://openjdk.org/jeps/454 | +| **JEP 472** | "Prepare to Restrict the Use of JNI" — part of the JDK's integrity-by-default direction under which native access (via JNI or FFM) requires explicit consumer opt-in (`--enable-native-access`). Drives both the FFM configuration-burden concern and the expectation that JNA itself will eventually require the same opt-in. | https://openjdk.org/jeps/472 | +| **DJL** (Deep Java Library) | Amazon's open-source Java framework for ML inference, used here as a reference for the per-platform classifier JAR distribution pattern. Its PyTorch native artifacts (`pytorch-native-cpu-*-.jar`) are the direct model for the proposed `copilot-sdk-java-runtime:VERSION:` artifacts. | https://djl.ai/ | +| **os-maven-plugin** | A Maven extension that detects the current OS and architecture and exposes them as properties (e.g., `${os.detected.classifier}`) so that `` values can be resolved at build time rather than hardcoded. | https://github.com/trustin/os-maven-plugin | +| **ONNX Runtime** | Microsoft's cross-platform ML inference runtime, used in this ADR as the size comparable for a monolithic all-platform JAR (~130 MB, Option 1). | https://onnxruntime.ai/ | Additional source references: @@ -381,4 +401,3 @@ Additional source references: - `github/copilot-agent-runtime` build target definitions: `script/build-runtime.ts` - `github/copilot-agent-runtime` glibc sysroot and verification: `script/linux/install-sysroot.cjs`, `script/linux/verify-glibc-requirements.sh` - ONNX Runtime Java on Maven Central (size comparable): https://repo1.maven.org/maven2/com/microsoft/onnxruntime/onnxruntime/1.21.0/ - diff --git a/java/pom.xml b/java/pom.xml index 42dcb2a08..cb6662458 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -6,12 +6,12 @@ 4.0.0 com.github - copilot-sdk-java - 1.0.9-preview.0-SNAPSHOT - jar + copilot-sdk-java-parent + 1.0.12-preview.2-SNAPSHOT + pom - GitHub Copilot SDK :: Java - Official SDK for programmatic control of GitHub Copilot CLI + GitHub Copilot SDK :: Java :: Parent + Parent POM for the GitHub Copilot Java SDK multi-module reactor https://github.com/github/copilot-sdk @@ -36,48 +36,25 @@ HEAD - - - central - https://central.sonatype.com/repository/maven-snapshots/ - - + + sdk + copilot-native + + + 17 UTF-8 ${project.basedir}/.. - ${copilot.sdk.root}/test - - ${copilot.sdk.root}/nodejs/node_modules/@github/copilot/npm-loader.js - - false - - ${skip.test.harness} - - - ^1.0.73 - + ^1.0.79 + + true - - - - com.fasterxml.jackson.core - jackson-databind - 2.22.0 - - - com.fasterxml.jackson.core - jackson-annotations - 2.22 - - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - 2.22.0 - - - - - com.github.spotbugs - spotbugs-annotations - 4.10.2 - provided - - - - - org.junit.jupiter - junit-jupiter - 5.14.4 - test - - - org.mockito - mockito-core - 5.23.0 - test - - - + + org.apache.maven.plugins + maven-clean-plugin + 3.5.0 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.15.0 + + + org.apache.maven.plugins + maven-jar-plugin + 3.5.1 + org.apache.maven.plugins maven-javadoc-plugin @@ -144,517 +96,111 @@ none + + org.apache.maven.plugins + maven-source-plugin + 3.4.0 + + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.6 + + + org.apache.maven.plugins + maven-failsafe-plugin + 3.5.6 + + + org.apache.maven.plugins + maven-antrun-plugin + 3.2.0 + + + org.apache.maven.plugins + maven-enforcer-plugin + 3.6.3 + + + org.apache.maven.plugins + maven-gpg-plugin + 3.2.8 + + + org.apache.maven.plugins + maven-release-plugin + 3.1.1 + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.6.0 + com.github.spotbugs spotbugs-maven-plugin - 4.10.2.0 + 4.10.3.0 + + + com.diffplug.spotless + spotless-maven-plugin + 2.46.1 + + + org.jacoco + jacoco-maven-plugin + 0.8.15 + + + org.codehaus.mojo + exec-maven-plugin + 3.6.3 + + + org.codehaus.mojo + build-helper-maven-plugin + 3.6.1 + + + org.sonatype.central + central-publishing-maven-plugin + 0.11.0 + + + org.codehaus.mojo + flatten-maven-plugin + 1.7.0 - config/spotbugs/spotbugs-exclude.xml + ossrh + + + flatten + process-resources + + flatten + + + + flatten-clean + clean + + clean + + + - - org.apache.maven.plugins - maven-clean-plugin - 3.5.0 - - - org.apache.maven.plugins - maven-compiler-plugin - 3.15.0 - - - -Acopilot.experimental.allowed=true - - none - - - - org.apache.maven.plugins - maven-jar-plugin - 3.5.0 - - - org.apache.maven.plugins - maven-antrun-plugin - 3.2.0 - - - print-test-jdk-banner - process-test-classes - - run - - - - - - - - - - org.codehaus.mojo - exec-maven-plugin - 3.6.3 - - - install-harness-dependencies - generate-test-resources - - exec - - - ${skip.test.harness} - npm - ${copilot.sdk.root}/test/harness - - ci - - - - - - install-nodejs-cli-dependencies - generate-test-resources - - exec - - - ${skip.cli.install} - npm - ${copilot.sdk.root}/nodejs - - ci - --ignore-scripts - - - - - - - - org.apache.maven.plugins - maven-failsafe-plugin - 3.5.6 - - - - integration-test - verify - - - - - - ${project.build.directory} - ${project.build.finalName} - ${project.build.testOutputDirectory} - - - - ${copilot.cli.path} - - - - - org.apache.maven.plugins - maven-surefire-plugin - 3.5.6 - - alphabetical - - ${testExecutionAgentArgs} ${surefire.jvm.args} - - 2 - - ${copilot.tests.dir} - ${copilot.sdk.root} - - - - ${copilot.cli.path} - - - - - - isolated-resume-tests - test - - test - - - isolated-resume - - ${project.build.directory}/surefire-reports-isolated - - - - - default-test - - isolated-resume - - - - - - - org.codehaus.mojo - build-helper-maven-plugin - 3.6.1 - - - add-generated-source - generate-sources - - add-source - - - - ${project.basedir}/src/generated/java - - - - - - - com.diffplug.spotless - spotless-maven-plugin - 2.46.1 - - - - src/generated/java/**/*.java - - - 4.33 - - - - - - true - 4 - - - - - - - org.jacoco - jacoco-maven-plugin - 0.8.15 - - - - wire-up-coverage-instrumentation - - prepare-agent - - - - ${project.build.directory}/jacoco-test-results/sdk-tests.exec - - testExecutionAgentArgs - - - com/github/copilot/** - - - com/github/copilot/E2ETestContext* - com/github/copilot/CapiProxy* - - - - - - build-coverage-report-from-tests - - report - - verify - - ${project.build.directory}/jacoco-test-results/sdk-tests.exec - ${project.reporting.outputDirectory}/jacoco-coverage - - - META-INF/versions/**/*.class - - - - - - - org.apache.maven.plugins - maven-checkstyle-plugin - 3.6.0 - - config/checkstyle/checkstyle.xml - true - true - false - - - - validate - validate - - check - - - - - - com.puppycrawl.tools - checkstyle - 10.26.1 - - - - - - org.sonatype.central - central-publishing-maven-plugin - 0.10.0 - true - - central - true - - - - org.apache.maven.plugins - maven-enforcer-plugin - 3.6.3 - - - enforce-jdk25 - - enforce - - - - - [25,) - JDK 25+ is required to build the Multi-Release JAR with the virtual-thread overlay. - - - - - - verify-multi-release-overlay - verify - - enforce - - - - - - ${project.build.outputDirectory}/META-INF/versions/25/com/github/copilot/InternalExecutorProvider.class - - Multi-Release JAR overlay missing: META-INF/versions/25/com/github/copilot/InternalExecutorProvider.class was not compiled. Ensure the build runs on JDK 25+. - - - - - + flatten-maven-plugin - - - jdk21+ - - [21,) - - - -XX:+EnableDynamicAgentLoading - - - - java25-multi-release - - [25,) - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - compile-java25 - compile - - compile - - - 25 - false - - ${project.basedir}/src/main/java25 - - true - - - - - - org.apache.maven.plugins - maven-jar-plugin - - - - true - - - - - - - org.apache.maven.plugins - maven-antrun-plugin - - - verify-java25-overlay - package - - run - - - - - - - - - -JDK 25 multi-release overlay class is missing from the packaged JAR. -Expected entry: META-INF/versions/25/com/github/copilot/InternalExecutorProvider.class -JAR: ${project.build.directory}/${project.build.finalName}.jar - -This usually means the 'java25-multi-release' Maven profile did not activate -(e.g. the build is running on a JDK older than 25) or maven-compiler-plugin -did not produce the multi-release output. Re-build on JDK 25+ and verify the -'compile-java25' execution ran during the 'compile' phase. - - - - - - - - - - - - skip-test-harness - - true - - - - - skip-cli-install-when-tests-skipped - - - skipTests - true - - - - true - - - - - skip-cli-install-when-maven-test-skip - - - maven.test.skip - true - - - - true - - - - - debug - - - - org.apache.maven.plugins - maven-surefire-plugin - - - ${project.basedir}/src/test/resources/logging-debug.properties - - - - - - release @@ -662,7 +208,6 @@ did not produce the multi-release output. Re-build on JDK 25+ and verify the org.apache.maven.plugins maven-source-plugin - 3.4.0 attach-sources @@ -687,7 +232,6 @@ did not produce the multi-release output. Re-build on JDK 25+ and verify the org.apache.maven.plugins maven-gpg-plugin - 3.2.8 sign-artifacts @@ -701,103 +245,5 @@ did not produce the multi-release output. Re-build on JDK 25+ and verify the - - - update-schemas-from-npm-artifact - - - - org.codehaus.mojo - exec-maven-plugin - 3.6.3 - - - update-copilot-schema-version - generate-sources - - exec - - - npm - ${project.basedir}/scripts/codegen - - install - @github/copilot@${copilot.schema.version} - - - - - - - org.apache.maven.plugins - maven-enforcer-plugin - 3.6.3 - - - require-schema-version - validate - - enforce - - - - - copilot.schema.version - You must specify -Dcopilot.schema.version=VERSION (e.g. 1.0.25) - - - - - - - - - - - - codegen - - - - org.codehaus.mojo - exec-maven-plugin - 3.6.3 - - - codegen-npm-install - generate-sources - - exec - - - npm - ${project.basedir}/scripts/codegen - - ci - - - - - codegen-generate - generate-sources - - exec - - - npm - ${project.basedir}/scripts/codegen - - run - generate - - - - - - - - diff --git a/java/scripts/codegen/java.ts b/java/scripts/codegen/java.ts index f0c083c6b..3bdc51d03 100644 --- a/java/scripts/codegen/java.ts +++ b/java/scripts/codegen/java.ts @@ -4,7 +4,7 @@ /** * Java code generator for session-events and RPC types. - * Generates Java source files under src/generated/java/ from JSON Schema files. + * Generates Java source files under sdk/src/generated/java/ from JSON Schema files. */ import fs from "fs/promises"; @@ -256,6 +256,40 @@ function resolveRef(schema: JSONSchema7 | undefined): JSONSchema7 | undefined { return schema; } +function hasOmissionSentinel(schema: JSONSchema7): boolean { + return (schema.anyOf ?? []).some( + (variant) => + typeof variant === "object" + && variant !== null + && typeof (variant as JSONSchema7).not === "object" + && (variant as JSONSchema7).not !== null + && Object.keys((variant as JSONSchema7).not as object).length === 0 + ); +} + +/** + * Resolve a method's params schema to the object schema that carries its properties. + * + * Methods whose params object is entirely optional are published as + * `anyOf: [{ not: {} }, { ...object }]`, so the properties live on a variant + * rather than on the schema itself. + */ +function resolveMethodParamsSchema(method: RpcMethodNode): JSONSchema7 | undefined { + const params = resolveRef(method.params ?? undefined); + if (!params || typeof params !== "object") return undefined; + if (params.properties) return params; + if (!Array.isArray(params.anyOf)) return undefined; + const objectVariants = resolveAnyOfVariants(params.anyOf as JSONSchema7[]).filter((variant) => !!variant.properties); + return hasOmissionSentinel(params) && objectVariants.length === 1 ? objectVariants[0] : undefined; +} + +function resolveMethodParamsUnionSchema(method: RpcMethodNode): JSONSchema7 | undefined { + const params = resolveRef(method.params ?? undefined); + if (!params || typeof params !== "object" || !Array.isArray(params.anyOf)) return undefined; + const variants = resolveAnyOfVariants(params.anyOf as JSONSchema7[]); + return variants.length > 1 && findDiscriminator(variants) ? params : undefined; +} + /** Extract the definition name from a $ref string (e.g., "#/definitions/Foo" → "Foo") */ function extractRefName(schema: JSONSchema7 | null | undefined): string | null { if (!schema?.$ref) return null; @@ -745,7 +779,7 @@ async function generateSessionEvents(schemaPath: string): Promise { const variants = extractEventVariants(schema); const packageName = "com.github.copilot.generated"; - const packageDir = `src/generated/java/com/github/copilot/generated`; + const packageDir = `sdk/src/generated/java/com/github/copilot/generated`; // Generate base SessionEvent class await generateSessionEventBaseClass(variants, packageName, packageDir); @@ -1350,7 +1384,7 @@ async function generateRpcTypes(schemaPath: string): Promise { } const packageName = "com.github.copilot.generated.rpc"; - const packageDir = `src/generated/java/com/github/copilot/generated/rpc`; + const packageDir = `sdk/src/generated/java/com/github/copilot/generated/rpc`; // Collect all RPC methods from all sections const sections: [string, Record][] = []; @@ -1379,6 +1413,19 @@ async function generateRpcTypes(schemaPath: string): Promise { } else if (paramsSchema?.$ref) { paramsSchema = resolveRef(paramsSchema) as JSONSchema7; } + const paramsUnionSchema = resolveMethodParamsUnionSchema(method); + if (paramsUnionSchema) { + const paramsClassName = `${className}Params`; + if (!generatedClasses.has(paramsClassName)) { + generatedClasses.set(paramsClassName, true); + await generatePolymorphicResultClass(paramsClassName, paramsUnionSchema, packageName, packageDir); + allFiles.push(`${paramsClassName}.java`); + } + paramsSchema = null; + } + if (paramsSchema && !paramsSchema.properties) { + paramsSchema = resolveMethodParamsSchema(method) ?? paramsSchema; + } if (paramsSchema && typeof paramsSchema === "object" && paramsSchema.properties) { const paramsClassName = `${className}Params`; if (!generatedClasses.has(paramsClassName)) { @@ -1669,9 +1716,11 @@ function addWrapperResultImports(resultType: string, allImports: Set, pa * callers supply it explicitly. */ function wrapperParamsClassName(method: RpcMethodNode, isSession: boolean): string | null { - let params = method.params; - if (params?.$ref) params = resolveRef(params) as JSONSchema7; - if (!params || typeof params !== "object") return null; + if (resolveMethodParamsUnionSchema(method)) { + return rpcMethodToClassName(method.rpcMethod) + "Params"; + } + const params = resolveMethodParamsSchema(method); + if (!params) return null; const props = params.properties ?? {}; const userProps = Object.keys(props).filter((k) => !isSession || k !== "sessionId"); if (userProps.length === 0) return null; @@ -1680,11 +1729,16 @@ function wrapperParamsClassName(method: RpcMethodNode, isSession: boolean): stri /** True if the method's params schema contains a "sessionId" property */ function methodHasSessionId(method: RpcMethodNode): boolean { - let params = method.params; - if (params?.$ref) params = resolveRef(params) as JSONSchema7; + const params = resolveMethodParamsSchema(method); return !!params?.properties && "sessionId" in params.properties; } +/** True if the method's params object may be omitted entirely */ +function methodParamsAreOptional(method: RpcMethodNode): boolean { + const params = resolveRef(method.params ?? undefined); + return !!params && typeof params === "object" && hasOmissionSentinel(params); +} + /** * Generate the Java source for a single method in a wrapper API class. * Returns the Java source lines and whether an ObjectMapper is required. @@ -1699,6 +1753,7 @@ function generateApiMethod( const paramsClass = wrapperParamsClassName(method, isSession); const hasSessionId = methodHasSessionId(method); const hasExtraParams = paramsClass !== null; + const paramsOptional = hasExtraParams && methodParamsAreOptional(method); let needsMapper = false; const lines: string[] = []; @@ -1707,26 +1762,39 @@ function generateApiMethod( const description = (method.params as JSONSchema7 | null)?.description ?? (method.result as JSONSchema7 | null)?.description ?? `Invokes {@code ${method.rpcMethod}}.`; - lines.push(` /**`); - lines.push(` * ${description}`); - if (isSession && hasExtraParams && hasSessionId) { - lines.push(` *

`); - lines.push(` * Note: the {@code sessionId} field in the params record is overridden`); - lines.push(` * by the session-scoped wrapper; any value provided is ignored.`); - } - if (method.stability === "experimental") { - lines.push(` *`); - lines.push(` * @apiNote This method is experimental and may change in a future version.`); - } - lines.push(` * @since 1.0.0`); - lines.push(` */`); - if (method.deprecated) { - lines.push(` @Deprecated`); - } - if (method.stability === "experimental") { - lines.push(` @CopilotExperimental`); + const pushJavadoc = (extraLines: string[] = [], includeSessionIdNote = true): void => { + lines.push(` /**`); + lines.push(` * ${description}`); + if (includeSessionIdNote && isSession && hasExtraParams && hasSessionId) { + lines.push(` *

`); + lines.push(` * Note: the {@code sessionId} field in the params record is overridden`); + lines.push(` * by the session-scoped wrapper; any value provided is ignored.`); + } + lines.push(...extraLines); + if (method.stability === "experimental") { + lines.push(` *`); + lines.push(` * @apiNote This method is experimental and may change in a future version.`); + } + lines.push(` * @since 1.0.0`); + lines.push(` */`); + if (method.deprecated) { + lines.push(` @Deprecated`); + } + if (method.stability === "experimental") { + lines.push(` @CopilotExperimental`); + } + }; + + if (paramsOptional) { + pushJavadoc([` *

`, ` * Invokes the method with no params, applying the runtime defaults.`], false); + lines.push(` public CompletableFuture<${resultClass}> ${key}() {`); + lines.push(` return ${key}(null);`); + lines.push(` }`); + lines.push(``); } + pushJavadoc(); + // Signature if (hasExtraParams) { lines.push(` public CompletableFuture<${resultClass}> ${key}(${paramsClass} params) {`); @@ -1739,7 +1807,10 @@ function generateApiMethod( if (hasExtraParams) { // Merge sessionId into the params using Jackson ObjectNode needsMapper = true; - lines.push(` com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params);`); + const paramsNode = paramsOptional + ? `params == null ? MAPPER.createObjectNode() : MAPPER.valueToTree(params)` + : `MAPPER.valueToTree(params)`; + lines.push(` com.fasterxml.jackson.databind.node.ObjectNode _p = ${paramsNode};`); lines.push(` _p.put("sessionId", ${sessionIdExpr});`); lines.push(` return caller.invoke("${method.rpcMethod}", _p, ${wrapperResultTypeExpression(resultClass)});`); } else if (hasSessionId) { @@ -1750,7 +1821,8 @@ function generateApiMethod( } else { // Server-side: pass params directly (or empty map if no params) if (hasExtraParams) { - lines.push(` return caller.invoke("${method.rpcMethod}", params, ${wrapperResultTypeExpression(resultClass)});`); + const paramsArg = paramsOptional ? `params == null ? java.util.Map.of() : params` : `params`; + lines.push(` return caller.invoke("${method.rpcMethod}", ${paramsArg}, ${wrapperResultTypeExpression(resultClass)});`); } else { lines.push(` return caller.invoke("${method.rpcMethod}", java.util.Map.of(), ${wrapperResultTypeExpression(resultClass)});`); } @@ -2152,7 +2224,7 @@ async function generateRpcWrappers(schemaPath: string): Promise { currentDefinitions = (schema.definitions ?? {}) as Record; const packageName = "com.github.copilot.generated.rpc"; - const packageDir = `src/generated/java/com/github/copilot/generated/rpc`; + const packageDir = `sdk/src/generated/java/com/github/copilot/generated/rpc`; // RpcCaller interface and shared ObjectMapper holder await generateRpcCallerInterface(packageName, packageDir); @@ -2275,7 +2347,7 @@ async function main(): Promise { console.log("============================"); // Clean the generated output directory to remove orphaned files from previous runs - const generatedOutputDir = path.join(REPO_ROOT, "src/generated/java/com/github/copilot/generated"); + const generatedOutputDir = path.join(REPO_ROOT, "sdk/src/generated/java/com/github/copilot/generated"); console.log(`🧹 Cleaning output directory: ${generatedOutputDir}`); await fs.rm(generatedOutputDir, { recursive: true, force: true }); await fs.mkdir(generatedOutputDir, { recursive: true }); @@ -2290,8 +2362,8 @@ async function main(): Promise { await generateRpcWrappers(apiSchemaPath); // Generate package-info.java for each generated package - const generatedPkgDir = `src/generated/java/com/github/copilot/generated`; - const rpcPkgDir = `src/generated/java/com/github/copilot/generated/rpc`; + const generatedPkgDir = `sdk/src/generated/java/com/github/copilot/generated`; + const rpcPkgDir = `sdk/src/generated/java/com/github/copilot/generated/rpc`; await generateGeneratedPackageInfo(generatedPkgDir); await generateRpcPackageInfo(rpcPkgDir); diff --git a/java/scripts/codegen/package-lock.json b/java/scripts/codegen/package-lock.json index 107a77785..527eef03e 100644 --- a/java/scripts/codegen/package-lock.json +++ b/java/scripts/codegen/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "copilot-sdk-java-codegen", "dependencies": { - "@github/copilot": "^1.0.73", + "@github/copilot": "^1.0.79", "json-schema": "^0.4.0", "tsx": "^4.23.1" } @@ -428,9 +428,9 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.73", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.73.tgz", - "integrity": "sha512-8I2Ejg2CX/PQA3c2H8W1zuqhniCeR1q1/bD8CrV53/ZLw8GF7DAV0xQpwa8ELYvFgjXb6AADojafCKwdbVef+A==", + "version": "1.0.79", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.79.tgz", + "integrity": "sha512-uHBm2BYbKJgyfiKp1WokX7QUNHGvzEX0zaGeb3qM3CybP06rsJrX3JgQe95qwwma6vQz0ah9gV68ERW2JqaKRA==", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { "detect-libc": "^2.1.2" @@ -439,20 +439,20 @@ "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.73", - "@github/copilot-darwin-x64": "1.0.73", - "@github/copilot-linux-arm64": "1.0.73", - "@github/copilot-linux-x64": "1.0.73", - "@github/copilot-linuxmusl-arm64": "1.0.73", - "@github/copilot-linuxmusl-x64": "1.0.73", - "@github/copilot-win32-arm64": "1.0.73", - "@github/copilot-win32-x64": "1.0.73" + "@github/copilot-darwin-arm64": "1.0.79", + "@github/copilot-darwin-x64": "1.0.79", + "@github/copilot-linux-arm64": "1.0.79", + "@github/copilot-linux-x64": "1.0.79", + "@github/copilot-linuxmusl-arm64": "1.0.79", + "@github/copilot-linuxmusl-x64": "1.0.79", + "@github/copilot-win32-arm64": "1.0.79", + "@github/copilot-win32-x64": "1.0.79" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.73", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.73.tgz", - "integrity": "sha512-5jv7t2sw35/zI0cPze38hG6239NT5/q/Emjx6gLibYkolDqMDJjpm17Ps7tc8oafUEOiMQMb+ar7+qi6rSiGJA==", + "version": "1.0.79", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.79.tgz", + "integrity": "sha512-rsw7JoMvlcxXb0yx08oIeEc0x2hUEwKSfhX9ESKfdMVt0Ckrzm4OEvNUyzOpOnLJ9+l3h/aI+u1w5g2ZU2K7UA==", "cpu": [ "arm64" ], @@ -466,9 +466,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.73", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.73.tgz", - "integrity": "sha512-l794k6Ahb11AG2FQT/P4TEWxWblzM1h8aQQCzG8jBWp8dfwjhyYjJ+d+0CWQzM3Fc1ddNUZRjKXCUsfvFjiZhQ==", + "version": "1.0.79", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.79.tgz", + "integrity": "sha512-D983e2lXYnq+KhjA8mTZXonY1+LGfJN9BM195J73shUvx49nRJmibDHWLvVtGeYc+43evGUOAQrOqOspAhhWPQ==", "cpu": [ "x64" ], @@ -482,9 +482,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.73", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.73.tgz", - "integrity": "sha512-Zu0W5nupJjNeem0brqU/pG+VY0IWr6EWr/FsC90g5SEDiaM4VhVNVWcz8t0E3DQCSYetV6IBaNMtjs/3uIIiDQ==", + "version": "1.0.79", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.79.tgz", + "integrity": "sha512-qqaNkvi92Wg+4OZk/kTWC2nUG72G0vV6eRAo5+PnKaPmjdX1GsI0a+lPxXPEbzX0zYLi/8yrUyANwyyNEsGgXA==", "cpu": [ "arm64" ], @@ -498,9 +498,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.73", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.73.tgz", - "integrity": "sha512-k33XIr6/PVp+K+5F/zv3No4PPaNImvHz73mcbIw63oxh5iiacXjgr0WqbBIS5s/rkhOWjNPIkbof/TTPZ7mQjA==", + "version": "1.0.79", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.79.tgz", + "integrity": "sha512-wzotZfvHkItutciLFMXZT2k9Qiii4Ta8tsVDCMQ7CP8hPxV91FyJ1yf3+FFSSfPvWrfYM6BOAiqIuX+LjgRuiw==", "cpu": [ "x64" ], @@ -514,9 +514,9 @@ } }, "node_modules/@github/copilot-linuxmusl-arm64": { - "version": "1.0.73", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.73.tgz", - "integrity": "sha512-HJWzhfD3oaiIgfRAHkNWzp17fELtshqM9HVN5n+lFEmSO2EETCEh0P1lhJc4m+FYfXSJnL0raAqVuyaNMuPoPw==", + "version": "1.0.79", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.79.tgz", + "integrity": "sha512-INtRSARl7DdNm2MXnn4GJuK+Y7QD24ANox02uH8htNQwRlNvdvg+YGS1V/mYgLDXFepeUjMjzTNC+i70+kh5uw==", "cpu": [ "arm64" ], @@ -530,9 +530,9 @@ } }, "node_modules/@github/copilot-linuxmusl-x64": { - "version": "1.0.73", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.73.tgz", - "integrity": "sha512-/BpOXSb16wHEu8I1SaKiLszQ4Kvu4+Z4uCn7W0bv4xI4fPZwTEG0u3zgaI2W9Ao3+aBl0XRpPmpWzE9ziYEq+w==", + "version": "1.0.79", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.79.tgz", + "integrity": "sha512-LxJAIfPP6Ok/9qpXGZuhnAft3W9JVcK9tbO3jWXcGDJT3v+2NtutyjmP/A7/cDXdTruXVQ4MybwAgacN8Gj/sg==", "cpu": [ "x64" ], @@ -546,9 +546,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.73", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.73.tgz", - "integrity": "sha512-DbPeXiYzQjpOy9oboaBvuCzjRwfcL987c3bG09cK1crdCDrKfkTJ7NXpcp1KWRPIRFO1FQm1qToNE89J+L3uvg==", + "version": "1.0.79", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.79.tgz", + "integrity": "sha512-5wg/ayCBTVy4g4FdO/9BJZRVARY0sgjAn9rBkw5BSJMv4u7Mvxg5Sftlift+V5UWxTyCSHAELZ5IHKvox4Yi8w==", "cpu": [ "arm64" ], @@ -562,9 +562,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.73", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.73.tgz", - "integrity": "sha512-8D3E1l5i+N5Eq8HIOQpx+Zbcb3MXdFxszksM2gqq175Z1S7Zna67oY4GoR3psxlbIpSyHKiLEBWYiaps6ayHWw==", + "version": "1.0.79", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.79.tgz", + "integrity": "sha512-FTpThWwwCDYnLdE0pfdo5zpAQLLVg36kmC2IKyVMuCYv9iPe7rE1mz7ng/UITN9M3TAMBrwHSvCV3pITvw4W8Q==", "cpu": [ "x64" ], diff --git a/java/scripts/codegen/package.json b/java/scripts/codegen/package.json index dbe8b5e6e..b1393f40b 100644 --- a/java/scripts/codegen/package.json +++ b/java/scripts/codegen/package.json @@ -7,7 +7,7 @@ "generate:java": "tsx java.ts" }, "dependencies": { - "@github/copilot": "^1.0.73", + "@github/copilot": "^1.0.79", "json-schema": "^0.4.0", "tsx": "^4.23.1" } diff --git a/java/scripts/test-update-documentation-versions.sh b/java/scripts/test-update-documentation-versions.sh new file mode 100755 index 000000000..606e2f7f8 --- /dev/null +++ b/java/scripts/test-update-documentation-versions.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash + +set -euo pipefail + +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +UPDATER="${SCRIPT_DIR}/update-documentation-versions.sh" +TEMP_DIR=$(mktemp -d) +trap 'rm -rf "$TEMP_DIR"' EXIT + +run_case() { + local name=$1 + local old_version=$2 + local old_dev_version=$3 + local version=$4 + local dev_version=$5 + local old_jbang_version=$6 + local case_dir="${TEMP_DIR}/${name}" + + mkdir "$case_dir" + printf '%s\n' \ + '' \ + ' copilot-sdk-java' \ + " ${old_version}" \ + '' \ + "implementation 'com.github:copilot-sdk-java:${old_version}'" \ + '' \ + ' copilot-sdk-java' \ + " ${old_dev_version}" \ + '' \ + "implementation 'com.github:copilot-sdk-java:${old_dev_version}'" \ + '' \ + ' jna' \ + ' 5.19.1' \ + '' \ + > "${case_dir}/README.md" + printf '%s\n' \ + "///usr/bin/env jbang \"\$0\" \"\$@\" ; exit \$?" \ + "//DEPS com.github:copilot-sdk-java:${old_jbang_version}" \ + > "${case_dir}/jbang-example.java" + + "$UPDATER" "$version" "$dev_version" "${case_dir}/README.md" "${case_dir}/jbang-example.java" + + grep -Fqx " ${version}" "${case_dir}/README.md" + grep -Fqx "implementation 'com.github:copilot-sdk-java:${version}'" "${case_dir}/README.md" + grep -Fqx " ${dev_version}" "${case_dir}/README.md" + grep -Fqx "implementation 'com.github:copilot-sdk-java:${dev_version}'" "${case_dir}/README.md" + grep -Fqx ' 5.19.1' "${case_dir}/README.md" + grep -Fqx "//DEPS com.github:copilot-sdk-java:${version}" "${case_dir}/jbang-example.java" + + if grep -Fq "$old_version" "${case_dir}/README.md" "${case_dir}/jbang-example.java" || + grep -Fq "$old_dev_version" "${case_dir}/README.md"; then + echo "Stale version remained in ${name} test output" >&2 + exit 1 + fi +} + +run_case stable 1.0.8 1.0.9-SNAPSHOT 1.0.9 1.0.10-SNAPSHOT "\${project.version}" +run_case preview 1.0.9-preview.2-01 1.0.10-preview.2-SNAPSHOT 1.0.10-preview.2 1.0.11-preview.2-SNAPSHOT 1.0.9-preview.2-01 diff --git a/java/scripts/update-documentation-versions.sh b/java/scripts/update-documentation-versions.sh new file mode 100755 index 000000000..5280f55d5 --- /dev/null +++ b/java/scripts/update-documentation-versions.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash + +set -euo pipefail + +if [[ $# -ne 4 ]]; then + echo "Usage: $0 " >&2 + exit 2 +fi + +VERSION=$1 +DEV_VERSION=$2 +README=$3 +JBANG_EXAMPLE=$4 +VERSION_FORMAT='[0-9]+\.[0-9]+\.[0-9]+(-(preview|(beta-)?java(-preview)?)\.[0-9]+)?' + +if [[ ! "$VERSION" =~ ^${VERSION_FORMAT}$ ]]; then + echo "Invalid release version: $VERSION" >&2 + exit 2 +fi +if [[ ! "$DEV_VERSION" =~ ^${VERSION_FORMAT}-SNAPSHOT$ ]]; then + echo "Invalid development version: $DEV_VERSION" >&2 + exit 2 +fi +if [[ ! -f "$README" || ! -f "$JBANG_EXAMPLE" ]]; then + echo "README and JBang example files must exist" >&2 + exit 2 +fi + +export VERSION DEV_VERSION + +perl -0 - "$README" <<'PERL' +use strict; +use warnings; + +my ($path) = @ARGV; +open my $input, '<', $path or die "Cannot read $path: $!\n"; +my $content = do { local $/; <$input> }; +close $input or die "Cannot close $path: $!\n"; + +# Match accepted release versions plus numeric suffixes left by the former broken updater. +my $version = qr/[0-9]+\.[0-9]+\.[0-9]+(?:-(?:preview|(?:beta-)?java(?:-preview)?)\.[0-9]+)?(?:-[0-9]+)*/; +my $sdk_dependency_version = qr{(copilot-sdk-java(?:(?!).)*?)}s; +my $snapshot_xml = ($content =~ s{$sdk_dependency_version$version-SNAPSHOT}{$1$ENV{DEV_VERSION}}g); +my $snapshot_gradle = ($content =~ s{(copilot-sdk-java:)$version-SNAPSHOT(?![-A-Za-z0-9.])}{$1 . $ENV{DEV_VERSION}}ge); +my $release_xml = ($content =~ s{$sdk_dependency_version$version}{$1$ENV{VERSION}}g); +my $release_gradle = ($content =~ s{(copilot-sdk-java:)$version(?![-A-Za-z0-9.])}{$1 . $ENV{VERSION}}ge); + +die "Expected one release and one snapshot example for both Maven and Gradle in $path\n" + unless $snapshot_xml == 1 && $snapshot_gradle == 1 && $release_xml == 1 && $release_gradle == 1; + +open my $output, '>', $path or die "Cannot write $path: $!\n"; +print {$output} $content; +close $output or die "Cannot close $path: $!\n"; +PERL + +perl -0 - "$JBANG_EXAMPLE" <<'PERL' +use strict; +use warnings; + +my ($path) = @ARGV; +open my $input, '<', $path or die "Cannot read $path: $!\n"; +my $content = do { local $/; <$input> }; +close $input or die "Cannot close $path: $!\n"; + +my $version = qr/[0-9]+\.[0-9]+\.[0-9]+(?:-(?:preview|(?:beta-)?java(?:-preview)?)\.[0-9]+)?(?:-[0-9]+)*/; +my $version_count = ($content =~ s{(copilot-sdk-java:)$version(?![-A-Za-z0-9.])}{$1 . $ENV{VERSION}}ge); +my $placeholder_count = ($content =~ s{copilot-sdk-java:\$\{project\.version\}}{copilot-sdk-java:$ENV{VERSION}}g); + +die "Expected exactly one Copilot SDK dependency in $path\n" + unless $version_count + $placeholder_count == 1; + +open my $output, '>', $path or die "Cannot write $path: $!\n"; +print {$output} $content; +close $output or die "Cannot close $path: $!\n"; +PERL + +grep -Fqx " ${VERSION}" "$README" +grep -Fqx "implementation 'com.github:copilot-sdk-java:${VERSION}'" "$README" +grep -Fqx " ${DEV_VERSION}" "$README" +grep -Fqx "implementation 'com.github:copilot-sdk-java:${DEV_VERSION}'" "$README" +grep -Fqx "//DEPS com.github:copilot-sdk-java:${VERSION}" "$JBANG_EXAMPLE" diff --git a/java/config/checkstyle/checkstyle.xml b/java/sdk/config/checkstyle/checkstyle.xml similarity index 100% rename from java/config/checkstyle/checkstyle.xml rename to java/sdk/config/checkstyle/checkstyle.xml diff --git a/java/config/spotbugs/spotbugs-exclude.xml b/java/sdk/config/spotbugs/spotbugs-exclude.xml similarity index 100% rename from java/config/spotbugs/spotbugs-exclude.xml rename to java/sdk/config/spotbugs/spotbugs-exclude.xml diff --git a/java/jbang-example.java b/java/sdk/jbang-example.java similarity index 96% rename from java/jbang-example.java rename to java/sdk/jbang-example.java index 506973d72..e60509eed 100644 --- a/java/jbang-example.java +++ b/java/sdk/jbang-example.java @@ -1,5 +1,5 @@ ///usr/bin/env jbang "$0" "$@" ; exit $? -//DEPS com.github:copilot-sdk-java:1.0.8-preview.0-01 +//DEPS com.github:copilot-sdk-java:1.0.11-preview.2 import com.github.copilot.CopilotClient; import com.github.copilot.generated.AssistantMessageEvent; import com.github.copilot.generated.SessionUsageInfoEvent; diff --git a/java/sdk/pom.xml b/java/sdk/pom.xml new file mode 100644 index 000000000..0c93a2b30 --- /dev/null +++ b/java/sdk/pom.xml @@ -0,0 +1,798 @@ + + + + 4.0.0 + + + com.github + copilot-sdk-java-parent + 1.0.12-preview.2-SNAPSHOT + ../pom.xml + + + com.github + copilot-sdk-java + jar + + GitHub Copilot SDK :: Java + Official SDK for programmatic control of GitHub Copilot CLI + https://github.com/github/copilot-sdk + + + scm:git:https://github.com/github/copilot-sdk.git + scm:git:https://github.com/github/copilot-sdk.git + https://github.com/github/copilot-sdk + HEAD + + + + + central + https://central.sonatype.com/repository/maven-snapshots/ + + + + + + ${project.basedir}/../.. + ${copilot.sdk.root}/test + + ${copilot.sdk.root}/nodejs/node_modules/@github/copilot/npm-loader.js + + ${copilot.sdk.root}/nodejs/node_modules/@github/copilot-linux-x64/copilot + + false + + ${skip.test.harness} + + notice + + + + false + + 5.19.1 + + + + + + com.fasterxml.jackson.core + jackson-databind + 2.22.1 + + + com.fasterxml.jackson.core + jackson-annotations + 2.22 + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.22.1 + + + + + com.github.spotbugs + spotbugs-annotations + 4.10.3 + provided + + + + + net.java.dev.jna + jna + ${jna.version} + true + + + + + org.junit.jupiter + junit-jupiter + 5.14.4 + test + + + org.mockito + mockito-core + 5.23.0 + test + + + + + + + src/main/resources + true + + + + + + com.github.spotbugs + spotbugs-maven-plugin + + config/spotbugs/spotbugs-exclude.xml + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + -Acopilot.experimental.allowed=true + + none + + + + org.apache.maven.plugins + maven-antrun-plugin + + + print-test-jdk-banner + process-test-classes + + run + + + + + + + + + + + + org.codehaus.mojo + exec-maven-plugin + + + install-harness-dependencies + generate-test-resources + + exec + + + ${skip.test.harness} + npm + ${copilot.sdk.root}/test/harness + + ci + --loglevel + ${npm.loglevel} + + + + + + install-nodejs-cli-dependencies + generate-test-resources + + exec + + + ${skip.cli.install} + npm + ${copilot.sdk.root}/nodejs + + ci + --ignore-scripts + --loglevel + ${npm.loglevel} + + + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + + + + ${project.build.directory} + ${project.build.finalName} + ${project.build.testOutputDirectory} + + + + ${copilot.cli.path} + + + + + org.apache.maven.plugins + maven-surefire-plugin + + alphabetical + + + ${testExecutionAgentArgs} ${surefire.jvm.args} --add-opens com.github.copilot.java/com.github.copilot.e2e=ALL-UNNAMED + + 2 + + ${copilot.tests.dir} + ${copilot.sdk.root} + + + + ${copilot.cli.path} + + + + + + isolated-resume-tests + test + + test + + + isolated-resume + + ${project.build.directory}/surefire-reports-isolated + + + + + default-test + + isolated-resume + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-generated-source + generate-sources + + add-source + + + + ${project.basedir}/src/generated/java + + + + + + + com.diffplug.spotless + spotless-maven-plugin + + + + src/generated/java/**/*.java + + + 4.33 + + + + + + true + 4 + + + + + + + org.jacoco + jacoco-maven-plugin + + + + wire-up-coverage-instrumentation + + prepare-agent + + + + ${project.build.directory}/jacoco-test-results/sdk-tests.exec + + testExecutionAgentArgs + + + com/github/copilot/** + + + com/github/copilot/E2ETestContext* + com/github/copilot/CapiProxy* + + + + + + build-coverage-report-from-tests + + report + + verify + + ${project.build.directory}/jacoco-test-results/sdk-tests.exec + ${project.reporting.outputDirectory}/jacoco-coverage + + + META-INF/versions/**/*.class + + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + config/checkstyle/checkstyle.xml + true + true + false + + + + validate + validate + + check + + + + + + com.puppycrawl.tools + checkstyle + 10.26.1 + + + + + + org.sonatype.central + central-publishing-maven-plugin + true + + central + true + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + enforce-jdk25 + + enforce + + + + + [25,) + JDK 25+ is required to build the Multi-Release JAR with the virtual-thread overlay. + + + + + + verify-multi-release-overlay + verify + + enforce + + + + + + ${project.build.outputDirectory}/META-INF/versions/25/com/github/copilot/InternalExecutorProvider.class + + Multi-Release JAR overlay missing: META-INF/versions/25/com/github/copilot/InternalExecutorProvider.class was not compiled. Ensure the build runs on JDK 25+. + + + + + + + + + + + + + jdk21+ + + [21,) + + + -XX:+EnableDynamicAgentLoading + + + + java25-multi-release + + [25,) + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + compile-java25 + compile + + compile + + + 25 + false + + ${project.basedir}/src/main/java25 + + true + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + true + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + verify-java25-overlay + package + + run + + + + + + + + + +JDK 25 multi-release overlay class is missing from the packaged JAR. +Expected entry: META-INF/versions/25/com/github/copilot/InternalExecutorProvider.class +JAR: ${project.build.directory}/${project.build.finalName}.jar + +This usually means the 'java25-multi-release' Maven profile did not activate +(e.g. the build is running on a JDK older than 25) or maven-compiler-plugin +did not produce the multi-release output. Re-build on JDK 25+ and verify the +'compile-java25' execution ran during the 'compile' phase. + + + + + + + + + + + + skip-test-harness + + true + + + + + inprocess + + inprocess + + + + + com.github + copilot-sdk-java-runtime + ${project.version} + linux-x64 + test + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + false + 1 + none + + inprocess + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + 1 + none + + ${copilot.inprocess.cli.path} + inprocess + + + + + + + + + skip-cli-install-when-tests-skipped + + + skipTests + true + + + + true + + + + + skip-cli-install-when-maven-test-skip + + + maven.test.skip + true + + + + true + + + + + debug + + + + org.apache.maven.plugins + maven-surefire-plugin + + + ${project.basedir}/src/test/resources/logging-debug.properties + + + + + + + + + update-schemas-from-npm-artifact + + + + org.codehaus.mojo + exec-maven-plugin + + + update-copilot-schema-version + generate-sources + + exec + + + npm + ${project.parent.basedir}/scripts/codegen + + install + @github/copilot@${copilot.schema.version} + + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + require-schema-version + validate + + enforce + + + + + copilot.schema.version + You must specify -Dcopilot.schema.version=VERSION (e.g. 1.0.25) + + + + + + + + + + + + codegen + + + + org.codehaus.mojo + exec-maven-plugin + + + codegen-npm-install + generate-sources + + exec + + + npm + ${project.parent.basedir}/scripts/codegen + + ci + + + + + codegen-generate + generate-sources + + exec + + + npm + ${project.parent.basedir}/scripts/codegen + + run + generate + + + + + + + + + + diff --git a/java/src/generated/java/com/github/copilot/generated/AbortEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/AbortEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/AbortEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/AbortEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/AbortReason.java b/java/sdk/src/generated/java/com/github/copilot/generated/AbortReason.java similarity index 90% rename from java/src/generated/java/com/github/copilot/generated/AbortReason.java rename to java/sdk/src/generated/java/com/github/copilot/generated/AbortReason.java index 2ffbdb8d8..c1ba2119a 100644 --- a/java/src/generated/java/com/github/copilot/generated/AbortReason.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/AbortReason.java @@ -21,7 +21,9 @@ public enum AbortReason { /** The {@code remote_command} variant. */ REMOTE_COMMAND("remote_command"), /** The {@code user_abort} variant. */ - USER_ABORT("user_abort"); + USER_ABORT("user_abort"), + /** The {@code autopilot_credit_limit} variant. */ + AUTOPILOT_CREDIT_LIMIT("autopilot_credit_limit"); private final String value; AbortReason(String value) { this.value = value; } diff --git a/java/src/generated/java/com/github/copilot/generated/AssistantIdleEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantIdleEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/AssistantIdleEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/AssistantIdleEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/AssistantIntentEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantIntentEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/AssistantIntentEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/AssistantIntentEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/AssistantMessageDeltaEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageDeltaEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/AssistantMessageDeltaEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageDeltaEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/AssistantMessageEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageEvent.java similarity index 90% rename from java/src/generated/java/com/github/copilot/generated/AssistantMessageEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageEvent.java index fdbdc0afd..fee236ed2 100644 --- a/java/src/generated/java/com/github/copilot/generated/AssistantMessageEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageEvent.java @@ -53,6 +53,10 @@ public record AssistantMessageEventData( @JsonProperty("encryptedContent") String encryptedContent, /** Generation phase for phased-output models (e.g., thinking vs. response phases) */ @JsonProperty("phase") String phase, + /** Zero-based position of this message within its model call's response. Absent when the response was not split into chunks. */ + @JsonProperty("chunkIndex") Long chunkIndex, + /** Total messages the model call's response was split into, one per reasoning boundary. Absent for a single-message response; the last chunk is the one where chunkIndex is chunkCount - 1. */ + @JsonProperty("chunkCount") Long chunkCount, /** Actual output token count from the API response (completion_tokens), used for accurate token accounting */ @JsonProperty("outputTokens") Long outputTokens, /** CAPI interaction ID for correlating this message with upstream telemetry */ @@ -63,6 +67,7 @@ public record AssistantMessageEventData( @JsonProperty("clientRequestId") String clientRequestId, /** Copilot service request ID (x-copilot-service-request-id header) for CAPI log correlation */ @JsonProperty("serviceRequestId") String serviceRequestId, + @JsonProperty("rte") Boolean rte, /** Provider's completion / response identifier; shared across all chunks of a single API call. Used to group multi-chunk assistant utterances. */ @JsonProperty("apiCallId") String apiCallId, /** Neutral provider-tagged server-side tool-use payload (tool search, advisor) for verbatim round-tripping */ diff --git a/java/src/generated/java/com/github/copilot/generated/AssistantMessageServerTools.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageServerTools.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/AssistantMessageServerTools.java rename to java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageServerTools.java diff --git a/java/src/generated/java/com/github/copilot/generated/AssistantMessageStartEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageStartEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/AssistantMessageStartEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageStartEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/AssistantMessageToolRequest.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageToolRequest.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/AssistantMessageToolRequest.java rename to java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageToolRequest.java diff --git a/java/src/generated/java/com/github/copilot/generated/AssistantMessageToolRequestType.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageToolRequestType.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/AssistantMessageToolRequestType.java rename to java/sdk/src/generated/java/com/github/copilot/generated/AssistantMessageToolRequestType.java diff --git a/java/src/generated/java/com/github/copilot/generated/AssistantReasoningDeltaEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantReasoningDeltaEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/AssistantReasoningDeltaEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/AssistantReasoningDeltaEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/AssistantReasoningEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantReasoningEvent.java similarity index 94% rename from java/src/generated/java/com/github/copilot/generated/AssistantReasoningEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/AssistantReasoningEvent.java index 9b69fedac..52996aeee 100644 --- a/java/src/generated/java/com/github/copilot/generated/AssistantReasoningEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantReasoningEvent.java @@ -37,7 +37,8 @@ public record AssistantReasoningEventData( /** Unique identifier for this reasoning block */ @JsonProperty("reasoningId") String reasoningId, /** The complete extended thinking text from the model */ - @JsonProperty("content") String content + @JsonProperty("content") String content, + @JsonProperty("rte") Boolean rte ) { } } diff --git a/java/src/generated/java/com/github/copilot/generated/AssistantServerToolProgressEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantServerToolProgressEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/AssistantServerToolProgressEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/AssistantServerToolProgressEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/AssistantStreamingDeltaEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantStreamingDeltaEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/AssistantStreamingDeltaEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/AssistantStreamingDeltaEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/AssistantToolCallDeltaEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantToolCallDeltaEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/AssistantToolCallDeltaEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/AssistantToolCallDeltaEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/AssistantTurnEndEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantTurnEndEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/AssistantTurnEndEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/AssistantTurnEndEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/AssistantTurnRetryEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantTurnRetryEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/AssistantTurnRetryEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/AssistantTurnRetryEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/AssistantTurnStartEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantTurnStartEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/AssistantTurnStartEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/AssistantTurnStartEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/AssistantUsageApiEndpoint.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantUsageApiEndpoint.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/AssistantUsageApiEndpoint.java rename to java/sdk/src/generated/java/com/github/copilot/generated/AssistantUsageApiEndpoint.java diff --git a/java/src/generated/java/com/github/copilot/generated/AssistantUsageCopilotUsage.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantUsageCopilotUsage.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/AssistantUsageCopilotUsage.java rename to java/sdk/src/generated/java/com/github/copilot/generated/AssistantUsageCopilotUsage.java diff --git a/java/src/generated/java/com/github/copilot/generated/AssistantUsageCopilotUsageTokenDetail.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantUsageCopilotUsageTokenDetail.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/AssistantUsageCopilotUsageTokenDetail.java rename to java/sdk/src/generated/java/com/github/copilot/generated/AssistantUsageCopilotUsageTokenDetail.java diff --git a/java/src/generated/java/com/github/copilot/generated/AssistantUsageEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantUsageEvent.java similarity index 82% rename from java/src/generated/java/com/github/copilot/generated/AssistantUsageEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/AssistantUsageEvent.java index 47bfcbb4c..85bd81d3c 100644 --- a/java/src/generated/java/com/github/copilot/generated/AssistantUsageEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantUsageEvent.java @@ -60,12 +60,15 @@ public record AssistantUsageEventData( @JsonProperty("interTokenLatencyMs") Double interTokenLatencyMs, /** What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for user-initiated calls */ @JsonProperty("initiator") String initiator, + /** Coarse classification of the interaction that produced this call, mirroring the session's per-request agent context (e.g. `conversation-agent`, `conversation-subagent`, `conversation-sampling`, `conversation-background`, `conversation-compaction`, `conversation-user`). Non-billing; lets consumers attribute a model call to a call class (e.g. sub-agent/sidekick) independently of the billing initiator. Absent when the runtime did not classify the request. */ + @JsonProperty("interactionType") String interactionType, /** Completion ID from the model provider (e.g., chatcmpl-abc123) */ @JsonProperty("apiCallId") String apiCallId, /** GitHub request tracing ID (x-github-request-id header) for server-side log correlation */ @JsonProperty("providerCallId") String providerCallId, /** Copilot service request ID (x-copilot-service-request-id header) for CAPI log correlation */ @JsonProperty("serviceRequestId") String serviceRequestId, + @JsonProperty("rte") Boolean rte, /** API endpoint used for this model call, matching CAPI supported_endpoints vocabulary */ @JsonProperty("apiEndpoint") AssistantUsageApiEndpoint apiEndpoint, /** Parent tool call ID when this usage originates from a sub-agent */ @@ -76,6 +79,14 @@ public record AssistantUsageEventData( @JsonProperty("copilotUsage") AssistantUsageCopilotUsage copilotUsage, /** Reasoning effort level used for model calls, if applicable (e.g. "none", "low", "medium", "high", "xhigh", "max") */ @JsonProperty("reasoningEffort") String reasoningEffort, + /** Number of tools available to the model for this call */ + @JsonProperty("availableToolCount") Long availableToolCount, + /** Number of tokens used by tool definitions for this call */ + @JsonProperty("toolTokenCount") Long toolTokenCount, + /** Number of tool calls returned by the model */ + @JsonProperty("numToolCalls") Long numToolCalls, + /** Tool-call counts keyed by tool name */ + @JsonProperty("toolCounts") Map toolCounts, /** Finish reason reported by the model for this API call (e.g. "stop", "length", "tool_calls", "content_filter"). Normalized to OpenAI vocabulary; for Anthropic models a "refusal" stop reason maps to "content_filter". */ @JsonProperty("finishReason") String finishReason, /** Whether the model response was blocked or truncated by content filtering (finish_reason === 'content_filter'). For Anthropic models this corresponds to a 'refusal' stop reason. */ diff --git a/java/src/generated/java/com/github/copilot/generated/AssistantUsageQuotaSnapshot.java b/java/sdk/src/generated/java/com/github/copilot/generated/AssistantUsageQuotaSnapshot.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/AssistantUsageQuotaSnapshot.java rename to java/sdk/src/generated/java/com/github/copilot/generated/AssistantUsageQuotaSnapshot.java diff --git a/java/src/generated/java/com/github/copilot/generated/AutoModeResolvedReasoningBucket.java b/java/sdk/src/generated/java/com/github/copilot/generated/AutoModeResolvedReasoningBucket.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/AutoModeResolvedReasoningBucket.java rename to java/sdk/src/generated/java/com/github/copilot/generated/AutoModeResolvedReasoningBucket.java diff --git a/java/src/generated/java/com/github/copilot/generated/AutoModeSwitchCompletedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/AutoModeSwitchCompletedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/AutoModeSwitchCompletedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/AutoModeSwitchCompletedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/AutoModeSwitchRequestedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/AutoModeSwitchRequestedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/AutoModeSwitchRequestedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/AutoModeSwitchRequestedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/AutoModeSwitchResponse.java b/java/sdk/src/generated/java/com/github/copilot/generated/AutoModeSwitchResponse.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/AutoModeSwitchResponse.java rename to java/sdk/src/generated/java/com/github/copilot/generated/AutoModeSwitchResponse.java diff --git a/java/src/generated/java/com/github/copilot/generated/AutopilotObjectiveChangedOperation.java b/java/sdk/src/generated/java/com/github/copilot/generated/AutopilotObjectiveChangedOperation.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/AutopilotObjectiveChangedOperation.java rename to java/sdk/src/generated/java/com/github/copilot/generated/AutopilotObjectiveChangedOperation.java diff --git a/java/src/generated/java/com/github/copilot/generated/AutopilotObjectiveChangedStatus.java b/java/sdk/src/generated/java/com/github/copilot/generated/AutopilotObjectiveChangedStatus.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/AutopilotObjectiveChangedStatus.java rename to java/sdk/src/generated/java/com/github/copilot/generated/AutopilotObjectiveChangedStatus.java diff --git a/java/src/generated/java/com/github/copilot/generated/BinaryAssetType.java b/java/sdk/src/generated/java/com/github/copilot/generated/BinaryAssetType.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/BinaryAssetType.java rename to java/sdk/src/generated/java/com/github/copilot/generated/BinaryAssetType.java diff --git a/java/src/generated/java/com/github/copilot/generated/CanvasRegistryChangedCanvas.java b/java/sdk/src/generated/java/com/github/copilot/generated/CanvasRegistryChangedCanvas.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/CanvasRegistryChangedCanvas.java rename to java/sdk/src/generated/java/com/github/copilot/generated/CanvasRegistryChangedCanvas.java diff --git a/java/src/generated/java/com/github/copilot/generated/CanvasRegistryChangedCanvasAction.java b/java/sdk/src/generated/java/com/github/copilot/generated/CanvasRegistryChangedCanvasAction.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/CanvasRegistryChangedCanvasAction.java rename to java/sdk/src/generated/java/com/github/copilot/generated/CanvasRegistryChangedCanvasAction.java diff --git a/java/src/generated/java/com/github/copilot/generated/CapabilitiesChangedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/CapabilitiesChangedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/CapabilitiesChangedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/CapabilitiesChangedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/CapabilitiesChangedUI.java b/java/sdk/src/generated/java/com/github/copilot/generated/CapabilitiesChangedUI.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/CapabilitiesChangedUI.java rename to java/sdk/src/generated/java/com/github/copilot/generated/CapabilitiesChangedUI.java diff --git a/java/src/generated/java/com/github/copilot/generated/CitableSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/CitableSource.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/CitableSource.java rename to java/sdk/src/generated/java/com/github/copilot/generated/CitableSource.java diff --git a/java/src/generated/java/com/github/copilot/generated/CitationProvider.java b/java/sdk/src/generated/java/com/github/copilot/generated/CitationProvider.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/CitationProvider.java rename to java/sdk/src/generated/java/com/github/copilot/generated/CitationProvider.java diff --git a/java/src/generated/java/com/github/copilot/generated/CitationReference.java b/java/sdk/src/generated/java/com/github/copilot/generated/CitationReference.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/CitationReference.java rename to java/sdk/src/generated/java/com/github/copilot/generated/CitationReference.java diff --git a/java/src/generated/java/com/github/copilot/generated/CitationSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/CitationSource.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/CitationSource.java rename to java/sdk/src/generated/java/com/github/copilot/generated/CitationSource.java diff --git a/java/src/generated/java/com/github/copilot/generated/CitationSpan.java b/java/sdk/src/generated/java/com/github/copilot/generated/CitationSpan.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/CitationSpan.java rename to java/sdk/src/generated/java/com/github/copilot/generated/CitationSpan.java diff --git a/java/src/generated/java/com/github/copilot/generated/Citations.java b/java/sdk/src/generated/java/com/github/copilot/generated/Citations.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/Citations.java rename to java/sdk/src/generated/java/com/github/copilot/generated/Citations.java diff --git a/java/src/generated/java/com/github/copilot/generated/CommandCompletedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/CommandCompletedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/CommandCompletedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/CommandCompletedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/CommandExecuteEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/CommandExecuteEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/CommandExecuteEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/CommandExecuteEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/CommandQueuedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/CommandQueuedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/CommandQueuedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/CommandQueuedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/CommandsChangedCommand.java b/java/sdk/src/generated/java/com/github/copilot/generated/CommandsChangedCommand.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/CommandsChangedCommand.java rename to java/sdk/src/generated/java/com/github/copilot/generated/CommandsChangedCommand.java diff --git a/java/src/generated/java/com/github/copilot/generated/CommandsChangedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/CommandsChangedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/CommandsChangedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/CommandsChangedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/CompactionCompleteCompactionTokensUsed.java b/java/sdk/src/generated/java/com/github/copilot/generated/CompactionCompleteCompactionTokensUsed.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/CompactionCompleteCompactionTokensUsed.java rename to java/sdk/src/generated/java/com/github/copilot/generated/CompactionCompleteCompactionTokensUsed.java diff --git a/java/src/generated/java/com/github/copilot/generated/CompactionCompleteCompactionTokensUsedCopilotUsage.java b/java/sdk/src/generated/java/com/github/copilot/generated/CompactionCompleteCompactionTokensUsedCopilotUsage.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/CompactionCompleteCompactionTokensUsedCopilotUsage.java rename to java/sdk/src/generated/java/com/github/copilot/generated/CompactionCompleteCompactionTokensUsedCopilotUsage.java diff --git a/java/src/generated/java/com/github/copilot/generated/CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail.java b/java/sdk/src/generated/java/com/github/copilot/generated/CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail.java rename to java/sdk/src/generated/java/com/github/copilot/generated/CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/CompactionTrigger.java b/java/sdk/src/generated/java/com/github/copilot/generated/CompactionTrigger.java new file mode 100644 index 000000000..1c77861dc --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/CompactionTrigger.java @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import javax.annotation.processing.Generated; + +/** + * What initiated a conversation compaction + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum CompactionTrigger { + /** The {@code threshold} variant. */ + THRESHOLD("threshold"), + /** The {@code context_limit_retry} variant. */ + CONTEXT_LIMIT_RETRY("context_limit_retry"), + /** The {@code manual} variant. */ + MANUAL("manual"), + /** The {@code memory_pressure} variant. */ + MEMORY_PRESSURE("memory_pressure"), + /** The {@code model_switch} variant. */ + MODEL_SWITCH("model_switch"); + + private final String value; + CompactionTrigger(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static CompactionTrigger fromValue(String value) { + for (CompactionTrigger v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown CompactionTrigger value: " + value); + } +} diff --git a/java/src/generated/java/com/github/copilot/generated/ContextTier.java b/java/sdk/src/generated/java/com/github/copilot/generated/ContextTier.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ContextTier.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ContextTier.java diff --git a/java/src/generated/java/com/github/copilot/generated/CustomAgentsUpdatedAgent.java b/java/sdk/src/generated/java/com/github/copilot/generated/CustomAgentsUpdatedAgent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/CustomAgentsUpdatedAgent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/CustomAgentsUpdatedAgent.java diff --git a/java/src/generated/java/com/github/copilot/generated/ElicitationCompletedAction.java b/java/sdk/src/generated/java/com/github/copilot/generated/ElicitationCompletedAction.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ElicitationCompletedAction.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ElicitationCompletedAction.java diff --git a/java/src/generated/java/com/github/copilot/generated/ElicitationCompletedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/ElicitationCompletedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ElicitationCompletedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ElicitationCompletedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/ElicitationRequestedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/ElicitationRequestedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ElicitationRequestedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ElicitationRequestedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/ElicitationRequestedMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/ElicitationRequestedMode.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ElicitationRequestedMode.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ElicitationRequestedMode.java diff --git a/java/src/generated/java/com/github/copilot/generated/ElicitationRequestedSchema.java b/java/sdk/src/generated/java/com/github/copilot/generated/ElicitationRequestedSchema.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ElicitationRequestedSchema.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ElicitationRequestedSchema.java diff --git a/java/src/generated/java/com/github/copilot/generated/ExitPlanModeAction.java b/java/sdk/src/generated/java/com/github/copilot/generated/ExitPlanModeAction.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ExitPlanModeAction.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ExitPlanModeAction.java diff --git a/java/src/generated/java/com/github/copilot/generated/ExitPlanModeCompletedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/ExitPlanModeCompletedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ExitPlanModeCompletedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ExitPlanModeCompletedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/ExitPlanModeRequestedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/ExitPlanModeRequestedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ExitPlanModeRequestedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ExitPlanModeRequestedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/ExtensionsLoadedExtension.java b/java/sdk/src/generated/java/com/github/copilot/generated/ExtensionsLoadedExtension.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ExtensionsLoadedExtension.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ExtensionsLoadedExtension.java diff --git a/java/src/generated/java/com/github/copilot/generated/ExtensionsLoadedExtensionSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/ExtensionsLoadedExtensionSource.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ExtensionsLoadedExtensionSource.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ExtensionsLoadedExtensionSource.java diff --git a/java/src/generated/java/com/github/copilot/generated/ExtensionsLoadedExtensionStatus.java b/java/sdk/src/generated/java/com/github/copilot/generated/ExtensionsLoadedExtensionStatus.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ExtensionsLoadedExtensionStatus.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ExtensionsLoadedExtensionStatus.java diff --git a/java/src/generated/java/com/github/copilot/generated/ExternalToolCompletedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/ExternalToolCompletedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ExternalToolCompletedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ExternalToolCompletedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/ExternalToolRequestedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/ExternalToolRequestedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ExternalToolRequestedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ExternalToolRequestedEvent.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/FactoryRunUpdatedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/FactoryRunUpdatedEvent.java new file mode 100644 index 000000000..e9abb1053 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/FactoryRunUpdatedEvent.java @@ -0,0 +1,42 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Session event "factory.run_updated". Ephemeral invalidation signal for a changed factory run. + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class FactoryRunUpdatedEvent extends SessionEvent { + + @Override + public String getType() { return "factory.run_updated"; } + + @JsonProperty("data") + private FactoryRunUpdatedEventData data; + + public FactoryRunUpdatedEventData getData() { return data; } + public void setData(FactoryRunUpdatedEventData data) { this.data = data; } + + /** Data payload for {@link FactoryRunUpdatedEvent}. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record FactoryRunUpdatedEventData( + @JsonProperty("runId") String runId, + /** Monotonic revision now available for the run. */ + @JsonProperty("revision") Long revision + ) { + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/GitHubMcpToolConfig.java b/java/sdk/src/generated/java/com/github/copilot/generated/GitHubMcpToolConfig.java new file mode 100644 index 000000000..afa69b985 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/GitHubMcpToolConfig.java @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * Per-session configuration for the built-in GitHub MCP server + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record GitHubMcpToolConfig( + /** Whether to use the read-write endpoint and request all toolsets */ + @JsonProperty("enableAllTools") Boolean enableAllTools, + /** Additional GitHub MCP toolsets requested by the session */ + @JsonProperty("additionalToolsets") List additionalToolsets, + /** Additional GitHub MCP tools requested by the session */ + @JsonProperty("additionalTools") List additionalTools, + /** Whether to request the GitHub MCP insiders build */ + @JsonProperty("enableInsidersMode") Boolean enableInsidersMode +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/HandoffRepository.java b/java/sdk/src/generated/java/com/github/copilot/generated/HandoffRepository.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/HandoffRepository.java rename to java/sdk/src/generated/java/com/github/copilot/generated/HandoffRepository.java diff --git a/java/src/generated/java/com/github/copilot/generated/HandoffSourceType.java b/java/sdk/src/generated/java/com/github/copilot/generated/HandoffSourceType.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/HandoffSourceType.java rename to java/sdk/src/generated/java/com/github/copilot/generated/HandoffSourceType.java diff --git a/java/src/generated/java/com/github/copilot/generated/HeaderEntry.java b/java/sdk/src/generated/java/com/github/copilot/generated/HeaderEntry.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/HeaderEntry.java rename to java/sdk/src/generated/java/com/github/copilot/generated/HeaderEntry.java diff --git a/java/src/generated/java/com/github/copilot/generated/HookEndError.java b/java/sdk/src/generated/java/com/github/copilot/generated/HookEndError.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/HookEndError.java rename to java/sdk/src/generated/java/com/github/copilot/generated/HookEndError.java diff --git a/java/src/generated/java/com/github/copilot/generated/HookEndEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/HookEndEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/HookEndEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/HookEndEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/HookProgressEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/HookProgressEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/HookProgressEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/HookProgressEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/HookStartEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/HookStartEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/HookStartEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/HookStartEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/ManagedSettingsEnforcedAction.java b/java/sdk/src/generated/java/com/github/copilot/generated/ManagedSettingsEnforcedAction.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ManagedSettingsEnforcedAction.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ManagedSettingsEnforcedAction.java diff --git a/java/src/generated/java/com/github/copilot/generated/ManagedSettingsEnforcedEscalation.java b/java/sdk/src/generated/java/com/github/copilot/generated/ManagedSettingsEnforcedEscalation.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ManagedSettingsEnforcedEscalation.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ManagedSettingsEnforcedEscalation.java diff --git a/java/src/generated/java/com/github/copilot/generated/ManagedSettingsResolvedSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/ManagedSettingsResolvedSource.java similarity index 83% rename from java/src/generated/java/com/github/copilot/generated/ManagedSettingsResolvedSource.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ManagedSettingsResolvedSource.java index 1ffa10063..32386f898 100644 --- a/java/src/generated/java/com/github/copilot/generated/ManagedSettingsResolvedSource.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/ManagedSettingsResolvedSource.java @@ -10,7 +10,7 @@ import javax.annotation.processing.Generated; /** - * Which channel supplied the effective enterprise managed settings (highest-authority present layer wins wholesale) + * Summary of which managed-settings channels contributed to the effective session policy. Use the per-channel booleans for exact provenance. * * @since 1.0.0 */ @@ -20,6 +20,10 @@ public enum ManagedSettingsResolvedSource { SERVER("server"), /** The {@code device} variant. */ DEVICE("device"), + /** The {@code client} variant. */ + CLIENT("client"), + /** The {@code mixed} variant. */ + MIXED("mixed"), /** The {@code none} variant. */ NONE("none"); diff --git a/java/src/generated/java/com/github/copilot/generated/McpAppToolCallCompleteError.java b/java/sdk/src/generated/java/com/github/copilot/generated/McpAppToolCallCompleteError.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/McpAppToolCallCompleteError.java rename to java/sdk/src/generated/java/com/github/copilot/generated/McpAppToolCallCompleteError.java diff --git a/java/src/generated/java/com/github/copilot/generated/McpAppToolCallCompleteEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/McpAppToolCallCompleteEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/McpAppToolCallCompleteEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/McpAppToolCallCompleteEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/McpAppToolCallCompleteToolMeta.java b/java/sdk/src/generated/java/com/github/copilot/generated/McpAppToolCallCompleteToolMeta.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/McpAppToolCallCompleteToolMeta.java rename to java/sdk/src/generated/java/com/github/copilot/generated/McpAppToolCallCompleteToolMeta.java diff --git a/java/src/generated/java/com/github/copilot/generated/McpAppToolCallCompleteToolMetaUI.java b/java/sdk/src/generated/java/com/github/copilot/generated/McpAppToolCallCompleteToolMetaUI.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/McpAppToolCallCompleteToolMetaUI.java rename to java/sdk/src/generated/java/com/github/copilot/generated/McpAppToolCallCompleteToolMetaUI.java diff --git a/java/src/generated/java/com/github/copilot/generated/McpHeadersRefreshCompletedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/McpHeadersRefreshCompletedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/McpHeadersRefreshCompletedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/McpHeadersRefreshCompletedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/McpHeadersRefreshCompletedOutcome.java b/java/sdk/src/generated/java/com/github/copilot/generated/McpHeadersRefreshCompletedOutcome.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/McpHeadersRefreshCompletedOutcome.java rename to java/sdk/src/generated/java/com/github/copilot/generated/McpHeadersRefreshCompletedOutcome.java diff --git a/java/src/generated/java/com/github/copilot/generated/McpHeadersRefreshRequiredEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/McpHeadersRefreshRequiredEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/McpHeadersRefreshRequiredEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/McpHeadersRefreshRequiredEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/McpHeadersRefreshRequiredReason.java b/java/sdk/src/generated/java/com/github/copilot/generated/McpHeadersRefreshRequiredReason.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/McpHeadersRefreshRequiredReason.java rename to java/sdk/src/generated/java/com/github/copilot/generated/McpHeadersRefreshRequiredReason.java diff --git a/java/src/generated/java/com/github/copilot/generated/McpOauthCompletedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/McpOauthCompletedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/McpOauthCompletedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/McpOauthCompletedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/McpOauthCompletionOutcome.java b/java/sdk/src/generated/java/com/github/copilot/generated/McpOauthCompletionOutcome.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/McpOauthCompletionOutcome.java rename to java/sdk/src/generated/java/com/github/copilot/generated/McpOauthCompletionOutcome.java diff --git a/java/src/generated/java/com/github/copilot/generated/McpOauthHttpResponse.java b/java/sdk/src/generated/java/com/github/copilot/generated/McpOauthHttpResponse.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/McpOauthHttpResponse.java rename to java/sdk/src/generated/java/com/github/copilot/generated/McpOauthHttpResponse.java diff --git a/java/src/generated/java/com/github/copilot/generated/McpOauthRequestReason.java b/java/sdk/src/generated/java/com/github/copilot/generated/McpOauthRequestReason.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/McpOauthRequestReason.java rename to java/sdk/src/generated/java/com/github/copilot/generated/McpOauthRequestReason.java diff --git a/java/src/generated/java/com/github/copilot/generated/McpOauthRequiredEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/McpOauthRequiredEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/McpOauthRequiredEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/McpOauthRequiredEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/McpOauthRequiredStaticClientConfig.java b/java/sdk/src/generated/java/com/github/copilot/generated/McpOauthRequiredStaticClientConfig.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/McpOauthRequiredStaticClientConfig.java rename to java/sdk/src/generated/java/com/github/copilot/generated/McpOauthRequiredStaticClientConfig.java diff --git a/java/src/generated/java/com/github/copilot/generated/McpOauthWWWAuthenticateParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/McpOauthWWWAuthenticateParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/McpOauthWWWAuthenticateParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/McpOauthWWWAuthenticateParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/McpPromptsListChangedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/McpPromptsListChangedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/McpPromptsListChangedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/McpPromptsListChangedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/McpResourcesListChangedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/McpResourcesListChangedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/McpResourcesListChangedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/McpResourcesListChangedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/McpServerSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/McpServerSource.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/McpServerSource.java rename to java/sdk/src/generated/java/com/github/copilot/generated/McpServerSource.java diff --git a/java/src/generated/java/com/github/copilot/generated/McpServerStatus.java b/java/sdk/src/generated/java/com/github/copilot/generated/McpServerStatus.java similarity index 93% rename from java/src/generated/java/com/github/copilot/generated/McpServerStatus.java rename to java/sdk/src/generated/java/com/github/copilot/generated/McpServerStatus.java index b5bb08093..f11cebdc9 100644 --- a/java/src/generated/java/com/github/copilot/generated/McpServerStatus.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/McpServerStatus.java @@ -10,7 +10,7 @@ import javax.annotation.processing.Generated; /** - * Connection status: connected, failed, needs-auth, pending, disabled, or not_configured + * Connection status: connected, failed, needs-auth, pending, disabled, stopped, or not_configured * * @since 1.0.0 */ @@ -26,6 +26,8 @@ public enum McpServerStatus { PENDING("pending"), /** The {@code disabled} variant. */ DISABLED("disabled"), + /** The {@code stopped} variant. */ + STOPPED("stopped"), /** The {@code not_configured} variant. */ NOT_CONFIGURED("not_configured"); diff --git a/java/src/generated/java/com/github/copilot/generated/McpServerTransport.java b/java/sdk/src/generated/java/com/github/copilot/generated/McpServerTransport.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/McpServerTransport.java rename to java/sdk/src/generated/java/com/github/copilot/generated/McpServerTransport.java diff --git a/java/src/generated/java/com/github/copilot/generated/McpServersLoadedServer.java b/java/sdk/src/generated/java/com/github/copilot/generated/McpServersLoadedServer.java similarity index 97% rename from java/src/generated/java/com/github/copilot/generated/McpServersLoadedServer.java rename to java/sdk/src/generated/java/com/github/copilot/generated/McpServersLoadedServer.java index 1a2f05023..c4567f30f 100644 --- a/java/src/generated/java/com/github/copilot/generated/McpServersLoadedServer.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/McpServersLoadedServer.java @@ -23,7 +23,7 @@ public record McpServersLoadedServer( /** Server name (config key) */ @JsonProperty("name") String name, - /** Connection status: connected, failed, needs-auth, pending, disabled, or not_configured */ + /** Connection status: connected, failed, needs-auth, pending, disabled, stopped, or not_configured */ @JsonProperty("status") McpServerStatus status, /** Configuration source: user, workspace, plugin, or builtin */ @JsonProperty("source") McpServerSource source, diff --git a/java/src/generated/java/com/github/copilot/generated/McpToolsListChangedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/McpToolsListChangedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/McpToolsListChangedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/McpToolsListChangedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/ModelCallFailureBadRequestKind.java b/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallFailureBadRequestKind.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ModelCallFailureBadRequestKind.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ModelCallFailureBadRequestKind.java diff --git a/java/src/generated/java/com/github/copilot/generated/ModelCallFailureEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallFailureEvent.java similarity index 99% rename from java/src/generated/java/com/github/copilot/generated/ModelCallFailureEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ModelCallFailureEvent.java index 25c011fe3..f6c399b4c 100644 --- a/java/src/generated/java/com/github/copilot/generated/ModelCallFailureEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallFailureEvent.java @@ -45,6 +45,7 @@ public record ModelCallFailureEventData( @JsonProperty("providerCallId") String providerCallId, /** Copilot service request ID (x-copilot-service-request-id header) for CAPI log correlation */ @JsonProperty("serviceRequestId") String serviceRequestId, + @JsonProperty("rte") Boolean rte, /** HTTP status code from the failed request */ @JsonProperty("statusCode") Long statusCode, /** Duration of the failed API call in milliseconds */ diff --git a/java/src/generated/java/com/github/copilot/generated/ModelCallFailureKind.java b/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallFailureKind.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ModelCallFailureKind.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ModelCallFailureKind.java diff --git a/java/src/generated/java/com/github/copilot/generated/ModelCallFailureRequestFingerprint.java b/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallFailureRequestFingerprint.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ModelCallFailureRequestFingerprint.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ModelCallFailureRequestFingerprint.java diff --git a/java/src/generated/java/com/github/copilot/generated/ModelCallFailureSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallFailureSource.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ModelCallFailureSource.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ModelCallFailureSource.java diff --git a/java/src/generated/java/com/github/copilot/generated/ModelCallFailureTransport.java b/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallFailureTransport.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ModelCallFailureTransport.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ModelCallFailureTransport.java diff --git a/java/src/generated/java/com/github/copilot/generated/ModelCallStartEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallStartEvent.java similarity index 88% rename from java/src/generated/java/com/github/copilot/generated/ModelCallStartEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ModelCallStartEvent.java index 6ab9fa547..9f00e2ac2 100644 --- a/java/src/generated/java/com/github/copilot/generated/ModelCallStartEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/ModelCallStartEvent.java @@ -37,7 +37,9 @@ public record ModelCallStartEventData( /** Identifier of the assistant turn that initiated the model call */ @JsonProperty("turnId") String turnId, /** Model identifier used for this API call, when known */ - @JsonProperty("model") String model + @JsonProperty("model") String model, + /** Previous response or interaction identifier included in the model request, when present */ + @JsonProperty("previousResponseId") String previousResponseId ) { } } diff --git a/java/src/generated/java/com/github/copilot/generated/PendingMessagesModifiedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/PendingMessagesModifiedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/PendingMessagesModifiedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/PendingMessagesModifiedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/PermissionAllowAllMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/PermissionAllowAllMode.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/PermissionAllowAllMode.java rename to java/sdk/src/generated/java/com/github/copilot/generated/PermissionAllowAllMode.java diff --git a/java/src/generated/java/com/github/copilot/generated/PermissionCompletedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/PermissionCompletedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/PermissionCompletedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/PermissionCompletedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/PermissionRequestedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/PermissionRequestedEvent.java similarity index 90% rename from java/src/generated/java/com/github/copilot/generated/PermissionRequestedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/PermissionRequestedEvent.java index 98eecceb5..b7aae9ec8 100644 --- a/java/src/generated/java/com/github/copilot/generated/PermissionRequestedEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/PermissionRequestedEvent.java @@ -40,6 +40,8 @@ public record PermissionRequestedEventData( @JsonProperty("permissionRequest") Object permissionRequest, /** Derived user-facing permission prompt details for UI consumers */ @JsonProperty("promptRequest") Object promptRequest, + /** Neutral risk metadata supplied by the tool host. Consumers may display this value but must not use it to bypass the permission decision. */ + @JsonProperty("riskAssessment") Object riskAssessment, /** When true, this permission was already resolved by a permissionRequest hook and requires no client action */ @JsonProperty("resolvedByHook") Boolean resolvedByHook ) { diff --git a/java/src/generated/java/com/github/copilot/generated/PlanChangedOperation.java b/java/sdk/src/generated/java/com/github/copilot/generated/PlanChangedOperation.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/PlanChangedOperation.java rename to java/sdk/src/generated/java/com/github/copilot/generated/PlanChangedOperation.java diff --git a/java/src/generated/java/com/github/copilot/generated/ReasoningSummary.java b/java/sdk/src/generated/java/com/github/copilot/generated/ReasoningSummary.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ReasoningSummary.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ReasoningSummary.java diff --git a/java/src/generated/java/com/github/copilot/generated/SamplingCompletedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SamplingCompletedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SamplingCompletedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SamplingCompletedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SamplingRequestedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SamplingRequestedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SamplingRequestedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SamplingRequestedEvent.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/ScheduleOrigin.java b/java/sdk/src/generated/java/com/github/copilot/generated/ScheduleOrigin.java new file mode 100644 index 000000000..cba65eadb --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/ScheduleOrigin.java @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import javax.annotation.processing.Generated; + +/** + * Who created the schedule: `user` (an explicit user action such as `/every` or `/after`) or `model` (the agent via the `manage_schedule` tool). Gates whether a scheduled skill that opted out of model invocation may fire: only user-created schedules may. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum ScheduleOrigin { + /** The {@code user} variant. */ + USER("user"), + /** The {@code model} variant. */ + MODEL("model"); + + private final String value; + ScheduleOrigin(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static ScheduleOrigin fromValue(String value) { + for (ScheduleOrigin v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown ScheduleOrigin value: " + value); + } +} diff --git a/java/src/generated/java/com/github/copilot/generated/SessionAutoModeResolvedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionAutoModeResolvedEvent.java similarity index 71% rename from java/src/generated/java/com/github/copilot/generated/SessionAutoModeResolvedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionAutoModeResolvedEvent.java index f79be388e..88b06f447 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionAutoModeResolvedEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionAutoModeResolvedEvent.java @@ -47,7 +47,25 @@ public record SessionAutoModeResolvedEventData( /** Classifier confidence for the predicted label, when available */ @JsonProperty("confidence") Double confidence, /** Ordered candidate model list the router returned, when not a fallback */ - @JsonProperty("candidateModels") List candidateModels + @JsonProperty("candidateModels") List candidateModels, + /** The routing method the server applied, when Auto Intent ran */ + @JsonProperty("routingMethod") String routingMethod, + /** Models offered to the router for this resolution */ + @JsonProperty("availableModels") List availableModels, + /** Whether the router fell back to the standard Auto selection */ + @JsonProperty("fallback") Boolean fallback, + /** Server-provided reason for falling back, when available */ + @JsonProperty("fallbackReason") String fallbackReason, + /** Whether a sticky model choice overrode the router result */ + @JsonProperty("stickyOverride") Boolean stickyOverride, + /** Server-reported router processing time in milliseconds */ + @JsonProperty("routerLatencyMs") Double routerLatencyMs, + /** End-to-end client wait time for the router request in milliseconds */ + @JsonProperty("endToEndLatencyMs") Double endToEndLatencyMs, + /** The chosen model's score shortfall relative to the top candidate */ + @JsonProperty("chosenShortfall") Double chosenShortfall, + /** Whether the routed prompt contained an image */ + @JsonProperty("hasImage") Boolean hasImage ) { } } diff --git a/java/src/generated/java/com/github/copilot/generated/SessionAutopilotObjectiveChangedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionAutopilotObjectiveChangedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionAutopilotObjectiveChangedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionAutopilotObjectiveChangedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionBackgroundTasksChangedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionBackgroundTasksChangedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionBackgroundTasksChangedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionBackgroundTasksChangedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionBinaryAssetEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionBinaryAssetEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionBinaryAssetEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionBinaryAssetEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionCanvasClosedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionCanvasClosedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionCanvasClosedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionCanvasClosedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionCanvasOpenedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionCanvasOpenedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionCanvasOpenedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionCanvasOpenedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionCanvasRecordedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionCanvasRecordedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionCanvasRecordedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionCanvasRecordedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionCanvasRegistryChangedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionCanvasRegistryChangedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionCanvasRegistryChangedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionCanvasRegistryChangedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionCanvasRemovedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionCanvasRemovedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionCanvasRemovedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionCanvasRemovedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionCanvasUnavailableEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionCanvasUnavailableEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionCanvasUnavailableEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionCanvasUnavailableEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionCompactionCompleteEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionCompactionCompleteEvent.java similarity index 93% rename from java/src/generated/java/com/github/copilot/generated/SessionCompactionCompleteEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionCompactionCompleteEvent.java index 8771d3114..d05110abc 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionCompactionCompleteEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionCompactionCompleteEvent.java @@ -69,7 +69,11 @@ public record SessionCompactionCompleteEventData( /** Token count from tool definitions after compaction */ @JsonProperty("toolDefinitionsTokens") Long toolDefinitionsTokens, /** For failed compaction only: the HTTP status code of the compaction LLM call failure, when it carried one. Absent for successful compaction and for failures without an HTTP status (e.g. an empty model response or a transport error). */ - @JsonProperty("statusCode") Long statusCode + @JsonProperty("statusCode") Long statusCode, + /** Model context window token limit the compaction was targeting, when known */ + @JsonProperty("tokenLimit") Long tokenLimit, + /** What initiated this compaction, when known */ + @JsonProperty("trigger") CompactionTrigger trigger ) { } } diff --git a/java/src/generated/java/com/github/copilot/generated/SessionCompactionStartEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionCompactionStartEvent.java similarity index 82% rename from java/src/generated/java/com/github/copilot/generated/SessionCompactionStartEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionCompactionStartEvent.java index e92a3ac50..076a12426 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionCompactionStartEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionCompactionStartEvent.java @@ -41,7 +41,13 @@ public record SessionCompactionStartEventData( /** Token count from non-system messages (user, assistant, tool) at compaction start */ @JsonProperty("conversationTokens") Long conversationTokens, /** Token count from tool definitions at compaction start */ - @JsonProperty("toolDefinitionsTokens") Long toolDefinitionsTokens + @JsonProperty("toolDefinitionsTokens") Long toolDefinitionsTokens, + /** Total context tokens (system + conversation + tool definitions) at compaction start, when known */ + @JsonProperty("currentTokens") Long currentTokens, + /** Model context window token limit the compaction is targeting, when known */ + @JsonProperty("tokenLimit") Long tokenLimit, + /** What initiated this compaction, when known */ + @JsonProperty("trigger") CompactionTrigger trigger ) { } } diff --git a/java/src/generated/java/com/github/copilot/generated/SessionContextChangedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionContextChangedEvent.java similarity index 84% rename from java/src/generated/java/com/github/copilot/generated/SessionContextChangedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionContextChangedEvent.java index 6fb6a54db..fc96eff67 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionContextChangedEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionContextChangedEvent.java @@ -49,7 +49,9 @@ public record SessionContextChangedEventData( /** Head commit of current git branch at session start time */ @JsonProperty("headCommit") String headCommit, /** Base commit of current git branch at session start time */ - @JsonProperty("baseCommit") String baseCommit + @JsonProperty("baseCommit") String baseCommit, + /** Set on the immediate preliminary event of a working-directory change, before the git context is resolved. A settled follow-up event (enriched with git context, or cwd-only for a non-repository) is always emitted afterward, so observers may defer to it. Absent on standalone/final events (e.g. relay context changes). */ + @JsonProperty("pendingGitContext") Boolean pendingGitContext ) { } } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/SessionContextClearedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionContextClearedEvent.java new file mode 100644 index 000000000..7a4e9cd00 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionContextClearedEvent.java @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Session event "session.context_cleared". Context-cleared details emitted when the host clears the conversation (the session.history.clearContext RPC / Session.clearContextMessages) + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SessionContextClearedEvent extends SessionEvent { + + @Override + public String getType() { return "session.context_cleared"; } + + @JsonProperty("data") + private SessionContextClearedEventData data; + + public SessionContextClearedEventData getData() { return data; } + public void setData(SessionContextClearedEventData data) { this.data = data; } + + /** Data payload for {@link SessionContextClearedEvent}. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record SessionContextClearedEventData( + /** Optional initial message set after clearing */ + @JsonProperty("initialMessage") String initialMessage, + /** Number of conversation messages that were cleared */ + @JsonProperty("messagesCleared") Long messagesCleared + ) { + } +} diff --git a/java/src/generated/java/com/github/copilot/generated/SessionCustomAgentsUpdatedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionCustomAgentsUpdatedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionCustomAgentsUpdatedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionCustomAgentsUpdatedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionCustomNotificationEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionCustomNotificationEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionCustomNotificationEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionCustomNotificationEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionErrorEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionErrorEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionErrorEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionErrorEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionEvent.java similarity index 98% rename from java/src/generated/java/com/github/copilot/generated/SessionEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionEvent.java index 162572b03..582ecd3d4 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionEvent.java @@ -51,6 +51,7 @@ @JsonSubTypes.Type(value = SessionUsageCheckpointEvent.class, name = "session.usage_checkpoint"), @JsonSubTypes.Type(value = SessionContextChangedEvent.class, name = "session.context_changed"), @JsonSubTypes.Type(value = SessionUsageInfoEvent.class, name = "session.usage_info"), + @JsonSubTypes.Type(value = SessionContextClearedEvent.class, name = "session.context_cleared"), @JsonSubTypes.Type(value = SessionCompactionStartEvent.class, name = "session.compaction_start"), @JsonSubTypes.Type(value = SessionCompactionCompleteEvent.class, name = "session.compaction_complete"), @JsonSubTypes.Type(value = SessionTaskCompleteEvent.class, name = "session.task_complete"), @@ -122,6 +123,7 @@ @JsonSubTypes.Type(value = ExitPlanModeCompletedEvent.class, name = "exit_plan_mode.completed"), @JsonSubTypes.Type(value = SessionToolsUpdatedEvent.class, name = "session.tools_updated"), @JsonSubTypes.Type(value = SessionBackgroundTasksChangedEvent.class, name = "session.background_tasks_changed"), + @JsonSubTypes.Type(value = FactoryRunUpdatedEvent.class, name = "factory.run_updated"), @JsonSubTypes.Type(value = SessionSkillsLoadedEvent.class, name = "session.skills_loaded"), @JsonSubTypes.Type(value = SessionCustomAgentsUpdatedEvent.class, name = "session.custom_agents_updated"), @JsonSubTypes.Type(value = SessionMcpServersLoadedEvent.class, name = "session.mcp_servers_loaded"), @@ -167,6 +169,7 @@ public abstract sealed class SessionEvent permits SessionUsageCheckpointEvent, SessionContextChangedEvent, SessionUsageInfoEvent, + SessionContextClearedEvent, SessionCompactionStartEvent, SessionCompactionCompleteEvent, SessionTaskCompleteEvent, @@ -238,6 +241,7 @@ public abstract sealed class SessionEvent permits ExitPlanModeCompletedEvent, SessionToolsUpdatedEvent, SessionBackgroundTasksChangedEvent, + FactoryRunUpdatedEvent, SessionSkillsLoadedEvent, SessionCustomAgentsUpdatedEvent, SessionMcpServersLoadedEvent, diff --git a/java/src/generated/java/com/github/copilot/generated/SessionExtensionsAttachmentsPushedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionExtensionsAttachmentsPushedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionExtensionsAttachmentsPushedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionExtensionsAttachmentsPushedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionExtensionsLoadedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionExtensionsLoadedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionExtensionsLoadedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionExtensionsLoadedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionHandoffEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionHandoffEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionHandoffEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionHandoffEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionIdleEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionIdleEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionIdleEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionIdleEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionInfoEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionInfoEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionInfoEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionInfoEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionLimitsConfig.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionLimitsConfig.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionLimitsConfig.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionLimitsConfig.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionLimitsExhaustedCompletedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionLimitsExhaustedCompletedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionLimitsExhaustedCompletedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionLimitsExhaustedCompletedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionLimitsExhaustedRequestedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionLimitsExhaustedRequestedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionLimitsExhaustedRequestedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionLimitsExhaustedRequestedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionLimitsExhaustedResponse.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionLimitsExhaustedResponse.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionLimitsExhaustedResponse.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionLimitsExhaustedResponse.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionLimitsExhaustedResponseAction.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionLimitsExhaustedResponseAction.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionLimitsExhaustedResponseAction.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionLimitsExhaustedResponseAction.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionManagedSettingsEnforcedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionManagedSettingsEnforcedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionManagedSettingsEnforcedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionManagedSettingsEnforcedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionManagedSettingsResolvedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionManagedSettingsResolvedEvent.java similarity index 66% rename from java/src/generated/java/com/github/copilot/generated/SessionManagedSettingsResolvedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionManagedSettingsResolvedEvent.java index 7cc495269..f935f4462 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionManagedSettingsResolvedEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionManagedSettingsResolvedEvent.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Session event "session.managed_settings_resolved". Enterprise managed-settings resolution: the effective managed settings the session applied and where they came from, so SDK clients can show users what is enterprise-managed and by which authority. Fires whenever managed policy is (re)applied — at session start, on resume, and on account switch. This is an ephemeral live snapshot (delivered to subscribers but not persisted to the session event log), because at session start it resolves before `session.start` is emitted; for a session-independent pull, use the SDK `getManagedSettings()` API, which returns the identical payload. Managed settings have a single authoritative source, so the highest-authority present layer (server > device) wins wholesale; `bypassPermissionsDisabled` is deny-wins across layers. Marked experimental while the managed-settings surface stabilizes. + * Session event "session.managed_settings_resolved". Enterprise managed-settings resolution: the effective managed settings the session applied and which channels contributed, so SDK clients can show users what is enterprise-managed. Fires whenever managed policy is (re)applied — at session start, on resume, and on account switch. This is an ephemeral live snapshot (delivered to subscribers but not persisted to the session event log), because at session start it resolves before `session.start` is emitted. Device values take precedence over server values per ordinary key, while permissions compose restrictively across device, server, and SDK-client layers. The account-scoped `getManagedSettings()` API does not include session-local client injection. Marked experimental while the managed-settings surface stabilizes. * @since 1.0.0 */ @JsonIgnoreProperties(ignoreUnknown = true) @@ -35,16 +35,20 @@ public final class SessionManagedSettingsResolvedEvent extends SessionEvent { @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public record SessionManagedSettingsResolvedEventData( - /** Which channel supplied the effective managed settings (the winning layer), or `none` when no policy is in force */ + /** Channel summary: `server`, `device`, or `client` when exactly one channel contributed; `mixed` when multiple channels contributed; otherwise `none`. Consult the per-channel booleans for exact provenance. */ @JsonProperty("source") ManagedSettingsResolvedSource source, /** Whether the server (account/org) managed-settings layer was present */ @JsonProperty("serverManaged") Boolean serverManaged, - /** Whether the device (MDM/plist/registry/file) managed-settings layer was present */ + /** Whether an actual device MDM/plist/registry/file managed-settings layer was present */ @JsonProperty("deviceManaged") Boolean deviceManaged, + /** Whether a session-local permissions layer injected by the SDK host was present */ + @JsonProperty("clientManaged") Boolean clientManaged, /** Whether managed policy could not be determined (e.g. a failed server fetch) and the session fell back to the fail-closed restriction. When true, restrictions such as disabling bypass-permissions are enforced even though `settings` may be absent. */ @JsonProperty("failClosed") Boolean failClosed, /** Whether enterprise policy disables bypass-permissions ("yolo") mode for this session. Deny-wins across layers, and forced on when `failClosed` is true. */ @JsonProperty("bypassPermissionsDisabled") Boolean bypassPermissionsDisabled, + /** Whether at least two managed sources supplied permission allowlists, so enforcement intersects them and the flattened settings payload omits `permissions.allow`. */ + @JsonProperty("permissionsAllowIntersected") Boolean permissionsAllowIntersected, /** The setting keys under enterprise management in the effective managed settings (e.g. `model`, `enabledPlugins`, `permissions`). Empty when no managed settings are in force. */ @JsonProperty("managedKeys") List managedKeys, /** The effective (resolved) managed settings values, so clients can render exactly what is enforced. Absent when no managed policy is in force. */ diff --git a/java/src/generated/java/com/github/copilot/generated/SessionMcpServerStatusChangedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionMcpServerStatusChangedEvent.java similarity index 97% rename from java/src/generated/java/com/github/copilot/generated/SessionMcpServerStatusChangedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionMcpServerStatusChangedEvent.java index cb15f1d9e..b084652db 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionMcpServerStatusChangedEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionMcpServerStatusChangedEvent.java @@ -36,7 +36,7 @@ public final class SessionMcpServerStatusChangedEvent extends SessionEvent { public record SessionMcpServerStatusChangedEventData( /** Name of the MCP server whose status changed */ @JsonProperty("serverName") String serverName, - /** Connection status: connected, failed, needs-auth, pending, disabled, or not_configured */ + /** Connection status: connected, failed, needs-auth, pending, disabled, stopped, or not_configured */ @JsonProperty("status") McpServerStatus status, /** Error message if the server entered a failed state */ @JsonProperty("error") String error diff --git a/java/src/generated/java/com/github/copilot/generated/SessionMcpServersLoadedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionMcpServersLoadedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionMcpServersLoadedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionMcpServersLoadedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionMode.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionMode.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionMode.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionModeChangedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionModeChangedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionModeChangedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionModeChangedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionModelChangeEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionModelChangeEvent.java similarity index 90% rename from java/src/generated/java/com/github/copilot/generated/SessionModelChangeEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionModelChangeEvent.java index f12b86d08..e53c1594a 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionModelChangeEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionModelChangeEvent.java @@ -52,7 +52,7 @@ public record SessionModelChangeEventData( @JsonProperty("verbosity") Verbosity verbosity, /** Context tier after the model change; null explicitly clears a previously selected tier */ @JsonProperty("contextTier") ContextTier contextTier, - /** Reason the change happened, when not user-initiated. Currently `"rate_limit_auto_switch"` for changes triggered by the auto-mode-switch rate-limit recovery path. UI clients can use this to render contextual copy. */ + /** Reason the change happened, when not user-initiated. `"rate_limit_auto_switch"` for changes triggered by the auto-mode-switch rate-limit recovery path, or `"refusal_fallback"` when the active model declined a request (content refusal) and the runtime switched to the configured refusal-fallback model. UI clients can use this to render contextual copy. */ @JsonProperty("cause") String cause ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/SessionPermissionsChangedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionPermissionsChangedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionPermissionsChangedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionPermissionsChangedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionPlanChangedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionPlanChangedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionPlanChangedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionPlanChangedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionRemoteSteerableChangedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionRemoteSteerableChangedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionRemoteSteerableChangedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionRemoteSteerableChangedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionResumeEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionResumeEvent.java similarity index 84% rename from java/src/generated/java/com/github/copilot/generated/SessionResumeEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionResumeEvent.java index 6efac46f2..a3f39d769 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionResumeEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionResumeEvent.java @@ -57,11 +57,11 @@ public record SessionResumeEventData( @JsonProperty("context") WorkingDirectoryContext context, /** Whether the session was already in use by another client at resume time */ @JsonProperty("alreadyInUse") Boolean alreadyInUse, - /** True when this resume attached to a session that the runtime already had running in-memory (for example, an extension joining a session another client was actively driving). False (or omitted) for cold resumes — the runtime had to reconstitute the session from its persisted event log. */ + /** True when this resume passively joined a session that already had live work running in the runtime - an agent turn, a native queue run, a queued resume continuation, or an in-flight send (for example, an extension joining a session another client was actively driving). False (or omitted) when the session had no live work or when the resume explicitly abandoned pending work, including cold resumes and suspended sessions that remain resident in memory. */ @JsonProperty("sessionWasActive") Boolean sessionWasActive, /** Whether this session supports remote steering via GitHub */ @JsonProperty("remoteSteerable") Boolean remoteSteerable, - /** When true, tool calls and permission requests left in flight by the previous session lifetime remain pending after resume and the agentic loop awaits their results. User sends are queued behind the pending work until all such requests reach a terminal state. When false (the default), any such tool calls and permission requests are immediately marked as interrupted on resume. */ + /** When true, tool calls and permission requests left in flight by the previous session lifetime remain pending after resume and the agentic loop awaits their results. User sends are queued behind the pending work until all such requests reach a terminal state. When false or omitted, pending work is normally marked as interrupted unless the resume passively joined live work owned by another client; sessionWasActive distinguishes that case. */ @JsonProperty("continuePendingWork") Boolean continuePendingWork ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/SessionScheduleCancelledEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionScheduleCancelledEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionScheduleCancelledEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionScheduleCancelledEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionScheduleCreatedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionScheduleCreatedEvent.java similarity index 85% rename from java/src/generated/java/com/github/copilot/generated/SessionScheduleCreatedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionScheduleCreatedEvent.java index 435a8501e..cc0b3b165 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionScheduleCreatedEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionScheduleCreatedEvent.java @@ -51,7 +51,9 @@ public record SessionScheduleCreatedEventData( /** True for a self-paced (`dynamic`) schedule: no fixed cadence; the model arms each next run via the `manage_schedule` `wakeup` action. `nextRunAt` is model-controlled rather than auto-computed. */ @JsonProperty("selfPaced") Boolean selfPaced, /** Optional user-facing label shown in the timeline instead of the actual prompt (e.g. `/skill-name args` when the prompt is a skill invocation expansion) */ - @JsonProperty("displayPrompt") String displayPrompt + @JsonProperty("displayPrompt") String displayPrompt, + /** Who created the schedule (`user` or `model`). Persisted so a resumed session keeps gating non-user schedules from firing skills that opted out of model invocation. Absent on entries created before this field existed; a missing origin fails closed (treated the same as a non-user origin), so such a schedule may not resolve a `disable-model-invocation` skill. */ + @JsonProperty("origin") ScheduleOrigin origin ) { } } diff --git a/java/src/generated/java/com/github/copilot/generated/SessionScheduleRearmedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionScheduleRearmedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionScheduleRearmedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionScheduleRearmedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionSessionLimitsChangedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionSessionLimitsChangedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionSessionLimitsChangedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionSessionLimitsChangedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionShutdownEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionShutdownEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionShutdownEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionShutdownEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionSkillsLoadedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionSkillsLoadedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionSkillsLoadedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionSkillsLoadedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionSnapshotRewindEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionSnapshotRewindEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionSnapshotRewindEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionSnapshotRewindEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionStartEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionStartEvent.java similarity index 96% rename from java/src/generated/java/com/github/copilot/generated/SessionStartEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionStartEvent.java index 4beb487c3..bf8b4e91c 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionStartEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionStartEvent.java @@ -59,6 +59,8 @@ public record SessionStartEventData( @JsonProperty("sessionLimits") SessionLimitsConfig sessionLimits, /** Working directory and git context at session start */ @JsonProperty("context") WorkingDirectoryContext context, + /** Per-session GitHub MCP override persisted for cold resume */ + @JsonProperty("githubMcpToolConfig") GitHubMcpToolConfig gitHubMcpToolConfig, /** Whether the session was already in use by another client at start time */ @JsonProperty("alreadyInUse") Boolean alreadyInUse, /** Whether this session supports remote steering via GitHub */ diff --git a/java/src/generated/java/com/github/copilot/generated/SessionTaskCompleteEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionTaskCompleteEvent.java similarity index 64% rename from java/src/generated/java/com/github/copilot/generated/SessionTaskCompleteEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionTaskCompleteEvent.java index 8933662ba..c44c682b1 100644 --- a/java/src/generated/java/com/github/copilot/generated/SessionTaskCompleteEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SessionTaskCompleteEvent.java @@ -36,8 +36,14 @@ public final class SessionTaskCompleteEvent extends SessionEvent { public record SessionTaskCompleteEventData( /** Summary of the completed task, provided by the agent */ @JsonProperty("summary") String summary, - /** Whether the tool call succeeded. False when validation failed (e.g., invalid arguments) */ - @JsonProperty("success") Boolean success + /** Whether the task was accepted as complete. False when validation failed or completion was rejected or blocked by the reviewer */ + @JsonProperty("success") Boolean success, + /** Semantic completion decision. Absent on legacy events and invalid tool calls */ + @JsonProperty("outcome") TaskCompletionOutcome outcome, + /** Label-safe runtime rationale for the completion decision (e.g. a cancellation or pause/resume downgrade), when one applies. Reviewer-authored rationale is intentionally omitted here because this event has no IFC label channel; the reviewer's findings remain available through its own labeled sub-agent events */ + @JsonProperty("reason") String reason, + /** Active autopilot objective ID evaluated by the completion reviewer */ + @JsonProperty("objectiveId") Long objectiveId ) { } } diff --git a/java/src/generated/java/com/github/copilot/generated/SessionTitleChangedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionTitleChangedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionTitleChangedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionTitleChangedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionTodosChangedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionTodosChangedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionTodosChangedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionTodosChangedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionToolsUpdatedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionToolsUpdatedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionToolsUpdatedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionToolsUpdatedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionTruncationEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionTruncationEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionTruncationEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionTruncationEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionUsageCheckpointEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionUsageCheckpointEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionUsageCheckpointEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionUsageCheckpointEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionUsageInfoEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionUsageInfoEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionUsageInfoEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionUsageInfoEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionWarningEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionWarningEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionWarningEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionWarningEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SessionWorkspaceFileChangedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SessionWorkspaceFileChangedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SessionWorkspaceFileChangedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SessionWorkspaceFileChangedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/ShutdownCodeChanges.java b/java/sdk/src/generated/java/com/github/copilot/generated/ShutdownCodeChanges.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ShutdownCodeChanges.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ShutdownCodeChanges.java diff --git a/java/src/generated/java/com/github/copilot/generated/ShutdownModelMetric.java b/java/sdk/src/generated/java/com/github/copilot/generated/ShutdownModelMetric.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ShutdownModelMetric.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ShutdownModelMetric.java diff --git a/java/src/generated/java/com/github/copilot/generated/ShutdownModelMetricRequests.java b/java/sdk/src/generated/java/com/github/copilot/generated/ShutdownModelMetricRequests.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ShutdownModelMetricRequests.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ShutdownModelMetricRequests.java diff --git a/java/src/generated/java/com/github/copilot/generated/ShutdownModelMetricTokenDetail.java b/java/sdk/src/generated/java/com/github/copilot/generated/ShutdownModelMetricTokenDetail.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ShutdownModelMetricTokenDetail.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ShutdownModelMetricTokenDetail.java diff --git a/java/src/generated/java/com/github/copilot/generated/ShutdownModelMetricUsage.java b/java/sdk/src/generated/java/com/github/copilot/generated/ShutdownModelMetricUsage.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ShutdownModelMetricUsage.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ShutdownModelMetricUsage.java diff --git a/java/src/generated/java/com/github/copilot/generated/ShutdownTokenDetail.java b/java/sdk/src/generated/java/com/github/copilot/generated/ShutdownTokenDetail.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ShutdownTokenDetail.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ShutdownTokenDetail.java diff --git a/java/src/generated/java/com/github/copilot/generated/ShutdownType.java b/java/sdk/src/generated/java/com/github/copilot/generated/ShutdownType.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ShutdownType.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ShutdownType.java diff --git a/java/src/generated/java/com/github/copilot/generated/SkillInvokedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SkillInvokedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SkillInvokedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SkillInvokedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SkillInvokedTrigger.java b/java/sdk/src/generated/java/com/github/copilot/generated/SkillInvokedTrigger.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SkillInvokedTrigger.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SkillInvokedTrigger.java diff --git a/java/src/generated/java/com/github/copilot/generated/SkillSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/SkillSource.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SkillSource.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SkillSource.java diff --git a/java/src/generated/java/com/github/copilot/generated/SkillsLoadedSkill.java b/java/sdk/src/generated/java/com/github/copilot/generated/SkillsLoadedSkill.java similarity index 92% rename from java/src/generated/java/com/github/copilot/generated/SkillsLoadedSkill.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SkillsLoadedSkill.java index d3196c6bb..932d9affe 100644 --- a/java/src/generated/java/com/github/copilot/generated/SkillsLoadedSkill.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SkillsLoadedSkill.java @@ -23,6 +23,8 @@ public record SkillsLoadedSkill( /** Unique identifier for the skill */ @JsonProperty("name") String name, + /** Canonical slash command name used to invoke the skill, without the leading '/' */ + @JsonProperty("commandName") String commandName, /** Description of what the skill does */ @JsonProperty("description") String description, /** Source location type (e.g., project, personal-copilot, plugin, builtin) */ diff --git a/java/src/generated/java/com/github/copilot/generated/SubagentCompletedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SubagentCompletedEvent.java similarity index 85% rename from java/src/generated/java/com/github/copilot/generated/SubagentCompletedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SubagentCompletedEvent.java index f32613579..f7300ddbf 100644 --- a/java/src/generated/java/com/github/copilot/generated/SubagentCompletedEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SubagentCompletedEvent.java @@ -47,7 +47,9 @@ public record SubagentCompletedEventData( /** Total tokens (input + output) consumed by the sub-agent */ @JsonProperty("totalTokens") Long totalTokens, /** Wall-clock duration of the sub-agent execution in milliseconds */ - @JsonProperty("durationMs") Long durationMs + @JsonProperty("durationMs") Long durationMs, + /** Whether the sub-agent was torn down by cancellation - its own abort, or an ancestor being killed - instead of finishing its work. Cancellation is not a failure, so the run still reports completion; this distinguishes a torn-down sub-agent from one that ran to the end. */ + @JsonProperty("cancelled") Boolean cancelled ) { } } diff --git a/java/src/generated/java/com/github/copilot/generated/SubagentDeselectedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SubagentDeselectedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SubagentDeselectedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SubagentDeselectedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SubagentFailedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SubagentFailedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SubagentFailedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SubagentFailedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SubagentSelectedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SubagentSelectedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SubagentSelectedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SubagentSelectedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SubagentStartedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SubagentStartedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SubagentStartedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SubagentStartedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/SystemMessageEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SystemMessageEvent.java similarity index 92% rename from java/src/generated/java/com/github/copilot/generated/SystemMessageEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SystemMessageEvent.java index a6248a7fe..315e9e8bb 100644 --- a/java/src/generated/java/com/github/copilot/generated/SystemMessageEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/SystemMessageEvent.java @@ -36,6 +36,8 @@ public final class SystemMessageEvent extends SessionEvent { public record SystemMessageEventData( /** The system or developer prompt text sent as model input */ @JsonProperty("content") String content, + /** Logical interaction identifier for the model run receiving this prompt */ + @JsonProperty("interactionId") String interactionId, /** Message role: "system" for system prompts, "developer" for developer-injected instructions */ @JsonProperty("role") SystemMessageRole role, /** Optional name identifier for the message source */ diff --git a/java/src/generated/java/com/github/copilot/generated/SystemMessageMetadata.java b/java/sdk/src/generated/java/com/github/copilot/generated/SystemMessageMetadata.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SystemMessageMetadata.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SystemMessageMetadata.java diff --git a/java/src/generated/java/com/github/copilot/generated/SystemMessageRole.java b/java/sdk/src/generated/java/com/github/copilot/generated/SystemMessageRole.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SystemMessageRole.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SystemMessageRole.java diff --git a/java/src/generated/java/com/github/copilot/generated/SystemNotificationEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/SystemNotificationEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/SystemNotificationEvent.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/TaskCompletionOutcome.java b/java/sdk/src/generated/java/com/github/copilot/generated/TaskCompletionOutcome.java new file mode 100644 index 000000000..827cf2b77 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/TaskCompletionOutcome.java @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: session-events.schema.json + +package com.github.copilot.generated; + +import javax.annotation.processing.Generated; + +/** + * Semantic result of evaluating a task completion request + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum TaskCompletionOutcome { + /** The {@code completed} variant. */ + COMPLETED("completed"), + /** The {@code continue} variant. */ + CONTINUE("continue"), + /** The {@code blocked} variant. */ + BLOCKED("blocked"); + + private final String value; + TaskCompletionOutcome(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static TaskCompletionOutcome fromValue(String value) { + for (TaskCompletionOutcome v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown TaskCompletionOutcome value: " + value); + } +} diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteError.java b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteError.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteError.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteError.java diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteEvent.java similarity index 98% rename from java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteEvent.java index 99d138b1e..a265b5305 100644 --- a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteEvent.java @@ -45,6 +45,7 @@ public record ToolExecutionCompleteEventData( @JsonProperty("mcpMeta") Object mcpMeta, /** CAPI interaction ID for correlating this tool execution with upstream telemetry */ @JsonProperty("interactionId") String interactionId, + @JsonProperty("rte") Boolean rte, /** Whether this tool call was explicitly requested by the user rather than the assistant */ @JsonProperty("isUserRequested") Boolean isUserRequested, /** Tool execution result on success */ diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteToolDescription.java b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteToolDescription.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteToolDescription.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteToolDescription.java diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteToolDescriptionMeta.java b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteToolDescriptionMeta.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteToolDescriptionMeta.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteToolDescriptionMeta.java diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteToolDescriptionMetaUI.java b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteToolDescriptionMetaUI.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteToolDescriptionMetaUI.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteToolDescriptionMetaUI.java diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteToolDescriptionMetaUIVisibility.java b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteToolDescriptionMetaUIVisibility.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteToolDescriptionMetaUIVisibility.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteToolDescriptionMetaUIVisibility.java diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResource.java b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResource.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResource.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResource.java diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMeta.java b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMeta.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMeta.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMeta.java diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUI.java b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUI.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUI.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUI.java diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUICsp.java b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUICsp.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUICsp.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUICsp.java diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissions.java b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissions.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissions.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissions.java diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissionsCamera.java b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissionsCamera.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissionsCamera.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissionsCamera.java diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissionsClipboardWrite.java b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissionsClipboardWrite.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissionsClipboardWrite.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissionsClipboardWrite.java diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissionsGeolocation.java b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissionsGeolocation.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissionsGeolocation.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissionsGeolocation.java diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone.java b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionCompleteUIResourceMetaUIPermissionsMicrophone.java diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionPartialResultEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionPartialResultEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ToolExecutionPartialResultEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionPartialResultEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionProgressEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionProgressEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ToolExecutionProgressEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionProgressEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionStartEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionStartEvent.java similarity index 98% rename from java/src/generated/java/com/github/copilot/generated/ToolExecutionStartEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionStartEvent.java index b119a3eb0..782e93931 100644 --- a/java/src/generated/java/com/github/copilot/generated/ToolExecutionStartEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionStartEvent.java @@ -44,6 +44,7 @@ public record ToolExecutionStartEventData( @JsonProperty("shellToolInfo") ToolExecutionStartShellToolInfo shellToolInfo, /** Model identifier that generated this tool call */ @JsonProperty("model") String model, + @JsonProperty("rte") Boolean rte, /** Name of the MCP server hosting this tool, when the tool is an MCP tool */ @JsonProperty("mcpServerName") String mcpServerName, /** Original tool name on the MCP server, when the tool is an MCP tool */ diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionStartShellToolInfo.java b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionStartShellToolInfo.java similarity index 77% rename from java/src/generated/java/com/github/copilot/generated/ToolExecutionStartShellToolInfo.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionStartShellToolInfo.java index 88ac787fc..967dab4c3 100644 --- a/java/src/generated/java/com/github/copilot/generated/ToolExecutionStartShellToolInfo.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionStartShellToolInfo.java @@ -25,6 +25,8 @@ public record ToolExecutionStartShellToolInfo( /** File paths the command may read or write, derived from the command at start time. Produced by the same shell-aware extractor as PermissionRequestShell.possiblePaths, so it is present even when the command is auto-approved and no permission request fires. */ @JsonProperty("possiblePaths") List possiblePaths, /** Whether the command includes a file write redirection (e.g., > or >>). */ - @JsonProperty("hasWriteFileRedirection") Boolean hasWriteFileRedirection + @JsonProperty("hasWriteFileRedirection") Boolean hasWriteFileRedirection, + /** The command with a redundant leading `cd` into the working directory removed, present only when there was one to remove. Computed with the same routine the shell driver applies before spawning, so a surface that renders this shows the text that actually runs. Consumers that display it should keep the original tool arguments available on demand. */ + @JsonProperty("displayCommand") String displayCommand ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionStartToolDescription.java b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionStartToolDescription.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ToolExecutionStartToolDescription.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionStartToolDescription.java diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionStartToolDescriptionMeta.java b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionStartToolDescriptionMeta.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ToolExecutionStartToolDescriptionMeta.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionStartToolDescriptionMeta.java diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionStartToolDescriptionMetaUI.java b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionStartToolDescriptionMetaUI.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ToolExecutionStartToolDescriptionMetaUI.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionStartToolDescriptionMetaUI.java diff --git a/java/src/generated/java/com/github/copilot/generated/ToolExecutionStartToolDescriptionMetaUIVisibility.java b/java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionStartToolDescriptionMetaUIVisibility.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ToolExecutionStartToolDescriptionMetaUIVisibility.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ToolExecutionStartToolDescriptionMetaUIVisibility.java diff --git a/java/src/generated/java/com/github/copilot/generated/ToolSearchActivatedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/ToolSearchActivatedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ToolSearchActivatedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ToolSearchActivatedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/ToolUserRequestedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/ToolUserRequestedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/ToolUserRequestedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/ToolUserRequestedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/UnknownSessionEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/UnknownSessionEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/UnknownSessionEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/UnknownSessionEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/UsageCheckpointModelCacheState.java b/java/sdk/src/generated/java/com/github/copilot/generated/UsageCheckpointModelCacheState.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/UsageCheckpointModelCacheState.java rename to java/sdk/src/generated/java/com/github/copilot/generated/UsageCheckpointModelCacheState.java diff --git a/java/src/generated/java/com/github/copilot/generated/UserInputCompletedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/UserInputCompletedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/UserInputCompletedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/UserInputCompletedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/UserInputRequestedEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/UserInputRequestedEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/UserInputRequestedEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/UserInputRequestedEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/UserMessageAgentMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/UserMessageAgentMode.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/UserMessageAgentMode.java rename to java/sdk/src/generated/java/com/github/copilot/generated/UserMessageAgentMode.java diff --git a/java/src/generated/java/com/github/copilot/generated/UserMessageDelivery.java b/java/sdk/src/generated/java/com/github/copilot/generated/UserMessageDelivery.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/UserMessageDelivery.java rename to java/sdk/src/generated/java/com/github/copilot/generated/UserMessageDelivery.java diff --git a/java/src/generated/java/com/github/copilot/generated/UserMessageEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/UserMessageEvent.java similarity index 96% rename from java/src/generated/java/com/github/copilot/generated/UserMessageEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/UserMessageEvent.java index 5af484243..bc579968b 100644 --- a/java/src/generated/java/com/github/copilot/generated/UserMessageEvent.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/UserMessageEvent.java @@ -45,7 +45,7 @@ public record UserMessageEventData( @JsonProperty("supportedNativeDocumentMimeTypes") List supportedNativeDocumentMimeTypes, /** Path-backed native document attachments that stayed on the tagged_files path flow because native upload could not read them or would exceed the request size limit */ @JsonProperty("nativeDocumentPathFallbackPaths") List nativeDocumentPathFallbackPaths, - /** Origin of this message, used for timeline filtering (e.g., "skill-pdf" for skill-injected messages that should be hidden from the user) */ + /** Origin of this message, used for timeline filtering and attribution (e.g., `skill-pdf` for hidden skill injection or `agent-` for an inter-agent prompt) */ @JsonProperty("source") String source, /** How this message was delivered to the agentic loop relative to loop state (idle-start vs. steering/queued while busy). The timing axis; combine with `source` (origin) for the full picture. Used for telemetry attribution. */ @JsonProperty("delivery") UserMessageDelivery delivery, diff --git a/java/src/generated/java/com/github/copilot/generated/Verbosity.java b/java/sdk/src/generated/java/com/github/copilot/generated/Verbosity.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/Verbosity.java rename to java/sdk/src/generated/java/com/github/copilot/generated/Verbosity.java diff --git a/java/src/generated/java/com/github/copilot/generated/WorkingDirectoryContext.java b/java/sdk/src/generated/java/com/github/copilot/generated/WorkingDirectoryContext.java similarity index 80% rename from java/src/generated/java/com/github/copilot/generated/WorkingDirectoryContext.java rename to java/sdk/src/generated/java/com/github/copilot/generated/WorkingDirectoryContext.java index 813cd5e02..e22fc461d 100644 --- a/java/src/generated/java/com/github/copilot/generated/WorkingDirectoryContext.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/WorkingDirectoryContext.java @@ -36,6 +36,8 @@ public record WorkingDirectoryContext( /** Head commit of current git branch at session start time */ @JsonProperty("headCommit") String headCommit, /** Base commit of current git branch at session start time */ - @JsonProperty("baseCommit") String baseCommit + @JsonProperty("baseCommit") String baseCommit, + /** Set on the immediate preliminary event of a working-directory change, before the git context is resolved. A settled follow-up event (enriched with git context, or cwd-only for a non-repository) is always emitted afterward, so observers may defer to it. Absent on standalone/final events (e.g. relay context changes). */ + @JsonProperty("pendingGitContext") Boolean pendingGitContext ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/WorkingDirectoryContextHostType.java b/java/sdk/src/generated/java/com/github/copilot/generated/WorkingDirectoryContextHostType.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/WorkingDirectoryContextHostType.java rename to java/sdk/src/generated/java/com/github/copilot/generated/WorkingDirectoryContextHostType.java diff --git a/java/src/generated/java/com/github/copilot/generated/WorkspaceFileChangedOperation.java b/java/sdk/src/generated/java/com/github/copilot/generated/WorkspaceFileChangedOperation.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/WorkspaceFileChangedOperation.java rename to java/sdk/src/generated/java/com/github/copilot/generated/WorkspaceFileChangedOperation.java diff --git a/java/src/generated/java/com/github/copilot/generated/package-info.java b/java/sdk/src/generated/java/com/github/copilot/generated/package-info.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/package-info.java rename to java/sdk/src/generated/java/com/github/copilot/generated/package-info.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AbortReason.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AbortReason.java similarity index 90% rename from java/src/generated/java/com/github/copilot/generated/rpc/AbortReason.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AbortReason.java index a48640077..8d26959e8 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/AbortReason.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AbortReason.java @@ -21,7 +21,9 @@ public enum AbortReason { /** The {@code remote_command} variant. */ REMOTE_COMMAND("remote_command"), /** The {@code user_abort} variant. */ - USER_ABORT("user_abort"); + USER_ABORT("user_abort"), + /** The {@code autopilot_credit_limit} variant. */ + AUTOPILOT_CREDIT_LIMIT("autopilot_credit_limit"); private final String value; AbortReason(String value) { this.value = value; } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AccountAllUsers.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountAllUsers.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/AccountAllUsers.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountAllUsers.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AccountGetCurrentAuthResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountGetCurrentAuthResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/AccountGetCurrentAuthResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountGetCurrentAuthResult.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountGetQuotaParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountGetQuotaParams.java new file mode 100644 index 000000000..cea523551 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountGetQuotaParams.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Request parameters for the {@code account.getQuota} RPC method. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record AccountGetQuotaParams( + /** GitHub token for per-user quota lookup. When provided, resolves this token to determine the user's quota instead of using the global auth. */ + @JsonProperty("gitHubToken") String gitHubToken +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AccountGetQuotaResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountGetQuotaResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/AccountGetQuotaResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountGetQuotaResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AccountLoginParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountLoginParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/AccountLoginParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountLoginParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AccountLoginResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountLoginResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/AccountLoginResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountLoginResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AccountLogoutParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountLogoutParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/AccountLogoutParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountLogoutParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AccountLogoutResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountLogoutResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/AccountLogoutResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountLogoutResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AccountQuotaSnapshot.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountQuotaSnapshot.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/AccountQuotaSnapshot.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AccountQuotaSnapshot.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AdaptiveThinkingSupport.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AdaptiveThinkingSupport.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/AdaptiveThinkingSupport.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AdaptiveThinkingSupport.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AgentDiscoveryPath.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentDiscoveryPath.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/AgentDiscoveryPath.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentDiscoveryPath.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AgentDiscoveryPathScope.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentDiscoveryPathScope.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/AgentDiscoveryPathScope.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentDiscoveryPathScope.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AgentInfo.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentInfo.java similarity index 78% rename from java/src/generated/java/com/github/copilot/generated/rpc/AgentInfo.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentInfo.java index 29813e30a..f239c82e6 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/AgentInfo.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentInfo.java @@ -15,7 +15,7 @@ import javax.annotation.processing.Generated; /** - * Custom agent metadata, including identifiers, display details, source, tools, model, MCP servers, skills, and file path. + * Agent metadata, including identifiers, display details, source, tools, model, MCP servers, skills, and file path. * * @since 1.0.0 */ @@ -23,7 +23,7 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record AgentInfo( - /** Unique identifier of the custom agent */ + /** Name of the agent. Use `id` as the stable selection identifier. */ @JsonProperty("name") String name, /** Human-readable display name */ @JsonProperty("displayName") String displayName, @@ -39,11 +39,13 @@ public record AgentInfo( @JsonProperty("userInvocable") Boolean userInvocable, /** Allowed tool names for this agent. Empty array means none; omitted means inherit defaults. */ @JsonProperty("tools") List tools, - /** Preferred model id for this agent. When omitted, inherits the outer agent's model. */ + /** Authored preferred model id for this agent. Runtime model selection may choose a different model; omitted means no authored preference. */ @JsonProperty("model") String model, /** MCP server configurations attached to this agent, keyed by server name. Server config shape mirrors the MCP `mcpServers` schema. */ @JsonProperty("mcpServers") Map mcpServers, /** Skill names preloaded into this agent's context. Omitted means none. */ - @JsonProperty("skills") List skills + @JsonProperty("skills") List skills, + /** Authored base prompt for the agent. Runtime prompt assembly may add dynamic context at invocation time. Omitted from `session.agent.list` unless `includePrompt` is true. */ + @JsonProperty("prompt") String prompt ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AgentInfoSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentInfoSource.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/AgentInfoSource.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentInfoSource.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AgentRegistryLiveTargetEntry.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentRegistryLiveTargetEntry.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/AgentRegistryLiveTargetEntry.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentRegistryLiveTargetEntry.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AgentRegistryLiveTargetEntryAttentionKind.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentRegistryLiveTargetEntryAttentionKind.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/AgentRegistryLiveTargetEntryAttentionKind.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentRegistryLiveTargetEntryAttentionKind.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AgentRegistryLiveTargetEntryKind.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentRegistryLiveTargetEntryKind.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/AgentRegistryLiveTargetEntryKind.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentRegistryLiveTargetEntryKind.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AgentRegistryLiveTargetEntryLastTerminalEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentRegistryLiveTargetEntryLastTerminalEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/AgentRegistryLiveTargetEntryLastTerminalEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentRegistryLiveTargetEntryLastTerminalEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AgentRegistryLiveTargetEntryStatus.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentRegistryLiveTargetEntryStatus.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/AgentRegistryLiveTargetEntryStatus.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentRegistryLiveTargetEntryStatus.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AgentRegistryLogCapture.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentRegistryLogCapture.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/AgentRegistryLogCapture.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentRegistryLogCapture.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AgentRegistryLogCaptureOpenErrorReason.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentRegistryLogCaptureOpenErrorReason.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/AgentRegistryLogCaptureOpenErrorReason.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentRegistryLogCaptureOpenErrorReason.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnError.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnError.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnError.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnError.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnPermissionMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnPermissionMode.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnPermissionMode.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnPermissionMode.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnRegistryTimeout.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnRegistryTimeout.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnRegistryTimeout.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnRegistryTimeout.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnSpawned.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnSpawned.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnSpawned.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnSpawned.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnValidationError.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnValidationError.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnValidationError.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnValidationError.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnValidationErrorField.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnValidationErrorField.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnValidationErrorField.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnValidationErrorField.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnValidationErrorReason.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnValidationErrorReason.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnValidationErrorReason.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentRegistrySpawnValidationErrorReason.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AgentsDiscoverParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentsDiscoverParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/AgentsDiscoverParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentsDiscoverParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AgentsDiscoverResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentsDiscoverResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/AgentsDiscoverResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentsDiscoverResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AgentsGetDiscoveryPathsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentsGetDiscoveryPathsParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/AgentsGetDiscoveryPathsParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentsGetDiscoveryPathsParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AgentsGetDiscoveryPathsResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentsGetDiscoveryPathsResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/AgentsGetDiscoveryPathsResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AgentsGetDiscoveryPathsResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/AuthInfoType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/AuthInfoType.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/AuthInfoType.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/AuthInfoType.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/BuiltInModelCatalogEntry.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/BuiltInModelCatalogEntry.java new file mode 100644 index 000000000..679278356 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/BuiltInModelCatalogEntry.java @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * A well-known model in the runtime's built-in catalog. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record BuiltInModelCatalogEntry( + /** Well-known runtime model ID suitable for `ProviderConfig.modelId` or `ProviderModelConfig.modelId`. This is not necessarily the provider-facing deployment or model name and does not indicate CAPI entitlement or provider availability. */ + @JsonProperty("id") String id +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/CanvasAction.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CanvasAction.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/CanvasAction.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/CanvasAction.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/CanvasActionInvokeParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CanvasActionInvokeParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/CanvasActionInvokeParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/CanvasActionInvokeParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/CanvasCloseParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CanvasCloseParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/CanvasCloseParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/CanvasCloseParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/CanvasHostContext.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CanvasHostContext.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/CanvasHostContext.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/CanvasHostContext.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/CanvasHostContextCapabilities.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CanvasHostContextCapabilities.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/CanvasHostContextCapabilities.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/CanvasHostContextCapabilities.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/CanvasOpenParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CanvasOpenParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/CanvasOpenParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/CanvasOpenParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/CanvasOpenResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CanvasOpenResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/CanvasOpenResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/CanvasOpenResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/CanvasSessionContext.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CanvasSessionContext.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/CanvasSessionContext.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/CanvasSessionContext.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/CapiSessionOptions.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CapiSessionOptions.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/CapiSessionOptions.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/CapiSessionOptions.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/CommandsListResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CommandsListResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/CommandsListResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/CommandsListResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ConnectParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ConnectParams.java similarity index 73% rename from java/src/generated/java/com/github/copilot/generated/rpc/ConnectParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ConnectParams.java index 491dadad3..19172cb1b 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/ConnectParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ConnectParams.java @@ -26,7 +26,7 @@ public record ConnectParams( /** Connection token; required when the server was started with COPILOT_CONNECTION_TOKEN */ @JsonProperty("token") String token, - /** Opt this connection in to GitHub telemetry forwarding for its lifetime. When set, the runtime forwards every internal telemetry event it emits — across all sessions, plus sessionless events — to this connection over the `gitHubTelemetry.event` notification, in addition to the runtime's normal GitHub/CTS emission (dual-write). Intended for first-party hosts that re-emit the events into their own telemetry stores. Both unrestricted and restricted events are forwarded, each tagged with a `restricted` discriminator; a backstop drops restricted events when restricted telemetry is disabled. */ + /** Opt this connection in to GitHub telemetry forwarding for its lifetime. When set, the runtime forwards every internal telemetry event it emits — across all sessions, plus sessionless events — to this connection over the `gitHubTelemetry.event` notification. Regular events are also written to the runtime's normal GitHub/CTS path (dual-write); host-only compatibility events are forward-only and intentionally skip that path. Intended for first-party hosts that re-emit the events into their own telemetry stores. Both unrestricted and restricted events are forwarded, each tagged with a `restricted` discriminator; a backstop drops restricted events when restricted telemetry is disabled — using the process-global gate for ordinary events and an explicit session-scoped decision for host-only events. */ @JsonProperty("enableGitHubTelemetryForwarding") Boolean enableGitHubTelemetryForwarding ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ConnectResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ConnectResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ConnectResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ConnectResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ConnectedRemoteSessionMetadata.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ConnectedRemoteSessionMetadata.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ConnectedRemoteSessionMetadata.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ConnectedRemoteSessionMetadata.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ConnectedRemoteSessionMetadataKind.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ConnectedRemoteSessionMetadataKind.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ConnectedRemoteSessionMetadataKind.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ConnectedRemoteSessionMetadataKind.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ConnectedRemoteSessionMetadataRepository.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ConnectedRemoteSessionMetadataRepository.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ConnectedRemoteSessionMetadataRepository.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ConnectedRemoteSessionMetadataRepository.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ContentExclusionPathCheck.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ContentExclusionPathCheck.java new file mode 100644 index 000000000..ba48326c2 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ContentExclusionPathCheck.java @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Content-exclusion decision for one requested path. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record ContentExclusionPathCheck( + /** The path supplied by the caller. */ + @JsonProperty("path") String path, + /** Whether the session's complete content-exclusion policy excludes the path. */ + @JsonProperty("excluded") Boolean excluded +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ContextHeaviestMessage.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ContextHeaviestMessage.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ContextHeaviestMessage.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ContextHeaviestMessage.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ContextTier.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ContextTier.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ContextTier.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ContextTier.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/CurrentToolMetadata.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/CurrentToolMetadata.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/CurrentToolMetadata.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/CurrentToolMetadata.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsCollectedEntry.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsCollectedEntry.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsCollectedEntry.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsCollectedEntry.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsEntry.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsEntry.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsEntry.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsEntry.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsEntryKind.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsEntryKind.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsEntryKind.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsEntryKind.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsInclude.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsInclude.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsInclude.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsInclude.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsRedaction.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsRedaction.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsRedaction.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsRedaction.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsResultKind.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsResultKind.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsResultKind.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsResultKind.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsSkippedEntry.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsSkippedEntry.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsSkippedEntry.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsSkippedEntry.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsSource.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsSource.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/DebugCollectLogsSource.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DisableBypassPermissionsMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DisableBypassPermissionsMode.java new file mode 100644 index 000000000..1e6b1e7db --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DisableBypassPermissionsMode.java @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum DisableBypassPermissionsMode { + /** The {@code disable} variant. */ + DISABLE("disable"); + + private final String value; + DisableBypassPermissionsMode(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static DisableBypassPermissionsMode fromValue(String value) { + for (DisableBypassPermissionsMode v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown DisableBypassPermissionsMode value: " + value); + } +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/DiscoveredCanvas.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DiscoveredCanvas.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/DiscoveredCanvas.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/DiscoveredCanvas.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DiscoveredExtension.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DiscoveredExtension.java new file mode 100644 index 000000000..7bb2531fe --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DiscoveredExtension.java @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Discovered extension metadata and persistent enablement state. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record DiscoveredExtension( + /** Source-qualified ID accepted by both server and session extension enablement methods */ + @JsonProperty("id") String id, + /** Human-readable extension name */ + @JsonProperty("name") String name, + /** Absolute path to the extension entry module, suitable for revealing it in a file manager */ + @JsonProperty("path") String path, + /** Discovery source */ + @JsonProperty("source") DiscoveredExtensionSource source, + /** Whether this extension's persistent per-ID preference is enabled */ + @JsonProperty("enabled") Boolean enabled, + /** Containing plugin metadata for plugin-contributed extensions */ + @JsonProperty("plugin") DiscoveredExtensionPlugin plugin +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DiscoveredExtensionMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DiscoveredExtensionMode.java new file mode 100644 index 000000000..23bc32778 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DiscoveredExtensionMode.java @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * Effective extension loading and agent-management mode + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum DiscoveredExtensionMode { + /** The {@code disabled} variant. */ + DISABLED("disabled"), + /** The {@code load_only} variant. */ + LOAD_ONLY("load_only"), + /** The {@code load_and_augment} variant. */ + LOAD_AND_AUGMENT("load_and_augment"); + + private final String value; + DiscoveredExtensionMode(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static DiscoveredExtensionMode fromValue(String value) { + for (DiscoveredExtensionMode v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown DiscoveredExtensionMode value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DiscoveredExtensionPlugin.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DiscoveredExtensionPlugin.java new file mode 100644 index 000000000..8df0018ef --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DiscoveredExtensionPlugin.java @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Installed plugin that contributes a discovered extension. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record DiscoveredExtensionPlugin( + /** Installed plugin name */ + @JsonProperty("name") String name +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DiscoveredExtensionSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DiscoveredExtensionSource.java new file mode 100644 index 000000000..c38225167 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DiscoveredExtensionSource.java @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * Persisted extension discovery source + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum DiscoveredExtensionSource { + /** The {@code user} variant. */ + USER("user"), + /** The {@code plugin} variant. */ + PLUGIN("plugin"); + + private final String value; + DiscoveredExtensionSource(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static DiscoveredExtensionSource fromValue(String value) { + for (DiscoveredExtensionSource v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown DiscoveredExtensionSource value: " + value); + } +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/DiscoveredMcpServer.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DiscoveredMcpServer.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/DiscoveredMcpServer.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/DiscoveredMcpServer.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/DiscoveredMcpServerType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/DiscoveredMcpServerType.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/DiscoveredMcpServerType.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/DiscoveredMcpServerType.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/EventsAgentScope.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/EventsAgentScope.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/EventsAgentScope.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/EventsAgentScope.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/EventsCursorStatus.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/EventsCursorStatus.java similarity index 77% rename from java/src/generated/java/com/github/copilot/generated/rpc/EventsCursorStatus.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/EventsCursorStatus.java index 31c1fcab0..20f37bdfa 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/EventsCursorStatus.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/EventsCursorStatus.java @@ -10,7 +10,7 @@ import javax.annotation.processing.Generated; /** - * Cursor status: 'ok' means the cursor was applied successfully; 'expired' means the cursor referred to an event that no longer exists in history (e.g. truncated or compacted away) and the read started from the beginning of the remaining history. + * Cursor status: 'ok' means the cursor was applied successfully; 'expired' means the cursor referred to an event that no longer exists in history (e.g. truncated or compacted away) and the read fell back to a boundary of the remaining history (the beginning for a forward read, the tail for a backward read). The fallback page is a fresh boundary snapshot, not a continuation of the requested cursor, so it may overlap already-rendered events; on 'expired' a consumer should reset/rebase its pagination state (or deduplicate by event id) before continuing from the returned cursor. * * @since 1.0.0 */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/EventsReadDirection.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/EventsReadDirection.java new file mode 100644 index 000000000..1df0ac8f7 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/EventsReadDirection.java @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * Direction to page through the session's persisted event history. 'forward' pages from the cursor toward newer events; 'backward' returns the newest window first (tail-first) and pages toward older events. Events within a returned batch are always chronological (oldest-to-newest), even for a backward read. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum EventsReadDirection { + /** The {@code forward} variant. */ + FORWARD("forward"), + /** The {@code backward} variant. */ + BACKWARD("backward"); + + private final String value; + EventsReadDirection(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static EventsReadDirection fromValue(String value) { + for (EventsReadDirection v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown EventsReadDirection value: " + value); + } +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/Extension.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/Extension.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/Extension.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/Extension.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ExtensionLaunchProfile.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ExtensionLaunchProfile.java new file mode 100644 index 000000000..e7590c7f9 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ExtensionLaunchProfile.java @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import java.util.Map; +import javax.annotation.processing.Generated; + +/** + * Opaque integrator-owned process launch profile for one extension entrypoint. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record ExtensionLaunchProfile( + /** Executable used to launch the extension entrypoint. */ + @JsonProperty("executable") String executable, + /** Opaque integrator-defined arguments passed to the executable. The runtime does not append the extension entrypoint. */ + @JsonProperty("args") List args, + /** Opaque integrator-defined environment variables. Runtime-owned COPILOT_SDK_PATH, SESSION_ID, and COPILOT_EXTENSION_PARENT_PID values take precedence. */ + @JsonProperty("env") Map env +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ExtensionLaunchProviderResolveRequest.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ExtensionLaunchProviderResolveRequest.java new file mode 100644 index 000000000..7b520f906 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ExtensionLaunchProviderResolveRequest.java @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * A discovered extension entrypoint that the registered integrator may classify and resolve to an opaque launch profile. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record ExtensionLaunchProviderResolveRequest( + /** Source-qualified extension identifier. */ + @JsonProperty("id") String id, + /** Human-readable extension name. */ + @JsonProperty("name") String name, + /** Absolute path to the discovered extension entrypoint. */ + @JsonProperty("modulePath") String modulePath, + /** Discovery source for the extension entrypoint. */ + @JsonProperty("source") ExtensionSource source +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ExtensionLaunchProviderResolveResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ExtensionLaunchProviderResolveResult.java new file mode 100644 index 000000000..8a43ad4af --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ExtensionLaunchProviderResolveResult.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * The launch profile for a supported entrypoint. Omit launch when the provider does not support the entrypoint. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record ExtensionLaunchProviderResolveResult( + /** Opaque launch profile, omitted when this provider does not support the entrypoint. */ + @JsonProperty("launch") ExtensionLaunchProfile launch +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ExtensionSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ExtensionSource.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ExtensionSource.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ExtensionSource.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ExtensionStatus.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ExtensionStatus.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ExtensionStatus.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ExtensionStatus.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ExtensionsDisableParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ExtensionsDisableParams.java new file mode 100644 index 000000000..dc4ef9d6c --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ExtensionsDisableParams.java @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * Source-qualified extension identifiers to persistently disable for future sessions. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record ExtensionsDisableParams( + /** Source-qualified user or plugin extension IDs to disable */ + @JsonProperty("ids") List ids +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ExtensionsDiscoverResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ExtensionsDiscoverResult.java new file mode 100644 index 000000000..fa319d7fe --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ExtensionsDiscoverResult.java @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * Extensions discovered from persisted Copilot home state and their effective loading mode. Launch-scoped additional plugins are not included. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record ExtensionsDiscoverResult( + /** Discovered user and enabled installed-plugin extensions from persisted Copilot home state */ + @JsonProperty("extensions") List extensions, + /** Effective extension loading mode. Defaults to load_and_augment when unset. */ + @JsonProperty("mode") DiscoveredExtensionMode mode +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ExtensionsEnableParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ExtensionsEnableParams.java new file mode 100644 index 000000000..2e4351d3d --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ExtensionsEnableParams.java @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * Source-qualified extension identifiers to persistently enable for future sessions. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record ExtensionsEnableParams( + /** Source-qualified user or plugin extension IDs to enable */ + @JsonProperty("ids") List ids +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/FactoryAbortParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryAbortParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/FactoryAbortParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryAbortParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/FactoryAgentOptions.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryAgentOptions.java similarity index 69% rename from java/src/generated/java/com/github/copilot/generated/rpc/FactoryAgentOptions.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryAgentOptions.java index 675e715e8..9910d4f7f 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/FactoryAgentOptions.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryAgentOptions.java @@ -26,6 +26,12 @@ public record FactoryAgentOptions( /** Optional JSON Schema for structured agent output. */ @JsonProperty("schema") Object schema, /** Optional model identifier for the subagent. */ - @JsonProperty("model") String model + @JsonProperty("model") String model, + /** Optional reasoning effort for the subagent. This field is accepted but not yet honored. */ + @JsonProperty("reasoningEffort") String reasoningEffort, + /** Optional context tier for the subagent. This field is accepted but not yet honored. */ + @JsonProperty("contextTier") ContextTier contextTier, + /** Optional custom agent name for the subagent. This field is accepted but not yet honored. */ + @JsonProperty("agent") String agent ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryAgentSummary.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryAgentSummary.java new file mode 100644 index 000000000..af20d8e81 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryAgentSummary.java @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Prompt-safe durable identity and live status for a direct factory agent. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record FactoryAgentSummary( + @JsonProperty("agentId") String agentId, + @JsonProperty("toolCallId") String toolCallId, + @JsonProperty("runId") String runId, + @JsonProperty("phaseId") String phaseId, + @JsonProperty("label") String label, + @JsonProperty("agentType") String agentType, + @JsonProperty("status") String status, + @JsonProperty("requestedModel") String requestedModel, + @JsonProperty("resolvedModel") String resolvedModel, + @JsonProperty("startedAt") Long startedAt, + @JsonProperty("completedAt") Long completedAt, + @JsonProperty("activeMs") Long activeMs, + @JsonProperty("activity") String activity +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryCurrentPhase.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryCurrentPhase.java new file mode 100644 index 000000000..6a8de8e82 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryCurrentPhase.java @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Current factory phase identity. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record FactoryCurrentPhase( + @JsonProperty("id") String id, + @JsonProperty("ordinal") Long ordinal +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/FactoryRunLimits.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryDeclaredLimits.java similarity index 72% rename from java/src/generated/java/com/github/copilot/generated/rpc/FactoryRunLimits.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryDeclaredLimits.java index ede598b80..21f74646f 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/FactoryRunLimits.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryDeclaredLimits.java @@ -13,19 +13,17 @@ import javax.annotation.processing.Generated; /** - * Wire-only per-invocation factory resource ceiling overrides. + * Declared or approved factory resource ceilings. * * @since 1.0.0 */ @javax.annotation.processing.Generated("copilot-sdk-codegen") @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) -public record FactoryRunLimits( - /** Maximum number of factory subagents that may run concurrently. */ +public record FactoryDeclaredLimits( @JsonProperty("maxConcurrentSubagents") Long maxConcurrentSubagents, - /** Maximum total number of factory subagents that may be admitted. */ @JsonProperty("maxTotalSubagents") Long maxTotalSubagents, - /** Factory active-run timeout in milliseconds. */ - @JsonProperty("timeout") Double timeout + @JsonProperty("timeoutSeconds") Double timeoutSeconds, + @JsonProperty("maxAiCredits") Double maxAiCredits ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/FactoryExecuteParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryExecuteParams.java similarity index 91% rename from java/src/generated/java/com/github/copilot/generated/rpc/FactoryExecuteParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryExecuteParams.java index e6f453814..6834dd4b1 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/FactoryExecuteParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryExecuteParams.java @@ -30,6 +30,8 @@ public record FactoryExecuteParams( @JsonProperty("name") String name, /** Factory run identifier. */ @JsonProperty("runId") String runId, + /** Opaque token identifying this factory execution attempt. */ + @JsonProperty("executionToken") String executionToken, /** Factory input value. */ @JsonProperty("args") Object args ) { diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/FactoryExecuteResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryExecuteResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/FactoryExecuteResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryExecuteResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/FactoryLogLine.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryLogLine.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/FactoryLogLine.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryLogLine.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/FactoryLogLineKind.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryLogLineKind.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/FactoryLogLineKind.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryLogLineKind.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryPhaseObservation.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryPhaseObservation.java new file mode 100644 index 000000000..aa04ef5ba --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryPhaseObservation.java @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Durable lifecycle and timing for one factory phase. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record FactoryPhaseObservation( + @JsonProperty("id") String id, + @JsonProperty("ordinal") Long ordinal, + @JsonProperty("title") String title, + @JsonProperty("detail") String detail, + @JsonProperty("status") FactoryPhaseStatus status, + @JsonProperty("lastEnteredRunAttempt") Long lastEnteredRunAttempt, + @JsonProperty("entryCount") Long entryCount, + @JsonProperty("startedAt") Long startedAt, + @JsonProperty("completedAt") Long completedAt, + @JsonProperty("accumulatedActiveMs") Long accumulatedActiveMs, + @JsonProperty("currentActiveMs") Long currentActiveMs, + @JsonProperty("totalAgentCount") Long totalAgentCount, + @JsonProperty("liveAgentCount") Long liveAgentCount +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryPhaseStatus.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryPhaseStatus.java new file mode 100644 index 000000000..d9fea0bc3 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryPhaseStatus.java @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * Derived lifecycle state of a factory phase. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum FactoryPhaseStatus { + /** The {@code pending} variant. */ + PENDING("pending"), + /** The {@code active} variant. */ + ACTIVE("active"), + /** The {@code completed} variant. */ + COMPLETED("completed"), + /** The {@code skipped} variant. */ + SKIPPED("skipped"); + + private final String value; + FactoryPhaseStatus(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static FactoryPhaseStatus fromValue(String value) { + for (FactoryPhaseStatus v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown FactoryPhaseStatus value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryProgressLine.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryProgressLine.java new file mode 100644 index 000000000..3a26b67d7 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryProgressLine.java @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * One durable factory progress record. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record FactoryProgressLine( + /** Global monotonic sequence number within the run. */ + @JsonProperty("seq") Long seq, + /** Resume attempt that emitted this record. */ + @JsonProperty("attempt") Long attempt, + /** Phase active when the record was emitted, or null before any phase. */ + @JsonProperty("phaseId") String phaseId, + /** Epoch milliseconds when the record was persisted. */ + @JsonProperty("recordedAt") Long recordedAt, + /** Progress record kind. */ + @JsonProperty("kind") FactoryLogLineKind kind, + /** Prompt-safe progress text. */ + @JsonProperty("text") String text +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryProgressPage.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryProgressPage.java new file mode 100644 index 000000000..56732d8f4 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryProgressPage.java @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * A bidirectional page of factory progress. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record FactoryProgressPage( + @JsonProperty("records") List records, + @JsonProperty("oldestSeq") Long oldestSeq, + @JsonProperty("newestSeq") Long newestSeq, + @JsonProperty("hasMoreOlder") Boolean hasMoreOlder, + @JsonProperty("hasMoreNewer") Boolean hasMoreNewer, + /** Run revision reflected by this page. */ + @JsonProperty("revision") Long revision +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunConsumed.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunConsumed.java new file mode 100644 index 000000000..62cec5f73 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunConsumed.java @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Durable factory resource consumption. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record FactoryRunConsumed( + @JsonProperty("activeMs") Long activeMs, + @JsonProperty("subagents") Long subagents, + @JsonProperty("nanoAiu") Long nanoAiu +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunLimits.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunLimits.java new file mode 100644 index 000000000..79304772a --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunLimits.java @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Wire-only per-invocation factory resource ceiling overrides. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record FactoryRunLimits( + /** Maximum number of factory subagents that may run concurrently. */ + @JsonProperty("maxConcurrentSubagents") Long maxConcurrentSubagents, + /** Maximum total number of factory subagents that may be admitted. */ + @JsonProperty("maxTotalSubagents") Long maxTotalSubagents, + /** Maximum accumulated active-execution time in seconds. Active execution includes the entire extension body, subprocess waits, queued-agent waits, and sleeps; time between resumed attempts is not counted. */ + @JsonProperty("timeoutSeconds") Double timeoutSeconds, + /** Maximum AI credits consumed by factory subagents and their descendants. The post-paid ceiling is soft: parallel turns can settle beyond it before the run stops. */ + @JsonProperty("maxAiCredits") Double maxAiCredits +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunResult.java new file mode 100644 index 000000000..bb28f4088 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunResult.java @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Complete current or terminal factory run envelope. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record FactoryRunResult( + /** Factory run identifier. */ + @JsonProperty("runId") String runId, + /** Current or terminal factory run status. */ + @JsonProperty("status") FactoryRunStatus status, + /** Completed factory result. */ + @JsonProperty("result") Object result, + /** Error message for an errored run. */ + @JsonProperty("error") String error, + /** Machine-readable failure details for an errored run. */ + @JsonProperty("failure") Object failure, + /** Reason for a halted or cancelled run. */ + @JsonProperty("reason") String reason, + /** Partial journal and progress snapshot for a halted, cancelled, or errored run. */ + @JsonProperty("snapshot") Object snapshot +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/FactoryRunStatus.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunStatus.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/FactoryRunStatus.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunStatus.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunSummary.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunSummary.java new file mode 100644 index 000000000..fb90885ee --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunSummary.java @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Durable factory run summary with read-time live overlays. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record FactoryRunSummary( + @JsonProperty("runId") String runId, + @JsonProperty("factoryName") String factoryName, + @JsonProperty("description") String description, + @JsonProperty("status") FactoryRunStatus status, + @JsonProperty("revision") Long revision, + @JsonProperty("createdAt") Long createdAt, + @JsonProperty("startedAt") Long startedAt, + @JsonProperty("updatedAt") Long updatedAt, + @JsonProperty("completedAt") Long completedAt, + @JsonProperty("currentPhase") FactoryCurrentPhase currentPhase, + @JsonProperty("declaredPhaseCount") Long declaredPhaseCount, + @JsonProperty("liveAgentCount") Long liveAgentCount, + @JsonProperty("totalSpawnedAgentCount") Long totalSpawnedAgentCount, + @JsonProperty("consumed") FactoryRunConsumed consumed, + @JsonProperty("declaredLimits") FactoryDeclaredLimits declaredLimits, + @JsonProperty("approved") FactoryDeclaredLimits approved, + @JsonProperty("observedAt") Long observedAt, + @JsonProperty("activeSegmentStartedAt") Long activeSegmentStartedAt, + @JsonProperty("terminal") FactoryRunTerminal terminal +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunTerminal.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunTerminal.java new file mode 100644 index 000000000..231c1b8a1 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/FactoryRunTerminal.java @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Prompt-safe terminal factory outcome. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record FactoryRunTerminal( + @JsonProperty("reason") String reason, + @JsonProperty("failure") Object failure, + @JsonProperty("error") String error, + @JsonProperty("resultPreview") String resultPreview +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/GitHubTelemetryClientInfo.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTelemetryClientInfo.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/GitHubTelemetryClientInfo.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTelemetryClientInfo.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/GitHubTelemetryEvent.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTelemetryEvent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/GitHubTelemetryEvent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTelemetryEvent.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/GitHubTelemetryNotification.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTelemetryNotification.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/GitHubTelemetryNotification.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/GitHubTelemetryNotification.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/HistoryCompactContextWindow.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryCompactContextWindow.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/HistoryCompactContextWindow.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryCompactContextWindow.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryFileRestoreSkipReason.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryFileRestoreSkipReason.java new file mode 100644 index 000000000..46e943e01 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryFileRestoreSkipReason.java @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * Reason a captured file was not restored. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum HistoryFileRestoreSkipReason { + /** The {@code user-modified} variant. */ + USER_MODIFIED("user-modified"), + /** The {@code skipped-capture} variant. */ + SKIPPED_CAPTURE("skipped-capture"); + + private final String value; + HistoryFileRestoreSkipReason(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static HistoryFileRestoreSkipReason fromValue(String value) { + for (HistoryFileRestoreSkipReason v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown HistoryFileRestoreSkipReason value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindChangeType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindChangeType.java new file mode 100644 index 000000000..85b12b873 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindChangeType.java @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * Aggregate file change represented by a rewind preview. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum HistoryRewindChangeType { + /** The {@code created} variant. */ + CREATED("created"), + /** The {@code deleted} variant. */ + DELETED("deleted"), + /** The {@code modified} variant. */ + MODIFIED("modified"); + + private final String value; + HistoryRewindChangeType(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static HistoryRewindChangeType fromValue(String value) { + for (HistoryRewindChangeType v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown HistoryRewindChangeType value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindFilePreview.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindFilePreview.java new file mode 100644 index 000000000..7733246dd --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindFilePreview.java @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * A file that a conversation-and-files rewind would restore. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record HistoryRewindFilePreview( + /** Absolute path of the captured file. */ + @JsonProperty("path") String path, + /** Aggregate change made across the discarded turns. */ + @JsonProperty("changeType") HistoryRewindChangeType changeType, + /** Lines added across the discarded turns. */ + @JsonProperty("linesAdded") Long linesAdded, + /** Lines removed across the discarded turns. */ + @JsonProperty("linesRemoved") Long linesRemoved +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindMode.java new file mode 100644 index 000000000..f72ded947 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindMode.java @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * Scope of a rewind operation. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum HistoryRewindMode { + /** The {@code conversation} variant. */ + CONVERSATION("conversation"), + /** The {@code conversation-and-files} variant. */ + CONVERSATION_AND_FILES("conversation-and-files"); + + private final String value; + HistoryRewindMode(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static HistoryRewindMode fromValue(String value) { + for (HistoryRewindMode v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown HistoryRewindMode value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindOutcome.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindOutcome.java new file mode 100644 index 000000000..624795ee4 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindOutcome.java @@ -0,0 +1,49 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * Outcome of a rewind request. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum HistoryRewindOutcome { + /** The {@code success} variant. */ + SUCCESS("success"), + /** The {@code session-busy} variant. */ + SESSION_BUSY("session-busy"), + /** The {@code file-change-tracking-disabled} variant. */ + FILE_CHANGE_TRACKING_DISABLED("file-change-tracking-disabled"), + /** The {@code unsupported-remote-session} variant. */ + UNSUPPORTED_REMOTE_SESSION("unsupported-remote-session"), + /** The {@code files-rolled-back} variant. */ + FILES_ROLLED_BACK("files-rolled-back"), + /** The {@code rollback-incomplete} variant. */ + ROLLBACK_INCOMPLETE("rollback-incomplete"), + /** The {@code truncation-failed} variant. */ + TRUNCATION_FAILED("truncation-failed"), + /** The {@code checkpoint-cleanup-failed} variant. */ + CHECKPOINT_CLEANUP_FAILED("checkpoint-cleanup-failed"), + /** The {@code snapshot-prune-failed} variant. */ + SNAPSHOT_PRUNE_FAILED("snapshot-prune-failed"); + + private final String value; + HistoryRewindOutcome(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static HistoryRewindOutcome fromValue(String value) { + for (HistoryRewindOutcome v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown HistoryRewindOutcome value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindPoint.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindPoint.java new file mode 100644 index 000000000..84926c74e --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindPoint.java @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * A root user turn that the session can rewind to. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record HistoryRewindPoint( + /** ID of the user.message event that begins the discarded suffix. */ + @JsonProperty("eventId") String eventId, + /** User-visible message text for the turn. */ + @JsonProperty("userMessage") String userMessage, + /** ISO timestamp of the user turn. */ + @JsonProperty("timestamp") String timestamp, + /** Whether at least one file in this turn or a later turn can be restored. */ + @JsonProperty("canRestoreFiles") Boolean canRestoreFiles, + /** Number of unique files in this turn and all later turns that have captured changes. */ + @JsonProperty("fileCount") Long fileCount, + /** Whether this turn itself captured any file changes. */ + @JsonProperty("turnChangedFiles") Boolean turnChangedFiles, + /** Lines added by this turn's captured file changes. */ + @JsonProperty("linesAdded") Long linesAdded, + /** Lines removed by this turn's captured file changes. */ + @JsonProperty("linesRemoved") Long linesRemoved, + /** Whether this turn was an automatically injected autopilot continuation. */ + @JsonProperty("isAutopilotContinuation") Boolean isAutopilotContinuation +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindUnavailableReason.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindUnavailableReason.java new file mode 100644 index 000000000..ae6b029ac --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistoryRewindUnavailableReason.java @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * Reason a rewind read (rewind points, file-restore preview, or session diff) could not be answered from the session's file-change captures. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum HistoryRewindUnavailableReason { + /** The {@code file-change-tracking-disabled} variant. */ + FILE_CHANGE_TRACKING_DISABLED("file-change-tracking-disabled"), + /** The {@code session-busy} variant. */ + SESSION_BUSY("session-busy"), + /** The {@code unsupported-remote-session} variant. */ + UNSUPPORTED_REMOTE_SESSION("unsupported-remote-session"); + + private final String value; + HistoryRewindUnavailableReason(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static HistoryRewindUnavailableReason fromValue(String value) { + for (HistoryRewindUnavailableReason v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown HistoryRewindUnavailableReason value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistorySkippedFileRestore.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistorySkippedFileRestore.java new file mode 100644 index 000000000..60c8c2d40 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HistorySkippedFileRestore.java @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * A captured file that rewind intentionally left unchanged. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record HistorySkippedFileRestore( + /** Absolute path of the skipped file. */ + @JsonProperty("path") String path, + /** Reason the file was not restored. */ + @JsonProperty("reason") HistoryFileRestoreSkipReason reason +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/HookInvokeRequest.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HookInvokeRequest.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/HookInvokeRequest.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/HookInvokeRequest.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/HookType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HookType.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/HookType.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/HookType.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/HooksInvokeResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/HooksInvokeResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/HooksInvokeResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/HooksInvokeResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/InstalledPlugin.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstalledPlugin.java similarity index 76% rename from java/src/generated/java/com/github/copilot/generated/rpc/InstalledPlugin.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstalledPlugin.java index b3487e7e3..3da690f47 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/InstalledPlugin.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstalledPlugin.java @@ -34,6 +34,8 @@ public record InstalledPlugin( /** Path where the plugin is cached locally */ @JsonProperty("cache_path") String cachePath, /** Source for direct repo installs (when marketplace is empty) */ - @JsonProperty("source") Object source + @JsonProperty("source") Object source, + /** Per-plugin source fingerprint (a SHA-256 hash of the plugin's catalog source spec plus its resolved source subtree — NOT a Git commit SHA) captured at marketplace install/update time. Auto-update compares it against the freshly recomputed fingerprint to detect a content change that does not bump the version. Absent for pre-existing installs and for direct (non-marketplace) installs. */ + @JsonProperty("source_sha") String sourceSha ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/InstalledPluginInfo.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstalledPluginInfo.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/InstalledPluginInfo.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstalledPluginInfo.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/InstructionDiscoveryPath.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstructionDiscoveryPath.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/InstructionDiscoveryPath.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstructionDiscoveryPath.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/InstructionDiscoveryPathKind.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstructionDiscoveryPathKind.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/InstructionDiscoveryPathKind.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstructionDiscoveryPathKind.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/InstructionDiscoveryPathLocation.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstructionDiscoveryPathLocation.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/InstructionDiscoveryPathLocation.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstructionDiscoveryPathLocation.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/InstructionSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstructionSource.java similarity index 90% rename from java/src/generated/java/com/github/copilot/generated/rpc/InstructionSource.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstructionSource.java index 2581375b3..496e1eadc 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/InstructionSource.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstructionSource.java @@ -40,7 +40,7 @@ public record InstructionSource( @JsonProperty("description") String description, /** When true, this source starts disabled and must be toggled on by the user */ @JsonProperty("defaultDisabled") Boolean defaultDisabled, - /** The project path this source was discovered from. Only set by sessionless discovery for repository/working-directory sources, where it disambiguates same-named files (e.g. .github/copilot-instructions.md) across multiple workspace roots. The session-scoped getSources leaves it unset. */ + /** The project path this source was discovered from. Only set by sessionless discovery for repository, working-directory, and project-scoped plugin sources, where it disambiguates sources across multiple workspace roots. The session-scoped getSources leaves it unset. */ @JsonProperty("projectPath") String projectPath ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/InstructionSourceLocation.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstructionSourceLocation.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/InstructionSourceLocation.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstructionSourceLocation.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/InstructionSourceType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstructionSourceType.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/InstructionSourceType.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstructionSourceType.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/InstructionsDiscoverParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstructionsDiscoverParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/InstructionsDiscoverParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstructionsDiscoverParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/InstructionsDiscoverResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstructionsDiscoverResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/InstructionsDiscoverResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstructionsDiscoverResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/InstructionsGetDiscoveryPathsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstructionsGetDiscoveryPathsParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/InstructionsGetDiscoveryPathsParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstructionsGetDiscoveryPathsParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/InstructionsGetDiscoveryPathsResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstructionsGetDiscoveryPathsResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/InstructionsGetDiscoveryPathsResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/InstructionsGetDiscoveryPathsResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpRequestChunkRequest.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpRequestChunkRequest.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpRequestChunkRequest.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpRequestChunkRequest.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpRequestChunkResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpRequestChunkResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpRequestChunkResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpRequestChunkResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpRequestStartRequest.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpRequestStartRequest.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpRequestStartRequest.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpRequestStartRequest.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpRequestStartResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpRequestStartResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpRequestStartResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpRequestStartResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpRequestStartTransport.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpRequestStartTransport.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpRequestStartTransport.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpRequestStartTransport.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpResponseChunkError.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpResponseChunkError.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpResponseChunkError.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpResponseChunkError.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpResponseChunkParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpResponseChunkParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpResponseChunkParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpResponseChunkParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpResponseChunkResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpResponseChunkResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpResponseChunkResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpResponseChunkResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpResponseStartParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpResponseStartParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpResponseStartParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpResponseStartParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpResponseStartResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpResponseStartResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpResponseStartResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceHttpResponseStartResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceSetProviderResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceSetProviderResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceSetProviderResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/LlmInferenceSetProviderResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/LocalSessionMetadataValue.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/LocalSessionMetadataValue.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/LocalSessionMetadataValue.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/LocalSessionMetadataValue.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsReadResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsReadResult.java new file mode 100644 index 000000000..2018f62ce --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ManagedSettingsReadResult.java @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Validated device-managed settings discovered before a session exists. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record ManagedSettingsReadResult( + /** Validated, canonical managed-settings JSON. Omitted when no managed settings were discovered or when discovered settings failed validation. */ + @JsonProperty("settingsJson") Object settingsJson, + /** Discovery or validation error text when managed settings could not be read safely. */ + @JsonProperty("errorMessage") String errorMessage +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/MarketplaceInfo.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/MarketplaceInfo.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/MarketplaceInfo.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/MarketplaceInfo.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/MarketplacePluginInfo.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/MarketplacePluginInfo.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/MarketplacePluginInfo.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/MarketplacePluginInfo.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/MarketplaceRefreshEntry.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/MarketplaceRefreshEntry.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/MarketplaceRefreshEntry.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/MarketplaceRefreshEntry.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpAllowedServer.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpAllowedServer.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpAllowedServer.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpAllowedServer.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpAppsDiagnoseCapability.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpAppsDiagnoseCapability.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpAppsDiagnoseCapability.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpAppsDiagnoseCapability.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpAppsDiagnoseServer.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpAppsDiagnoseServer.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpAppsDiagnoseServer.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpAppsDiagnoseServer.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpAppsHostContextDetails.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpAppsHostContextDetails.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpAppsHostContextDetails.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpAppsHostContextDetails.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpAppsHostContextDetailsAvailableDisplayMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpAppsHostContextDetailsAvailableDisplayMode.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpAppsHostContextDetailsAvailableDisplayMode.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpAppsHostContextDetailsAvailableDisplayMode.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpAppsHostContextDetailsDisplayMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpAppsHostContextDetailsDisplayMode.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpAppsHostContextDetailsDisplayMode.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpAppsHostContextDetailsDisplayMode.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpAppsHostContextDetailsPlatform.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpAppsHostContextDetailsPlatform.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpAppsHostContextDetailsPlatform.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpAppsHostContextDetailsPlatform.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpAppsHostContextDetailsTheme.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpAppsHostContextDetailsTheme.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpAppsHostContextDetailsTheme.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpAppsHostContextDetailsTheme.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpAppsResourceContent.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpAppsResourceContent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpAppsResourceContent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpAppsResourceContent.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpAppsSetHostContextDetails.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpAppsSetHostContextDetails.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpAppsSetHostContextDetails.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpAppsSetHostContextDetails.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpAppsSetHostContextDetailsAvailableDisplayMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpAppsSetHostContextDetailsAvailableDisplayMode.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpAppsSetHostContextDetailsAvailableDisplayMode.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpAppsSetHostContextDetailsAvailableDisplayMode.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpAppsSetHostContextDetailsDisplayMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpAppsSetHostContextDetailsDisplayMode.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpAppsSetHostContextDetailsDisplayMode.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpAppsSetHostContextDetailsDisplayMode.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpAppsSetHostContextDetailsPlatform.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpAppsSetHostContextDetailsPlatform.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpAppsSetHostContextDetailsPlatform.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpAppsSetHostContextDetailsPlatform.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpAppsSetHostContextDetailsTheme.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpAppsSetHostContextDetailsTheme.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpAppsSetHostContextDetailsTheme.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpAppsSetHostContextDetailsTheme.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpConfigAddParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpConfigAddParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpConfigAddParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpConfigAddParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpConfigDisableParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpConfigDisableParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpConfigDisableParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpConfigDisableParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpConfigEnableParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpConfigEnableParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpConfigEnableParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpConfigEnableParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpConfigListResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpConfigListResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpConfigListResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpConfigListResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpConfigRemoveParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpConfigRemoveParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpConfigRemoveParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpConfigRemoveParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpConfigUpdateParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpConfigUpdateParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpConfigUpdateParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpConfigUpdateParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpDiscoverParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpDiscoverParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpDiscoverParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpDiscoverParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpDiscoverResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpDiscoverResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpDiscoverResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpDiscoverResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpExecuteSamplingRequest.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpExecuteSamplingRequest.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpExecuteSamplingRequest.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpExecuteSamplingRequest.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpExecuteSamplingResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpExecuteSamplingResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpExecuteSamplingResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpExecuteSamplingResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpFilteredServer.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpFilteredServer.java similarity index 87% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpFilteredServer.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpFilteredServer.java index 51dbd2bd1..e0ecefae7 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/McpFilteredServer.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpFilteredServer.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * MCP server filtered by policy, with name, reason, optional redacted reason, and enterprise login. + * MCP server filtered by policy, with name, reason, and optional redacted reason. * * @since 1.0.0 */ @@ -27,7 +27,7 @@ public record McpFilteredServer( @JsonProperty("reason") String reason, /** PII-free filter reason */ @JsonProperty("redactedReason") String redactedReason, - /** Enterprise login associated with an allowlist policy */ + /** Deprecated. This field is no longer populated. */ @JsonProperty("enterpriseName") String enterpriseName ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpHostState.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpHostState.java similarity index 95% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpHostState.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpHostState.java index 0326a120e..152bd8556 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/McpHostState.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpHostState.java @@ -27,7 +27,7 @@ public record McpHostState( @JsonProperty("mcp3pEnabled") Boolean mcp3pEnabled, /** Configured servers that are explicitly disabled. */ @JsonProperty("disabledServers") List disabledServers, - /** Configured servers filtered out by enterprise allowlist policy. */ + /** Configured servers filtered out by MCP server policy. */ @JsonProperty("filteredServers") List filteredServers, /** Names of currently-connected MCP clients. */ @JsonProperty("clients") List clients, diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpOauthLoginGrantType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpOauthLoginGrantType.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpOauthLoginGrantType.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpOauthLoginGrantType.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpResource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpResource.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpResource.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpResource.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpResourceAnnotations.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpResourceAnnotations.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpResourceAnnotations.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpResourceAnnotations.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpResourceContent.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpResourceContent.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpResourceContent.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpResourceContent.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpResourceIcon.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpResourceIcon.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpResourceIcon.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpResourceIcon.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpResourceTemplate.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpResourceTemplate.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpResourceTemplate.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpResourceTemplate.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpSamplingExecutionAction.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpSamplingExecutionAction.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpSamplingExecutionAction.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpSamplingExecutionAction.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpServer.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpServer.java similarity index 97% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpServer.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpServer.java index 382c54c40..14a9118d0 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/McpServer.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpServer.java @@ -23,7 +23,7 @@ public record McpServer( /** Server name (config key) */ @JsonProperty("name") String name, - /** Connection status: connected, failed, needs-auth, pending, disabled, or not_configured */ + /** Connection status: connected, failed, needs-auth, pending, disabled, stopped, or not_configured */ @JsonProperty("status") McpServerStatus status, /** Configuration source: user, workspace, plugin, or builtin */ @JsonProperty("source") McpServerSource source, diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpServerFailureInfo.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpServerFailureInfo.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpServerFailureInfo.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpServerFailureInfo.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpServerNeedsAuthInfo.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpServerNeedsAuthInfo.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpServerNeedsAuthInfo.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpServerNeedsAuthInfo.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpServerSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpServerSource.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpServerSource.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpServerSource.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpServerStatus.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpServerStatus.java similarity index 93% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpServerStatus.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpServerStatus.java index db463a737..4c1fb46b2 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/McpServerStatus.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpServerStatus.java @@ -10,7 +10,7 @@ import javax.annotation.processing.Generated; /** - * Connection status: connected, failed, needs-auth, pending, disabled, or not_configured + * Connection status: connected, failed, needs-auth, pending, disabled, stopped, or not_configured * * @since 1.0.0 */ @@ -26,6 +26,8 @@ public enum McpServerStatus { PENDING("pending"), /** The {@code disabled} variant. */ DISABLED("disabled"), + /** The {@code stopped} variant. */ + STOPPED("stopped"), /** The {@code not_configured} variant. */ NOT_CONFIGURED("not_configured"); diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpSetEnvValueModeDetails.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpSetEnvValueModeDetails.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpSetEnvValueModeDetails.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpSetEnvValueModeDetails.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpToolUi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpToolUi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpToolUi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpToolUi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpToolUiVisibility.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpToolUiVisibility.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpToolUiVisibility.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpToolUiVisibility.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/McpTools.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpTools.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/McpTools.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/McpTools.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/MemoryConfiguration.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/MemoryConfiguration.java new file mode 100644 index 000000000..63d327a41 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/MemoryConfiguration.java @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Memory configuration for this session. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record MemoryConfiguration( + /** Whether memory is enabled for the session. */ + @JsonProperty("enabled") Boolean enabled +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/MetadataSnapshotCurrentMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/MetadataSnapshotCurrentMode.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/MetadataSnapshotCurrentMode.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/MetadataSnapshotCurrentMode.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/MetadataSnapshotRemoteMetadata.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/MetadataSnapshotRemoteMetadata.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/MetadataSnapshotRemoteMetadata.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/MetadataSnapshotRemoteMetadata.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/MetadataSnapshotRemoteMetadataRepository.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/MetadataSnapshotRemoteMetadataRepository.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/MetadataSnapshotRemoteMetadataRepository.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/MetadataSnapshotRemoteMetadataRepository.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/MetadataSnapshotRemoteMetadataTaskType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/MetadataSnapshotRemoteMetadataTaskType.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/MetadataSnapshotRemoteMetadataTaskType.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/MetadataSnapshotRemoteMetadataTaskType.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/Model.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/Model.java similarity index 91% rename from java/src/generated/java/com/github/copilot/generated/rpc/Model.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/Model.java index c55fc026f..f002df540 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/Model.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/Model.java @@ -34,8 +34,6 @@ public record Model( @JsonProperty("billing") ModelBilling billing, /** Supported reasoning effort levels (only present if model supports reasoning effort) */ @JsonProperty("supportedReasoningEfforts") List supportedReasoningEfforts, - /** Default reasoning effort level (only present if model supports reasoning effort) */ - @JsonProperty("defaultReasoningEffort") String defaultReasoningEffort, /** Model capability category for grouping in the model picker */ @JsonProperty("modelPickerCategory") ModelPickerCategory modelPickerCategory, /** Relative cost tier for token-based billing users */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ModelBilling.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelBilling.java similarity index 93% rename from java/src/generated/java/com/github/copilot/generated/rpc/ModelBilling.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelBilling.java index f8298dd62..f72f4b5cf 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/ModelBilling.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelBilling.java @@ -27,7 +27,7 @@ public record ModelBilling( @JsonProperty("tokenPrices") ModelBillingTokenPrices tokenPrices, /** Whole-number percentage discount (0-100) applied to usage billed through this model. Populated for the synthetic `auto` model, where requests routed by auto-mode are billed at a reduced rate; absent for concrete models. */ @JsonProperty("discountPercent") Long discountPercent, - /** Active server-driven promotion for this model, if any. Present when the model is being promoted with a time-boxed discount. */ + /** Active server-driven promotion for this model, if any. Present when the model is being promoted with a discount, which may be time-boxed or open-ended. */ @JsonProperty("promo") ModelBillingPromo promo ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ModelBillingPromo.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelBillingPromo.java similarity index 81% rename from java/src/generated/java/com/github/copilot/generated/rpc/ModelBillingPromo.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelBillingPromo.java index 731e29835..087ca1c15 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/ModelBillingPromo.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelBillingPromo.java @@ -13,7 +13,7 @@ import javax.annotation.processing.Generated; /** - * Active server-driven promotion for a model, including its discount and expiry. + * Active server-driven promotion for a model, including its discount and optional expiry. * * @since 1.0.0 */ @@ -25,9 +25,9 @@ public record ModelBillingPromo( @JsonProperty("id") String id, /** Percentage discount (0-100) applied while the promotion is active. May be fractional. */ @JsonProperty("discountPercent") Double discountPercent, - /** UTC ISO 8601 timestamp marking when the promotion ends. Always present: the API only surfaces a promo whose expiry parses and is in the future. Consumers should treat a past value as expired. */ + /** UTC ISO 8601 timestamp marking when the promotion ends. Optional: an open-ended promotion omits this field. When present, the API only surfaces a promo whose expiry parses and is in the future, so consumers should treat a past value as expired. */ @JsonProperty("endsAt") String endsAt, - /** Human-readable promotion message. Does not include the expiry timestamp; consumers may format endsAt and append it. */ + /** Human-readable promotion message. Does not include the expiry timestamp; consumers may format endsAt and append it when present. */ @JsonProperty("message") String message ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ModelBillingTokenPrices.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelBillingTokenPrices.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ModelBillingTokenPrices.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelBillingTokenPrices.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ModelBillingTokenPricesLongContext.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelBillingTokenPricesLongContext.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ModelBillingTokenPricesLongContext.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelBillingTokenPricesLongContext.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ModelCapabilities.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelCapabilities.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ModelCapabilities.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelCapabilities.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ModelCapabilitiesLimits.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelCapabilitiesLimits.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ModelCapabilitiesLimits.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelCapabilitiesLimits.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ModelCapabilitiesLimitsVision.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelCapabilitiesLimitsVision.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ModelCapabilitiesLimitsVision.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelCapabilitiesLimitsVision.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ModelCapabilitiesOverride.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelCapabilitiesOverride.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ModelCapabilitiesOverride.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelCapabilitiesOverride.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ModelCapabilitiesOverrideLimits.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelCapabilitiesOverrideLimits.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ModelCapabilitiesOverrideLimits.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelCapabilitiesOverrideLimits.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ModelCapabilitiesOverrideLimitsVision.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelCapabilitiesOverrideLimitsVision.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ModelCapabilitiesOverrideLimitsVision.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelCapabilitiesOverrideLimitsVision.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ModelCapabilitiesOverrideSupports.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelCapabilitiesOverrideSupports.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ModelCapabilitiesOverrideSupports.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelCapabilitiesOverrideSupports.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ModelCapabilitiesSupports.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelCapabilitiesSupports.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ModelCapabilitiesSupports.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelCapabilitiesSupports.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ModelPickerCategory.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelPickerCategory.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ModelPickerCategory.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelPickerCategory.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ModelPickerPriceCategory.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelPickerPriceCategory.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ModelPickerPriceCategory.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelPickerPriceCategory.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ModelPolicy.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelPolicy.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ModelPolicy.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelPolicy.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ModelPolicyState.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelPolicyState.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ModelPolicyState.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelPolicyState.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelsGetBuiltInCatalogResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelsGetBuiltInCatalogResult.java new file mode 100644 index 000000000..9797a2d67 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelsGetBuiltInCatalogResult.java @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * The running runtime's complete catalog of well-known built-in model IDs, including supported models and additional IDs with built-in metadata. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record ModelsGetBuiltInCatalogResult( + /** Built-in model entries. */ + @JsonProperty("models") List models +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelsListParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelsListParams.java new file mode 100644 index 000000000..3366ff61c --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelsListParams.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Request parameters for the {@code models.list} RPC method. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record ModelsListParams( + /** GitHub token for per-user model listing. When provided, resolves this token to determine the user's Copilot plan and available models instead of using the global auth. */ + @JsonProperty("gitHubToken") String gitHubToken +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ModelsListResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelsListResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ModelsListResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ModelsListResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/NamedProviderConfig.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/NamedProviderConfig.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/NamedProviderConfig.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/NamedProviderConfig.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/OpenCanvasInstance.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/OpenCanvasInstance.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/OpenCanvasInstance.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/OpenCanvasInstance.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateAdditionalContentExclusionPolicy.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateAdditionalContentExclusionPolicy.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateAdditionalContentExclusionPolicy.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateAdditionalContentExclusionPolicy.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateAdditionalContentExclusionPolicyRule.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateAdditionalContentExclusionPolicyRule.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateAdditionalContentExclusionPolicyRule.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateAdditionalContentExclusionPolicyRule.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateAdditionalContentExclusionPolicyRuleSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateAdditionalContentExclusionPolicyRuleSource.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateAdditionalContentExclusionPolicyRuleSource.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateAdditionalContentExclusionPolicyRuleSource.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateAdditionalContentExclusionPolicyScope.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateAdditionalContentExclusionPolicyScope.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateAdditionalContentExclusionPolicyScope.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateAdditionalContentExclusionPolicyScope.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateContextTier.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateContextTier.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateContextTier.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateContextTier.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateEnvValueMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateEnvValueMode.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateEnvValueMode.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateEnvValueMode.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateReasoningSummary.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateReasoningSummary.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateReasoningSummary.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateReasoningSummary.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateToolFilterPrecedence.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateToolFilterPrecedence.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateToolFilterPrecedence.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/OptionsUpdateToolFilterPrecedence.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PendingPermissionRequest.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PendingPermissionRequest.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PendingPermissionRequest.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PendingPermissionRequest.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionDecisionContext.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionDecisionContext.java new file mode 100644 index 000000000..73934eea6 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionDecisionContext.java @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Optional informational context describing how and where the permission decision was made. This does not affect permission behavior. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record PermissionDecisionContext( + /** Disposition of the permission request as observed by the responding client. */ + @JsonProperty("outcome") PermissionDecisionOutcome outcome, + /** Controlled reason or actor responsible for the response. */ + @JsonProperty("source") PermissionDecisionSource source, + /** Client surface that submitted the response. */ + @JsonProperty("surface") PermissionDecisionSurface surface +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionDecisionOutcome.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionDecisionOutcome.java new file mode 100644 index 000000000..d46c460a2 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionDecisionOutcome.java @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * Disposition of a permission request as observed by the responding client. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum PermissionDecisionOutcome { + /** The {@code auto_approved} variant. */ + AUTO_APPROVED("auto_approved"), + /** The {@code autopilot_denied} variant. */ + AUTOPILOT_DENIED("autopilot_denied"), + /** The {@code prompted_user} variant. */ + PROMPTED_USER("prompted_user"); + + private final String value; + PermissionDecisionOutcome(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static PermissionDecisionOutcome fromValue(String value) { + for (PermissionDecisionOutcome v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown PermissionDecisionOutcome value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionDecisionSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionDecisionSource.java new file mode 100644 index 000000000..ee807b095 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionDecisionSource.java @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * Controlled reason or actor responsible for a permission response. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum PermissionDecisionSource { + /** The {@code judge_recommendation} variant. */ + JUDGE_RECOMMENDATION("judge_recommendation"), + /** The {@code human_response} variant. */ + HUMAN_RESPONSE("human_response"), + /** The {@code host_policy} variant. */ + HOST_POLICY("host_policy"), + /** The {@code unattended_fallback} variant. */ + UNATTENDED_FALLBACK("unattended_fallback"); + + private final String value; + PermissionDecisionSource(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static PermissionDecisionSource fromValue(String value) { + for (PermissionDecisionSource v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown PermissionDecisionSource value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionDecisionSurface.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionDecisionSurface.java new file mode 100644 index 000000000..2cf634879 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionDecisionSurface.java @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * Client surface that submitted a permission response. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum PermissionDecisionSurface { + /** The {@code tui} variant. */ + TUI("tui"), + /** The {@code prompt_mode} variant. */ + PROMPT_MODE("prompt_mode"), + /** The {@code copilot_app} variant. */ + COPILOT_APP("copilot_app"), + /** The {@code sdk} variant. */ + SDK("sdk"); + + private final String value; + PermissionDecisionSurface(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static PermissionDecisionSurface fromValue(String value) { + for (PermissionDecisionSurface v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown PermissionDecisionSurface value: " + value); + } +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PermissionLocationType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionLocationType.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PermissionLocationType.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionLocationType.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PermissionPathsConfig.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionPathsConfig.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PermissionPathsConfig.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionPathsConfig.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PermissionRule.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionRule.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PermissionRule.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionRule.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PermissionRulesSet.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionRulesSet.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PermissionRulesSet.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionRulesSet.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PermissionUrlsConfig.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionUrlsConfig.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PermissionUrlsConfig.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionUrlsConfig.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PermissionsAllowAllMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionsAllowAllMode.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PermissionsAllowAllMode.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionsAllowAllMode.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PermissionsConfigureAdditionalContentExclusionPolicy.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionsConfigureAdditionalContentExclusionPolicy.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PermissionsConfigureAdditionalContentExclusionPolicy.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionsConfigureAdditionalContentExclusionPolicy.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PermissionsConfigureAdditionalContentExclusionPolicyRule.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionsConfigureAdditionalContentExclusionPolicyRule.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PermissionsConfigureAdditionalContentExclusionPolicyRule.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionsConfigureAdditionalContentExclusionPolicyRule.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PermissionsConfigureAdditionalContentExclusionPolicyRuleSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionsConfigureAdditionalContentExclusionPolicyRuleSource.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PermissionsConfigureAdditionalContentExclusionPolicyRuleSource.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionsConfigureAdditionalContentExclusionPolicyRuleSource.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PermissionsConfigureAdditionalContentExclusionPolicyScope.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionsConfigureAdditionalContentExclusionPolicyScope.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PermissionsConfigureAdditionalContentExclusionPolicyScope.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionsConfigureAdditionalContentExclusionPolicyScope.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PermissionsModifyRulesScope.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionsModifyRulesScope.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PermissionsModifyRulesScope.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionsModifyRulesScope.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PermissionsSetAllowAllSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionsSetAllowAllSource.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PermissionsSetAllowAllSource.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionsSetAllowAllSource.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PermissionsSetApproveAllSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionsSetApproveAllSource.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PermissionsSetApproveAllSource.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PermissionsSetApproveAllSource.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PingParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PingParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PingParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PingParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PingResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PingResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PingResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PingResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PlanSqlTodoDependency.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PlanSqlTodoDependency.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PlanSqlTodoDependency.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PlanSqlTodoDependency.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PlanSqlTodosRow.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PlanSqlTodosRow.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PlanSqlTodosRow.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PlanSqlTodosRow.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/Plugin.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/Plugin.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/Plugin.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/Plugin.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PluginUpdateAllEntry.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginUpdateAllEntry.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PluginUpdateAllEntry.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginUpdateAllEntry.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PluginsDisableParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsDisableParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PluginsDisableParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsDisableParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PluginsEnableParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsEnableParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PluginsEnableParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsEnableParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PluginsInstallParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsInstallParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PluginsInstallParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsInstallParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PluginsInstallResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsInstallResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PluginsInstallResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsInstallResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PluginsListResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsListResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PluginsListResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsListResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesAddParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesAddParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesAddParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesAddParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesAddResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesAddResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesAddResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesAddResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesBrowseParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesBrowseParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesBrowseParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesBrowseParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesBrowseResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesBrowseResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesBrowseResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesBrowseResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesListResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesListResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesListResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesListResult.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesRefreshParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesRefreshParams.java new file mode 100644 index 000000000..a390962b5 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesRefreshParams.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Request parameters for the {@code plugins.marketplaces.refresh} RPC method. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record PluginsMarketplacesRefreshParams( + /** Marketplace name to refresh. When omitted, every registered marketplace is refreshed. */ + @JsonProperty("name") String name +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesRefreshResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesRefreshResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesRefreshResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesRefreshResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesRemoveParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesRemoveParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesRemoveParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesRemoveParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesRemoveResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesRemoveResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesRemoveResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsMarketplacesRemoveResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PluginsUninstallParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsUninstallParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PluginsUninstallParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsUninstallParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PluginsUpdateAllResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsUpdateAllResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PluginsUpdateAllResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsUpdateAllResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PluginsUpdateParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsUpdateParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PluginsUpdateParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsUpdateParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/PluginsUpdateResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsUpdateResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/PluginsUpdateResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/PluginsUpdateResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ProviderConfig.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderConfig.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ProviderConfig.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderConfig.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ProviderConfigAzure.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderConfigAzure.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ProviderConfigAzure.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderConfigAzure.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ProviderConfigTransport.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderConfigTransport.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ProviderConfigTransport.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderConfigTransport.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ProviderConfigType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderConfigType.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ProviderConfigType.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderConfigType.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ProviderConfigWireApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderConfigWireApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ProviderConfigWireApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderConfigWireApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ProviderEndpointTransport.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderEndpointTransport.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ProviderEndpointTransport.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderEndpointTransport.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ProviderEndpointType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderEndpointType.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ProviderEndpointType.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderEndpointType.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ProviderEndpointWireApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderEndpointWireApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ProviderEndpointWireApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderEndpointWireApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ProviderModelConfig.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderModelConfig.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ProviderModelConfig.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderModelConfig.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ProviderSessionToken.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderSessionToken.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ProviderSessionToken.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderSessionToken.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ProviderTokenGetTokenParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderTokenGetTokenParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ProviderTokenGetTokenParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderTokenGetTokenParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ProviderTokenGetTokenResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderTokenGetTokenResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ProviderTokenGetTokenResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ProviderTokenGetTokenResult.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/QueueInsertMessage.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/QueueInsertMessage.java new file mode 100644 index 000000000..d1056f872 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/QueueInsertMessage.java @@ -0,0 +1,51 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import java.util.Map; +import javax.annotation.processing.Generated; + +/** + * Serializable message fields accepted by queue.insertAt. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record QueueInsertMessage( + /** The user message text. */ + @JsonProperty("prompt") String prompt, + /** Optional user-facing display text. */ + @JsonProperty("displayPrompt") String displayPrompt, + /** Optional attachments for the message. */ + @JsonProperty("attachments") List attachments, + /** Optional explicit agent mode. When omitted, the session's current mode is assigned. */ + @JsonProperty("agentMode") SendAgentMode agentMode, + /** Optional provenance source. `system` is rejected: it would hide the inserted row from `pendingItems` and make it unaddressable while still executing, so inserted items must stay visible. */ + @JsonProperty("source") String source, + /** Whether the message is billable. */ + @JsonProperty("billable") Boolean billable, + /** Required tool name for the turn, when any. */ + @JsonProperty("requiredTool") String requiredTool, + /** Per-turn request headers. */ + @JsonProperty("requestHeaders") Map requestHeaders, + /** Accepted for SendOptions compatibility but ignored; inserted items always use queued delivery semantics. */ + @JsonProperty("mode") SendMode mode, + /** Accepted for SendOptions compatibility but ignored; the requested public position controls placement. */ + @JsonProperty("prepend") Boolean prepend, + /** Accepted for SendOptions compatibility but ignored; insertion scheduling is controlled by the queue drain state. */ + @JsonProperty("wait") Boolean wait_, + /** Accepted for internal SendOptions compatibility but ignored; delivery is derived from current session activity. */ + @JsonProperty("delivery") String delivery +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/QueuePendingItems.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/QueuePendingItems.java similarity index 66% rename from java/src/generated/java/com/github/copilot/generated/rpc/QueuePendingItems.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/QueuePendingItems.java index 8767e91d0..f3b2f9918 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/QueuePendingItems.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/QueuePendingItems.java @@ -21,9 +21,13 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record QueuePendingItems( + /** Stable opaque id for the canonical queued item. Batch rows share one id. */ + @JsonProperty("id") String id, /** Whether this item is a queued user message or a queued slash command / model change */ @JsonProperty("kind") QueuePendingItemsKind kind, /** Human-readable text to display for this queue entry in the UI */ - @JsonProperty("displayText") String displayText + @JsonProperty("displayText") String displayText, + /** Agent mode stored on this queued entry, as stamped when it was enqueued. Items without an explicit mode report interactive. This is not necessarily the mode that will constrain the turn: a plan or autopilot session applies its own write gate, continuation loop and permission posture to every drained item regardless of the mode stored here. */ + @JsonProperty("agentMode") SendAgentMode agentMode ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/QueuePendingItemsKind.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/QueuePendingItemsKind.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/QueuePendingItemsKind.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/QueuePendingItemsKind.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ReasoningSummary.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ReasoningSummary.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ReasoningSummary.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ReasoningSummary.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/RemoteControlConfig.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RemoteControlConfig.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/RemoteControlConfig.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/RemoteControlConfig.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/RemoteControlConfigExistingMcSession.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RemoteControlConfigExistingMcSession.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/RemoteControlConfigExistingMcSession.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/RemoteControlConfigExistingMcSession.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/RemoteSessionMetadataRepository.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RemoteSessionMetadataRepository.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/RemoteSessionMetadataRepository.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/RemoteSessionMetadataRepository.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/RemoteSessionMetadataTaskType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RemoteSessionMetadataTaskType.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/RemoteSessionMetadataTaskType.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/RemoteSessionMetadataTaskType.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/RemoteSessionMetadataValue.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RemoteSessionMetadataValue.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/RemoteSessionMetadataValue.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/RemoteSessionMetadataValue.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/RemoteSessionMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RemoteSessionMode.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/RemoteSessionMode.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/RemoteSessionMode.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RemoteSessionRepository.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RemoteSessionRepository.java new file mode 100644 index 000000000..1ab906bb1 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RemoteSessionRepository.java @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Repository context for the remote session. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record RemoteSessionRepository( + /** Repository owner or organization login. */ + @JsonProperty("owner") String owner, + /** Repository name. */ + @JsonProperty("name") String name, + /** Optional branch associated with the remote session. */ + @JsonProperty("branch") String branch +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/RpcCaller.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RpcCaller.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/RpcCaller.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/RpcCaller.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/RpcMapper.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RpcMapper.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/RpcMapper.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/RpcMapper.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/RunOptions.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/RunOptions.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/RunOptions.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/RunOptions.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SandboxConfig.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfig.java similarity index 64% rename from java/src/generated/java/com/github/copilot/generated/rpc/SandboxConfig.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfig.java index 3460560b2..d9130eb81 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SandboxConfig.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfig.java @@ -27,9 +27,9 @@ public record SandboxConfig( @JsonProperty("userPolicy") SandboxConfigUserPolicy userPolicy, /** Whether to auto-add the current working directory to readwritePaths. Default: true. */ @JsonProperty("addCurrentWorkingDirectory") Boolean addCurrentWorkingDirectory, - /** Whether to inject the Copilot GitHub token as an `http..extraheader` so authenticated HTTPS git works inside the sandbox without the shell-based credential helper the sandbox blocks. Default: false (opt-in). */ - @JsonProperty("gitAuth") Boolean gitAuth, - /** Whether to export `GH_TOKEN` so the `gh` CLI authenticates inside the sandbox without the OS keyring the sandbox blocks. Default: false (opt-in). */ - @JsonProperty("ghAuth") Boolean ghAuth + /** Credential-injection capability flags. */ + @JsonProperty("auth") SandboxConfigAuth auth, + /** Whether to auto-grant read access to common developer-tool caches, registries, and toolchains in their default home locations (cargo, go, npm, Maven, and more), plus read-write access to (and, on Unix, up-front creation of) the scratch caches builds write on every run (go-build, ccache, sccache, Gradle caches, Cargo lock/tracker files), so builds work without extra configuration; a relocated CARGO_HOME additionally gets its Cargo lock files granted read-write. Default: true (enabled by default; set to false to opt out). */ + @JsonProperty("allowDevToolAccess") Boolean allowDevToolAccess ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigAuth.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigAuth.java new file mode 100644 index 000000000..4a3612e0b --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigAuth.java @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Credential-injection capability flags applied while the sandbox is enabled. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SandboxConfigAuth( + /** Whether to inject git credentials as an `http..extraheader` so authenticated HTTPS git works inside the sandbox without the shell-based credential helper the sandbox blocks. github.com is served by the Copilot token; every other forge (Azure DevOps, GitHub Enterprise Server, GitLab, ...) by a credential the host resolves from the user's own helper before the sandbox is applied. Default: false (opt-in). */ + @JsonProperty("git") Boolean git, + /** Whether to export `GH_TOKEN` so the `gh` CLI authenticates inside the sandbox without the OS keyring the sandbox blocks. Default: false (opt-in). */ + @JsonProperty("gh") Boolean gh +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicy.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicy.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicy.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicy.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyExperimental.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyExperimental.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyExperimental.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyExperimental.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyExperimentalSeatbelt.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyExperimentalSeatbelt.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyExperimentalSeatbelt.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyExperimentalSeatbelt.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyFilesystem.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyFilesystem.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyFilesystem.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyFilesystem.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyNetwork.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyNetwork.java similarity index 70% rename from java/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyNetwork.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyNetwork.java index 9b8b53c81..1e56acb53 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyNetwork.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyNetwork.java @@ -24,6 +24,8 @@ public record SandboxConfigUserPolicyNetwork( /** Whether outbound network traffic is allowed at all. */ @JsonProperty("allowOutbound") Boolean allowOutbound, /** Whether traffic to local/loopback addresses is allowed. */ - @JsonProperty("allowLocalNetwork") Boolean allowLocalNetwork + @JsonProperty("allowLocalNetwork") Boolean allowLocalNetwork, + /** HTTP proxy the sandboxed process routes traffic through. Enforced on Windows and cooperative (honored by well-behaved tools, not strictly enforced) on Linux and macOS. Credentials go in the separate `username`/`password` fields. A credential-free http:// loopback proxy URL is routed through the localhost proxy automatically; an https:// or authenticated loopback URL is used as-is. */ + @JsonProperty("proxy") SandboxConfigUserPolicyNetworkProxy proxy ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyNetworkProxy.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyNetworkProxy.java new file mode 100644 index 000000000..74ff86919 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicyNetworkProxy.java @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * HTTP proxy configuration for sandboxed traffic. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SandboxConfigUserPolicyNetworkProxy( + /** Proxy URL (e.g. http://proxy.example.com:8080). The port is optional and defaults to the scheme's standard port when omitted. Credentials must not be embedded here — a `user:pass@` authority is rejected; put them in the separate `username`/`password` fields. A credential-free http:// loopback URL is routed through the localhost proxy automatically; loopback covers localhost and any *.localhost subdomain, the whole 127.0.0.0/8 range, ::1, and IPv4-mapped loopback (::ffff:127.0.0.1). An https:// URL, or one with a username/password set, is used as-is. */ + @JsonProperty("url") String url, + /** Optional username for proxy authentication. Combined with the URL (and `password`) into `user:pass@host` when the sandboxed process routes through the proxy. */ + @JsonProperty("username") String username, + /** Optional password for proxy authentication, combined with the URL at spawn time. The persisted value may be a literal password, a `${secret:…}` reference resolved from the OS keychain, or a `${VAR}`/`$VAR` environment reference; it is resolved just before the sandboxed process routes through the proxy. The /sandbox dialog stores a real password in the OS keychain and persists only a `${secret:…}` placeholder (never plaintext in settings.json); the field is masked in the dialog and redacted by /settings show. */ + @JsonProperty("password") String password +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicySeatbelt.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicySeatbelt.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicySeatbelt.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SandboxConfigUserPolicySeatbelt.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ScheduleEntry.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ScheduleEntry.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ScheduleEntry.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ScheduleEntry.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SecretsAddFilterValuesParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SecretsAddFilterValuesParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SecretsAddFilterValuesParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SecretsAddFilterValuesParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SecretsAddFilterValuesResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SecretsAddFilterValuesResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SecretsAddFilterValuesResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SecretsAddFilterValuesResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SendAgentMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SendAgentMode.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SendAgentMode.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SendAgentMode.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SendMessageItem.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SendMessageItem.java similarity index 87% rename from java/src/generated/java/com/github/copilot/generated/rpc/SendMessageItem.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SendMessageItem.java index bdb37cd9f..4a6696c01 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SendMessageItem.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SendMessageItem.java @@ -32,7 +32,7 @@ public record SendMessageItem( @JsonProperty("billable") Boolean billable, /** If set, the request will fail if the named tool is not available when this message is among the user messages at the start of the current exchange */ @JsonProperty("requiredTool") String requiredTool, - /** Optional provenance tag copied to the resulting user.message event. Must match one of three forms: the literal `system`, `command-` for messages originating from a command (e.g. slash command, Mission Control command), or `schedule-` for messages originating from a scheduled job. */ + /** Optional provenance tag copied to the resulting user.message event. Must be `user`, `system`, `command-` for command-originated messages, `schedule-` for scheduled prompts, or `agent-` for prompts sent by another agent. */ @JsonProperty("source") String source ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SendMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SendMode.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SendMode.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SendMode.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ServerAccountApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerAccountApi.java similarity index 82% rename from java/src/generated/java/com/github/copilot/generated/rpc/ServerAccountApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerAccountApi.java index 4b7cf718e..a6ecb0554 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/ServerAccountApi.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerAccountApi.java @@ -29,13 +29,26 @@ public final class ServerAccountApi { /** * Optional GitHub token used to look up quota for a specific user instead of the global auth context. + *

+ * Invokes the method with no params, applying the runtime defaults. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 */ @CopilotExperimental public CompletableFuture getQuota() { - return caller.invoke("account.getQuota", java.util.Map.of(), AccountGetQuotaResult.class); + return getQuota(null); + } + + /** + * Optional GitHub token used to look up quota for a specific user instead of the global auth context. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture getQuota(AccountGetQuotaParams params) { + return caller.invoke("account.getQuota", params == null ? java.util.Map.of() : params, AccountGetQuotaResult.class); } /** diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ServerAgentRegistryApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerAgentRegistryApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ServerAgentRegistryApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerAgentRegistryApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ServerAgentsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerAgentsApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ServerAgentsApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerAgentsApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ServerCommandsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerCommandsApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ServerCommandsApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerCommandsApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerExtensionsApi.java similarity index 55% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerExtensionsApi.java index 9b880e134..7bc74b441 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueApi.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerExtensionsApi.java @@ -12,53 +12,51 @@ import javax.annotation.processing.Generated; /** - * API methods for the {@code queue} namespace. + * API methods for the {@code extensions} namespace. * * @since 1.0.0 */ @javax.annotation.processing.Generated("copilot-sdk-codegen") -public final class SessionQueueApi { +public final class ServerExtensionsApi { private final RpcCaller caller; - private final String sessionId; /** @param caller the RPC transport function */ - SessionQueueApi(RpcCaller caller, String sessionId) { + ServerExtensionsApi(RpcCaller caller) { this.caller = caller; - this.sessionId = sessionId; } /** - * Identifies the target session. + * Extensions discovered from persisted Copilot home state and their effective loading mode. Launch-scoped additional plugins are not included. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 */ @CopilotExperimental - public CompletableFuture pendingItems() { - return caller.invoke("session.queue.pendingItems", java.util.Map.of("sessionId", this.sessionId), SessionQueuePendingItemsResult.class); + public CompletableFuture discover() { + return caller.invoke("extensions.discover", java.util.Map.of(), ExtensionsDiscoverResult.class); } /** - * Identifies the target session. + * Source-qualified extension identifiers to persistently enable for future sessions. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 */ @CopilotExperimental - public CompletableFuture removeMostRecent() { - return caller.invoke("session.queue.removeMostRecent", java.util.Map.of("sessionId", this.sessionId), SessionQueueRemoveMostRecentResult.class); + public CompletableFuture enable(ExtensionsEnableParams params) { + return caller.invoke("extensions.enable", params, Void.class); } /** - * Identifies the target session. + * Source-qualified extension identifiers to persistently disable for future sessions. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 */ @CopilotExperimental - public CompletableFuture clear() { - return caller.invoke("session.queue.clear", java.util.Map.of("sessionId", this.sessionId), Void.class); + public CompletableFuture disable(ExtensionsDisableParams params) { + return caller.invoke("extensions.disable", params, Void.class); } } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ServerInstructionsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerInstructionsApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ServerInstructionsApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerInstructionsApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ServerLlmInferenceApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerLlmInferenceApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ServerLlmInferenceApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerLlmInferenceApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ServerModelsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerManagedSettingsApi.java similarity index 68% rename from java/src/generated/java/com/github/copilot/generated/rpc/ServerModelsApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerManagedSettingsApi.java index 64ccc62cd..e85b7b987 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/ServerModelsApi.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerManagedSettingsApi.java @@ -12,29 +12,29 @@ import javax.annotation.processing.Generated; /** - * API methods for the {@code models} namespace. + * API methods for the {@code managedSettings} namespace. * * @since 1.0.0 */ @javax.annotation.processing.Generated("copilot-sdk-codegen") -public final class ServerModelsApi { +public final class ServerManagedSettingsApi { private final RpcCaller caller; /** @param caller the RPC transport function */ - ServerModelsApi(RpcCaller caller) { + ServerManagedSettingsApi(RpcCaller caller) { this.caller = caller; } /** - * Optional GitHub token used to list models for a specific user instead of the global auth context. + * Validated device-managed settings discovered before a session exists. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 */ @CopilotExperimental - public CompletableFuture list() { - return caller.invoke("models.list", java.util.Map.of(), ModelsListResult.class); + public CompletableFuture read() { + return caller.invoke("managedSettings.read", java.util.Map.of(), ManagedSettingsReadResult.class); } } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ServerMcpApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerMcpApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ServerMcpApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerMcpApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ServerMcpConfigApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerMcpConfigApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ServerMcpConfigApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerMcpConfigApi.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerModelsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerModelsApi.java new file mode 100644 index 000000000..0b1497970 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerModelsApi.java @@ -0,0 +1,64 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.github.copilot.CopilotExperimental; +import java.util.concurrent.CompletableFuture; +import javax.annotation.processing.Generated; + +/** + * API methods for the {@code models} namespace. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class ServerModelsApi { + + private final RpcCaller caller; + + /** @param caller the RPC transport function */ + ServerModelsApi(RpcCaller caller) { + this.caller = caller; + } + + /** + * Optional GitHub token used to list models for a specific user instead of the global auth context. + *

+ * Invokes the method with no params, applying the runtime defaults. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture list() { + return list(null); + } + + /** + * Optional GitHub token used to list models for a specific user instead of the global auth context. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture list(ModelsListParams params) { + return caller.invoke("models.list", params == null ? java.util.Map.of() : params, ModelsListResult.class); + } + + /** + * The running runtime's complete catalog of well-known built-in model IDs, including supported models and additional IDs with built-in metadata. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture getBuiltInCatalog() { + return caller.invoke("models.getBuiltInCatalog", java.util.Map.of(), ModelsGetBuiltInCatalogResult.class); + } + +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ServerPluginsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerPluginsApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ServerPluginsApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerPluginsApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ServerPluginsMarketplacesApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerPluginsMarketplacesApi.java similarity index 83% rename from java/src/generated/java/com/github/copilot/generated/rpc/ServerPluginsMarketplacesApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerPluginsMarketplacesApi.java index 47b239a0c..e01bbeeec 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/ServerPluginsMarketplacesApi.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerPluginsMarketplacesApi.java @@ -72,13 +72,26 @@ public CompletableFuture browse(PluginsMarketpl /** * Optional marketplace name; omit to refresh all. + *

+ * Invokes the method with no params, applying the runtime defaults. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 */ @CopilotExperimental public CompletableFuture refresh() { - return caller.invoke("plugins.marketplaces.refresh", java.util.Map.of(), PluginsMarketplacesRefreshResult.class); + return refresh(null); + } + + /** + * Optional marketplace name; omit to refresh all. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture refresh(PluginsMarketplacesRefreshParams params) { + return caller.invoke("plugins.marketplaces.refresh", params == null ? java.util.Map.of() : params, PluginsMarketplacesRefreshResult.class); } } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ServerRpc.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerRpc.java similarity index 85% rename from java/src/generated/java/com/github/copilot/generated/rpc/ServerRpc.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerRpc.java index 033fe8bf3..c01545a18 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/ServerRpc.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerRpc.java @@ -35,6 +35,8 @@ public final class ServerRpc { public final ServerSecretsApi secrets; /** API methods for the {@code mcp} namespace. */ public final ServerMcpApi mcp; + /** API methods for the {@code extensions} namespace. */ + public final ServerExtensionsApi extensions; /** API methods for the {@code plugins} namespace. */ public final ServerPluginsApi plugins; /** API methods for the {@code skills} namespace. */ @@ -47,6 +49,8 @@ public final class ServerRpc { public final ServerCommandsApi commands; /** API methods for the {@code user} namespace. */ public final ServerUserApi user; + /** API methods for the {@code managedSettings} namespace. */ + public final ServerManagedSettingsApi managedSettings; /** API methods for the {@code runtime} namespace. */ public final ServerRuntimeApi runtime; /** API methods for the {@code sessionFs} namespace. */ @@ -70,12 +74,14 @@ public ServerRpc(RpcCaller caller) { this.account = new ServerAccountApi(caller); this.secrets = new ServerSecretsApi(caller); this.mcp = new ServerMcpApi(caller); + this.extensions = new ServerExtensionsApi(caller); this.plugins = new ServerPluginsApi(caller); this.skills = new ServerSkillsApi(caller); this.agents = new ServerAgentsApi(caller); this.instructions = new ServerInstructionsApi(caller); this.commands = new ServerCommandsApi(caller); this.user = new ServerUserApi(caller); + this.managedSettings = new ServerManagedSettingsApi(caller); this.runtime = new ServerRuntimeApi(caller); this.sessionFs = new ServerSessionFsApi(caller); this.llmInference = new ServerLlmInferenceApi(caller); @@ -105,4 +111,15 @@ public CompletableFuture connect(ConnectParams params) { return caller.invoke("connect", params, ConnectResult.class); } + /** + * Invokes {@code registerExtensionLaunchProvider}. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture registerExtensionLaunchProvider() { + return caller.invoke("registerExtensionLaunchProvider", java.util.Map.of(), Void.class); + } + } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ServerRuntimeApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerRuntimeApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ServerRuntimeApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerRuntimeApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ServerSecretsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSecretsApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ServerSecretsApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSecretsApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ServerSessionFsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSessionFsApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ServerSessionFsApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSessionFsApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ServerSessionsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSessionsApi.java similarity index 83% rename from java/src/generated/java/com/github/copilot/generated/rpc/ServerSessionsApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSessionsApi.java index 7e3b4d90d..52481a7d7 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/ServerSessionsApi.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSessionsApi.java @@ -33,8 +33,8 @@ public final class ServerSessionsApi { * @since 1.0.0 */ @CopilotExperimental - public CompletableFuture open() { - return caller.invoke("sessions.open", java.util.Map.of(), SessionsOpenResult.class); + public CompletableFuture open(SessionsOpenParams params) { + return caller.invoke("sessions.open", params, SessionsOpenResult.class); } /** @@ -61,13 +61,48 @@ public CompletableFuture connect(SessionsConnectParams pa /** * Optional source filter, metadata-load limit, and context filter applied to the returned sessions. + *

+ * Invokes the method with no params, applying the runtime defaults. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 */ @CopilotExperimental public CompletableFuture list() { - return caller.invoke("sessions.list", java.util.Map.of(), SessionsListResult.class); + return list(null); + } + + /** + * Optional source filter, metadata-load limit, and context filter applied to the returned sessions. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture list(SessionsListParams params) { + return caller.invoke("sessions.list", params == null ? java.util.Map.of() : params, SessionsListResult.class); + } + + /** + * Session ID whose persisted metadata should be read. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture getMetadata(SessionsGetMetadataParams params) { + return caller.invoke("sessions.getMetadata", params, SessionsGetMetadataResult.class); + } + + /** + * Limit for non-empty local session IDs. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture listNonEmptySessionIds(SessionsListNonEmptySessionIdsParams params) { + return caller.invoke("sessions.listNonEmptySessionIds", params, SessionsListNonEmptySessionIdsResult.class); } /** @@ -169,6 +204,17 @@ public CompletableFuture bulkDelete(SessionsBulkDelete return caller.invoke("sessions.bulkDelete", params, SessionsBulkDeleteResult.class); } + /** + * Session ID to delete from disk. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture delete(SessionsDeleteParams params) { + return caller.invoke("sessions.delete", params, Void.class); + } + /** * Age threshold and optional flags controlling which old sessions are pruned (or simulated when dryRun is true). * @@ -292,13 +338,26 @@ public CompletableFuture setRemoteContro /** * Parameters for stopping the remote-control singleton. + *

+ * Invokes the method with no params, applying the runtime defaults. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 */ @CopilotExperimental public CompletableFuture stopRemoteControl() { - return caller.invoke("sessions.stopRemoteControl", java.util.Map.of(), SessionsStopRemoteControlResult.class); + return stopRemoteControl(null); + } + + /** + * Parameters for stopping the remote-control singleton. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture stopRemoteControl(SessionsStopRemoteControlParams params) { + return caller.invoke("sessions.stopRemoteControl", params == null ? java.util.Map.of() : params, SessionsStopRemoteControlResult.class); } /** diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ServerSkill.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSkill.java similarity index 92% rename from java/src/generated/java/com/github/copilot/generated/rpc/ServerSkill.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSkill.java index 349824526..b1d409d9d 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/ServerSkill.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSkill.java @@ -23,6 +23,8 @@ public record ServerSkill( /** Unique identifier for the skill */ @JsonProperty("name") String name, + /** Canonical slash command name used to invoke the skill, without the leading '/' */ + @JsonProperty("commandName") String commandName, /** Description of what the skill does */ @JsonProperty("description") String description, /** Source location type (e.g., project, personal-copilot, plugin, builtin) */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ServerSkillsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSkillsApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ServerSkillsApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSkillsApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ServerSkillsConfigApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSkillsConfigApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ServerSkillsConfigApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerSkillsConfigApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ServerToolsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerToolsApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ServerToolsApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerToolsApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ServerUserApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerUserApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ServerUserApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerUserApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ServerUserSettingsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerUserSettingsApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ServerUserSettingsApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ServerUserSettingsApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionAbortParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAbortParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionAbortParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAbortParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionAbortResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAbortResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionAbortResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAbortResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionAgentApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAgentApi.java similarity index 66% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionAgentApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAgentApi.java index 37d6dafce..d2499fe3a 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionAgentApi.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAgentApi.java @@ -31,14 +31,48 @@ public final class SessionAgentApi { } /** - * Identifies the target session. + * Controls whether built-in agents and authored prompt text are included. + *

+ * Invokes the method with no params, applying the runtime defaults. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 */ @CopilotExperimental public CompletableFuture list() { - return caller.invoke("session.agent.list", java.util.Map.of("sessionId", this.sessionId), SessionAgentListResult.class); + return list(null); + } + + /** + * Controls whether built-in agents and authored prompt text are included. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture list(SessionAgentListParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = params == null ? MAPPER.createObjectNode() : MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.agent.list", _p, SessionAgentListResult.class); + } + + /** + * An in-memory authored prompt override for an available agent. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture setPrompt(SessionAgentSetPromptParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.agent.setPrompt", _p, Void.class); } /** diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionAgentDeselectParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAgentDeselectParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionAgentDeselectParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAgentDeselectParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionAgentGetCurrentParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAgentGetCurrentParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionAgentGetCurrentParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAgentGetCurrentParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionAgentGetCurrentResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAgentGetCurrentResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionAgentGetCurrentResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAgentGetCurrentResult.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAgentListParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAgentListParams.java new file mode 100644 index 000000000..00743cff6 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAgentListParams.java @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Request parameters for the {@code session.agent.list} RPC method. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionAgentListParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** When true, request the session's configured built-in agents alongside custom agents. Listing applies feature, context, inclusion, exclusion, and user-disabled-agent policy, but does not evaluate transient invocation requirements such as model availability. Built-in metadata may be omitted when the session cannot project it, such as a relay session. */ + @JsonProperty("includeBuiltInAgents") Boolean includeBuiltInAgents, + /** When true, request authored base prompt text on each AgentInfo. Prompt text may be omitted when unavailable, such as for agents projected through a relay session. */ + @JsonProperty("includePrompt") Boolean includePrompt +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionAgentListResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAgentListResult.java similarity index 92% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionAgentListResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAgentListResult.java index acd240285..3eefc2fd8 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionAgentListResult.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAgentListResult.java @@ -15,7 +15,7 @@ import javax.annotation.processing.Generated; /** - * Custom agents available to the session. + * Agents available to the session. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 @@ -25,7 +25,7 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record SessionAgentListResult( - /** Available custom agents */ + /** Available agents */ @JsonProperty("agents") List agents ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionAgentReloadParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAgentReloadParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionAgentReloadParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAgentReloadParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionAgentReloadResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAgentReloadResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionAgentReloadResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAgentReloadResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionAgentSelectParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAgentSelectParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionAgentSelectParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAgentSelectParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionAgentSelectResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAgentSelectResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionAgentSelectResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAgentSelectResult.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAgentSetPromptParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAgentSetPromptParams.java new file mode 100644 index 000000000..4395a195e --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionAgentSetPromptParams.java @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * An in-memory authored prompt override for an available agent. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionAgentSetPromptParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Stable effective agent id. Plugin namespace separators are normalized. */ + @JsonProperty("id") String id, + /** Replacement authored prompt. Empty text is valid. */ + @JsonProperty("prompt") String prompt +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPluginsReloadParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCancelAllBackgroundAgentsParams.java similarity index 89% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPluginsReloadParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCancelAllBackgroundAgentsParams.java index f48c7ee88..0851f331e 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPluginsReloadParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCancelAllBackgroundAgentsParams.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Optional flags controlling which side effects the reload performs. + * Identifies the target session. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 @@ -23,7 +23,7 @@ @javax.annotation.processing.Generated("copilot-sdk-codegen") @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) -public record SessionPluginsReloadParams( +public record SessionCancelAllBackgroundAgentsParams( /** Target session identifier */ @JsonProperty("sessionId") String sessionId ) { diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasActionApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasActionApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasActionApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasActionApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasActionInvokeParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasActionInvokeParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasActionInvokeParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasActionInvokeParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasActionInvokeResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasActionInvokeResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasActionInvokeResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasActionInvokeResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasCloseParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasCloseParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasCloseParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasCloseParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasListOpenParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasListOpenParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasListOpenParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasListOpenParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasListOpenResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasListOpenResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasListOpenResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasListOpenResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasListParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasListParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasListParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasListParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasListResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasListResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasListResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasListResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasOpenParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasOpenParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasOpenParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasOpenParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasOpenResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasOpenResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasOpenResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCanvasOpenResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionCapability.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCapability.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionCapability.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCapability.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsApi.java similarity index 85% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsApi.java index 0cb7bb551..facb3fcfc 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsApi.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsApi.java @@ -32,13 +32,31 @@ public final class SessionCommandsApi { /** * Optional filters controlling which command sources to include in the listing. + *

+ * Invokes the method with no params, applying the runtime defaults. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 */ @CopilotExperimental public CompletableFuture list() { - return caller.invoke("session.commands.list", java.util.Map.of("sessionId", this.sessionId), SessionCommandsListResult.class); + return list(null); + } + + /** + * Optional filters controlling which command sources to include in the listing. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture list(SessionCommandsListParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = params == null ? MAPPER.createObjectNode() : MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.commands.list", _p, SessionCommandsListResult.class); } /** diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsEnqueueParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsEnqueueParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsEnqueueParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsEnqueueParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsEnqueueResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsEnqueueResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsEnqueueResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsEnqueueResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsExecuteParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsExecuteParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsExecuteParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsExecuteParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsExecuteResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsExecuteResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsExecuteResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsExecuteResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsHandlePendingCommandParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsHandlePendingCommandParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsHandlePendingCommandParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsHandlePendingCommandParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsHandlePendingCommandResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsHandlePendingCommandResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsHandlePendingCommandResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsHandlePendingCommandResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsInvokeParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsInvokeParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsInvokeParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsInvokeParams.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsListParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsListParams.java new file mode 100644 index 000000000..0e2dd73aa --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsListParams.java @@ -0,0 +1,36 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Request parameters for the {@code session.commands.list} RPC method. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionCommandsListParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Include runtime built-in commands */ + @JsonProperty("includeBuiltins") Boolean includeBuiltins, + /** Include enabled user-invocable skills and commands */ + @JsonProperty("includeSkills") Boolean includeSkills, + /** Include commands registered by protocol clients, including SDK clients and extensions */ + @JsonProperty("includeClientCommands") Boolean includeClientCommands +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsListResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsListResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsListResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsListResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsRespondToQueuedCommandParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsRespondToQueuedCommandParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsRespondToQueuedCommandParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsRespondToQueuedCommandParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsRespondToQueuedCommandResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsRespondToQueuedCommandResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsRespondToQueuedCommandResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsRespondToQueuedCommandResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionCompletionItem.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCompletionItem.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionCompletionItem.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCompletionItem.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionCompletionsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCompletionsApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionCompletionsApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCompletionsApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionCompletionsGetTriggerCharactersParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCompletionsGetTriggerCharactersParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionCompletionsGetTriggerCharactersParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCompletionsGetTriggerCharactersParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionCompletionsGetTriggerCharactersResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCompletionsGetTriggerCharactersResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionCompletionsGetTriggerCharactersResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCompletionsGetTriggerCharactersResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionCompletionsRequestParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCompletionsRequestParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionCompletionsRequestParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCompletionsRequestParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionCompletionsRequestResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCompletionsRequestResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionCompletionsRequestResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionCompletionsRequestResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionContentExclusionApi.java similarity index 63% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionContentExclusionApi.java index 2e42c1bc3..eb621e6b4 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleApi.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionContentExclusionApi.java @@ -12,12 +12,12 @@ import javax.annotation.processing.Generated; /** - * API methods for the {@code schedule} namespace. + * API methods for the {@code contentExclusion} namespace. * * @since 1.0.0 */ @javax.annotation.processing.Generated("copilot-sdk-codegen") -public final class SessionScheduleApi { +public final class SessionContentExclusionApi { private static final com.fasterxml.jackson.databind.ObjectMapper MAPPER = RpcMapper.INSTANCE; @@ -25,24 +25,13 @@ public final class SessionScheduleApi { private final String sessionId; /** @param caller the RPC transport function */ - SessionScheduleApi(RpcCaller caller, String sessionId) { + SessionContentExclusionApi(RpcCaller caller, String sessionId) { this.caller = caller; this.sessionId = sessionId; } /** - * Identifies the target session. - * - * @apiNote This method is experimental and may change in a future version. - * @since 1.0.0 - */ - @CopilotExperimental - public CompletableFuture list() { - return caller.invoke("session.schedule.list", java.util.Map.of("sessionId", this.sessionId), SessionScheduleListResult.class); - } - - /** - * Identifier of the scheduled prompt to remove. + * Local file system absolute paths within the session working directory to check against its content-exclusion policy. *

* Note: the {@code sessionId} field in the params record is overridden * by the session-scoped wrapper; any value provided is ignored. @@ -51,10 +40,10 @@ public CompletableFuture list() { * @since 1.0.0 */ @CopilotExperimental - public CompletableFuture stop(SessionScheduleStopParams params) { + public CompletableFuture checkPaths(SessionContentExclusionCheckPathsParams params) { com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); _p.put("sessionId", this.sessionId); - return caller.invoke("session.schedule.stop", _p, SessionScheduleStopResult.class); + return caller.invoke("session.contentExclusion.checkPaths", _p, SessionContentExclusionCheckPathsResult.class); } } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionContentExclusionCheckPathsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionContentExclusionCheckPathsParams.java new file mode 100644 index 000000000..0c61f6c91 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionContentExclusionCheckPathsParams.java @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * Local file system absolute paths within the session working directory to check against its content-exclusion policy. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionContentExclusionCheckPathsParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Local file system absolute paths within the session working directory to check. Results are returned in the same order, including duplicates. */ + @JsonProperty("paths") List paths +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionContentExclusionCheckPathsResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionContentExclusionCheckPathsResult.java new file mode 100644 index 000000000..956ffc44d --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionContentExclusionCheckPathsResult.java @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * Batch content-exclusion result. Callers must fail closed when policy evaluation is unavailable. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionContentExclusionCheckPathsResult( + /** Whether the session's policy service was available for the complete batch. When false, checks is empty and callers must treat every requested path as excluded. */ + @JsonProperty("available") Boolean available, + /** Per-path decisions in request order. Empty when available is false. */ + @JsonProperty("checks") List checks +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionContext.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionContext.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionContext.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionContext.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionContextHostType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionContextHostType.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionContextHostType.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionContextHostType.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionDebugApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionDebugApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionDebugApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionDebugApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionDebugCollectLogsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionDebugCollectLogsParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionDebugCollectLogsParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionDebugCollectLogsParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionDebugCollectLogsResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionDebugCollectLogsResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionDebugCollectLogsResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionDebugCollectLogsResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogReadParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogReadParams.java similarity index 50% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogReadParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogReadParams.java index d330d616d..bbc5abb7c 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogReadParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogReadParams.java @@ -11,6 +11,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.github.copilot.CopilotExperimental; +import java.util.List; import javax.annotation.processing.Generated; /** @@ -30,11 +31,17 @@ public record SessionEventLogReadParams( @JsonProperty("cursor") String cursor, /** Maximum number of events to return in this batch (1–1000, default 200). */ @JsonProperty("max") Long max, - /** Milliseconds to wait for new events when the cursor is at the tail of history. 0 (default) returns immediately even if no events are available. Capped at 30000ms. Ephemeral events that arrive during the wait are delivered in this batch but are NOT replayable on a subsequent read (use a non-zero waitMs in your next call to capture future ephemerals as they happen). */ + /** Milliseconds to wait for new events when the cursor is at the tail of history. 0 (default) returns immediately even if no events are available. Capped at 30000ms. Ephemeral events that arrive during the wait are delivered in this batch but are NOT replayable on a subsequent read (use a non-zero waitMs in your next call to capture future ephemerals as they happen). This applies to forward reads only: a backward read always returns immediately and ignores `waitMs`, because backward paging covers persisted history only while new events append at the tail (the opposite end from a backward page), so no blocking or ephemeral delivery can occur. */ @JsonProperty("waitMs") Long waitMs, /** Either '*' to receive all event types, or a non-empty list of event types to receive */ @JsonProperty("types") Object types, /** Agent-scope filter: 'primary' returns only main-agent events plus events whose type starts with 'subagent.' (matching the typed-subscription default behavior); 'all' returns events from all agents (matching wildcard-subscription behavior). Default is 'all' to preserve wildcard semantics for catch-up callers. */ - @JsonProperty("agentScope") EventsAgentScope agentScope + @JsonProperty("agentScope") EventsAgentScope agentScope, + /** Optional non-empty list of subagent identifiers. When provided, only events owned by one of these agents are returned; ownership recognizes the event envelope's agentId plus legacy data.agentId and data.parentToolCallId markers. This filter takes precedence over agentScope. */ + @JsonProperty("agentIds") List agentIds, + /** Direction to page through the session's persisted event history. 'forward' (default) pages from the cursor toward newer events (or from the start of history when no cursor is given). 'backward' enables tail-first reads: with no cursor it returns the NEWEST `max` events, and the returned cursor pages toward OLDER events on subsequent backward reads. Events within a returned batch are always in chronological (oldest-to-newest) order, even for a backward read. Backward reads cover PERSISTED history only; ephemeral events are never returned by a backward read. `direction` selects the INITIAL read only: the returned cursor is self-describing, so a continuation read pages in the cursor's own direction regardless of the `direction` passed alongside it — a forward cursor always pages forward and a backward cursor always pages backward. Pass the direction that matches the cursor to avoid confusion. */ + @JsonProperty("direction") EventsReadDirection direction, + /** When false, skip ephemeral events entirely and return only durable (persisted) events. History-backfill callers that discard ephemerals anyway should set this so the read is bounded by the durable log length instead of racing the ephemeral ring on a busy session. Defaults to true (ephemerals are interleaved with durable events in creation order). Ignored by backward reads, which always cover persisted history only. */ + @JsonProperty("includeEphemeral") Boolean includeEphemeral ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogReadResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogReadResult.java new file mode 100644 index 000000000..767acc879 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogReadResult.java @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * Batch of session events returned by a read, with cursor and continuation metadata. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionEventLogReadResult( + /** Session events for this batch, merged into a single stream in creation order: durable (persisted) events and ephemeral events interleave exactly as they were emitted. Set `includeEphemeral: false` to receive only durable events. Ephemeral events are never replayable once pruned from the in-memory ring, so a consumer that needs them should keep reading with a non-zero `waitMs`. For a backward (tail-first) read, the returned window contains persisted events only, still in chronological (oldest-to-newest) append order. */ + @JsonProperty("events") List events, + /** Opaque cursor for the next read. Pass back unchanged in the next read.cursor to continue from where this read left off. Always present, even when no events were returned. For a backward read this cursor pages toward OLDER events; keep passing `direction: backward` with it (the cursor is also self-describing, so backward paging continues correctly). */ + @JsonProperty("cursor") String cursor, + /** True when more events are available in the read's direction. For a forward read, true means the batch returned `max` events and more are available immediately. For a backward read, true means older persisted events remain before the returned window. */ + @JsonProperty("hasMore") Boolean hasMore, + /** Cursor status: 'ok' means the cursor was applied successfully; 'expired' means the cursor referred to an event that no longer exists in history (e.g. truncated or compacted away) and the read fell back to a boundary of the remaining history. For a forward read the fallback starts from the beginning of the remaining history; for a backward read it falls back to the tail (the newest window). Because the fallback page is a fresh boundary snapshot rather than a continuation of the requested cursor, it may overlap events the consumer has already rendered — a backward fallback to the tail in particular can repeat the newest window. On 'expired', consumers should reset or rebase their local pagination state (or deduplicate by event id) before continuing from the returned cursor rather than blindly appending/prepending the fallback page. */ + @JsonProperty("cursorStatus") EventsCursorStatus cursorStatus +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogRegisterInterestParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogRegisterInterestParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogRegisterInterestParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogRegisterInterestParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogRegisterInterestResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogRegisterInterestResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogRegisterInterestResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogRegisterInterestResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogReleaseInterestParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogReleaseInterestParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogReleaseInterestParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogReleaseInterestParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogReleaseInterestResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogReleaseInterestResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogReleaseInterestResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogReleaseInterestResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogTailParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogTailParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogTailParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogTailParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogTailResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogTailResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogTailResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogTailResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionExtensionsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionExtensionsApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionExtensionsApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionExtensionsApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionExtensionsDisableParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionExtensionsDisableParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionExtensionsDisableParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionExtensionsDisableParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionExtensionsEnableParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionExtensionsEnableParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionExtensionsEnableParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionExtensionsEnableParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionExtensionsListParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionExtensionsListParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionExtensionsListParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionExtensionsListParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionExtensionsListResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionExtensionsListResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionExtensionsListResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionExtensionsListResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionExtensionsReloadParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionExtensionsReloadParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionExtensionsReloadParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionExtensionsReloadParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionExtensionsSendAttachmentsToMessageParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionExtensionsSendAttachmentsToMessageParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionExtensionsSendAttachmentsToMessageParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionExtensionsSendAttachmentsToMessageParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryAgentParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryAgentParams.java similarity index 91% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryAgentParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryAgentParams.java index 7f31066fc..6ab02c27e 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryAgentParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryAgentParams.java @@ -28,6 +28,8 @@ public record SessionFactoryAgentParams( @JsonProperty("sessionId") String sessionId, /** Factory run identifier that owns the subagent. */ @JsonProperty("factoryRunId") String factoryRunId, + /** Opaque token identifying the current factory execution attempt. */ + @JsonProperty("executionToken") String executionToken, /** Prompt to send to the subagent. */ @JsonProperty("prompt") String prompt, /** Subagent execution options. */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryAgentResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryAgentResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryAgentResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryAgentResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryApi.java similarity index 61% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryApi.java index 703a2e711..e0628ea3d 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryApi.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryApi.java @@ -50,6 +50,22 @@ public CompletableFuture run(SessionFactoryRunParams pa return caller.invoke("session.factory.run", _p, SessionFactoryRunResult.class); } + /** + * Parameters for resuming a factory run from its persisted identity. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture resume(SessionFactoryResumeParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.factory.resume", _p, SessionFactoryResumeResult.class); + } + /** * Parameters for retrieving a factory run. *

@@ -66,6 +82,54 @@ public CompletableFuture getRun(SessionFactoryGetRun return caller.invoke("session.factory.getRun", _p, SessionFactoryGetRunResult.class); } + /** + * Parameters for paging factory runs. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture listRuns(SessionFactoryListRunsParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.factory.listRuns", _p, SessionFactoryListRunsResult.class); + } + + /** + * Parameters for retrieving a factory run. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture getRunDetail(SessionFactoryGetRunDetailParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.factory.getRunDetail", _p, SessionFactoryGetRunDetailResult.class); + } + + /** + * Parameters for paging factory progress. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture getRunProgress(SessionFactoryGetRunProgressParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.factory.getRunProgress", _p, SessionFactoryGetRunProgressResult.class); + } + /** * Parameters for cancelling a factory run. *

diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryCancelParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryCancelParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryCancelParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryCancelParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryCancelResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryCancelResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryCancelResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryCancelResult.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunDetailParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunDetailParams.java new file mode 100644 index 000000000..b4563d4b3 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunDetailParams.java @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Parameters for retrieving a factory run. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionFactoryGetRunDetailParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Factory run identifier. */ + @JsonProperty("runId") String runId +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunDetailResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunDetailResult.java new file mode 100644 index 000000000..5da6f4979 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunDetailResult.java @@ -0,0 +1,51 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * Full factory run observability detail. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionFactoryGetRunDetailResult( + @JsonProperty("runId") String runId, + @JsonProperty("factoryName") String factoryName, + @JsonProperty("description") String description, + @JsonProperty("status") FactoryRunStatus status, + @JsonProperty("revision") Long revision, + @JsonProperty("createdAt") Long createdAt, + @JsonProperty("startedAt") Long startedAt, + @JsonProperty("updatedAt") Long updatedAt, + @JsonProperty("completedAt") Long completedAt, + @JsonProperty("currentPhase") FactoryCurrentPhase currentPhase, + @JsonProperty("declaredPhaseCount") Long declaredPhaseCount, + @JsonProperty("liveAgentCount") Long liveAgentCount, + @JsonProperty("totalSpawnedAgentCount") Long totalSpawnedAgentCount, + @JsonProperty("consumed") FactoryRunConsumed consumed, + @JsonProperty("declaredLimits") FactoryDeclaredLimits declaredLimits, + @JsonProperty("approved") FactoryDeclaredLimits approved, + @JsonProperty("observedAt") Long observedAt, + @JsonProperty("activeSegmentStartedAt") Long activeSegmentStartedAt, + @JsonProperty("terminal") FactoryRunTerminal terminal, + @JsonProperty("phases") List phases, + @JsonProperty("agents") List agents, + @JsonProperty("progress") FactoryProgressPage progress +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunParams.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunProgressParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunProgressParams.java new file mode 100644 index 000000000..8445943cb --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunProgressParams.java @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Parameters for paging factory progress. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionFactoryGetRunProgressParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Factory run identifier. */ + @JsonProperty("runId") String runId, + /** Optional phase identifier used to scope records and cursors. */ + @JsonProperty("phaseId") String phaseId, + /** Exclusive forward cursor. */ + @JsonProperty("afterSeq") Long afterSeq, + /** Exclusive backward cursor. */ + @JsonProperty("beforeSeq") Long beforeSeq, + /** Maximum records to return. Defaults to 200 and is capped at 500. */ + @JsonProperty("limit") Long limit +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunProgressResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunProgressResult.java new file mode 100644 index 000000000..369fa07c2 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunProgressResult.java @@ -0,0 +1,36 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * A bidirectional page of factory progress. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionFactoryGetRunProgressResult( + @JsonProperty("records") List records, + @JsonProperty("oldestSeq") Long oldestSeq, + @JsonProperty("newestSeq") Long newestSeq, + @JsonProperty("hasMoreOlder") Boolean hasMoreOlder, + @JsonProperty("hasMoreNewer") Boolean hasMoreNewer, + /** Run revision reflected by this page. */ + @JsonProperty("revision") Long revision +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryGetRunResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryJournalApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryJournalApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryJournalApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryJournalApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryJournalGetParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryJournalGetParams.java similarity index 90% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryJournalGetParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryJournalGetParams.java index a9b0acc7c..251ca946c 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryJournalGetParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryJournalGetParams.java @@ -28,6 +28,8 @@ public record SessionFactoryJournalGetParams( @JsonProperty("sessionId") String sessionId, /** Factory run identifier. */ @JsonProperty("runId") String runId, + /** Opaque token identifying the current factory execution attempt. */ + @JsonProperty("executionToken") String executionToken, /** Namespaced journal key. */ @JsonProperty("key") String key ) { diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryJournalGetResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryJournalGetResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryJournalGetResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryJournalGetResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryJournalPutParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryJournalPutParams.java similarity index 90% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryJournalPutParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryJournalPutParams.java index 84478dbb7..06467b265 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryJournalPutParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryJournalPutParams.java @@ -28,6 +28,8 @@ public record SessionFactoryJournalPutParams( @JsonProperty("sessionId") String sessionId, /** Factory run identifier. */ @JsonProperty("runId") String runId, + /** Opaque token identifying the current factory execution attempt. */ + @JsonProperty("executionToken") String executionToken, /** Namespaced journal key. */ @JsonProperty("key") String key, /** JSON result to memoize. */ diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryListRunsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryListRunsParams.java new file mode 100644 index 000000000..41de4ae4a --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryListRunsParams.java @@ -0,0 +1,36 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Parameters for paging factory runs. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionFactoryListRunsParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Exclusive forward cursor. */ + @JsonProperty("afterSeq") Long afterSeq, + /** Exclusive backward cursor. */ + @JsonProperty("beforeSeq") Long beforeSeq, + /** Maximum terminal runs to return. Defaults to 200 and is capped at 500. */ + @JsonProperty("limit") Long limit +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryListRunsResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryListRunsResult.java new file mode 100644 index 000000000..3a23bc369 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryListRunsResult.java @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * A page of factory runs in durable creation order. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionFactoryListRunsResult( + @JsonProperty("runs") List runs, + /** Oldest terminal-run cursor in this page, or null when the terminal window is empty. */ + @JsonProperty("oldestSeq") Long oldestSeq, + /** Newest terminal-run cursor in this page, or null when the terminal window is empty. */ + @JsonProperty("newestSeq") Long newestSeq, + /** Whether terminal runs newer than this page exist. */ + @JsonProperty("hasMoreNewer") Boolean hasMoreNewer, + /** Number of terminal runs older than this page. */ + @JsonProperty("omittedOlder") Long omittedOlder +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryLogParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryLogParams.java similarity index 90% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryLogParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryLogParams.java index 7618869e3..b4f52617f 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryLogParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryLogParams.java @@ -29,6 +29,8 @@ public record SessionFactoryLogParams( @JsonProperty("sessionId") String sessionId, /** Factory run identifier. */ @JsonProperty("runId") String runId, + /** Opaque token identifying the current factory execution attempt. */ + @JsonProperty("executionToken") String executionToken, /** Ordered progress lines to append. */ @JsonProperty("lines") List lines ) { diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryResumeParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryResumeParams.java new file mode 100644 index 000000000..9c264284f --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryResumeParams.java @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Parameters for resuming a factory run from its persisted identity. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionFactoryResumeParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Factory run identifier. */ + @JsonProperty("runId") String runId, + /** Optional per-invocation resource ceiling overrides. */ + @JsonProperty("limits") FactoryRunLimits limits +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryResumeResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryResumeResult.java new file mode 100644 index 000000000..b4cfcae11 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryResumeResult.java @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Resolved persisted factory identity and resumed run envelope. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionFactoryResumeResult( + /** Persisted factory name resolved for the resumed run. */ + @JsonProperty("factoryName") String factoryName, + /** Terminal resumed run envelope. */ + @JsonProperty("run") FactoryRunResult run +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryRunParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryRunParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryRunParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryRunParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryRunResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryRunResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryRunResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFactoryRunResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFleetApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFleetApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFleetApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFleetApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFleetStartParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFleetStartParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFleetStartParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFleetStartParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFleetStartResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFleetStartResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFleetStartResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFleetStartResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsAppendFileParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsAppendFileParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFsAppendFileParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsAppendFileParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsError.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsError.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFsError.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsError.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsErrorCode.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsErrorCode.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFsErrorCode.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsErrorCode.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsExistsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsExistsParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFsExistsParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsExistsParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsExistsResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsExistsResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFsExistsResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsExistsResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsMkdirParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsMkdirParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFsMkdirParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsMkdirParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsReadFileParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsReadFileParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFsReadFileParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsReadFileParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsReadFileResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsReadFileResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFsReadFileResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsReadFileResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsReaddirParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsReaddirParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFsReaddirParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsReaddirParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsReaddirResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsReaddirResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFsReaddirResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsReaddirResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsReaddirWithTypesEntry.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsReaddirWithTypesEntry.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFsReaddirWithTypesEntry.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsReaddirWithTypesEntry.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsReaddirWithTypesEntryType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsReaddirWithTypesEntryType.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFsReaddirWithTypesEntryType.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsReaddirWithTypesEntryType.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsReaddirWithTypesParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsReaddirWithTypesParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFsReaddirWithTypesParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsReaddirWithTypesParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsReaddirWithTypesResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsReaddirWithTypesResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFsReaddirWithTypesResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsReaddirWithTypesResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsRenameParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsRenameParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFsRenameParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsRenameParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsRmParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsRmParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFsRmParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsRmParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsSetProviderCapabilities.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSetProviderCapabilities.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFsSetProviderCapabilities.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSetProviderCapabilities.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsSetProviderConventions.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSetProviderConventions.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFsSetProviderConventions.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSetProviderConventions.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsSetProviderParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSetProviderParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFsSetProviderParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSetProviderParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsSetProviderResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSetProviderResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFsSetProviderResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSetProviderResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteExistsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteExistsParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteExistsParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteExistsParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteExistsResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteExistsResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteExistsResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteExistsResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteQueryParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteQueryParams.java similarity index 93% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteQueryParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteQueryParams.java index 47e30ebca..925863588 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteQueryParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteQueryParams.java @@ -15,7 +15,7 @@ import javax.annotation.processing.Generated; /** - * SQL query, query type, and optional bind parameters for executing a SQLite query against the per-session database. + * SQL query, query type, and optional bind parameters for executing a SQLite query against the per-session database. The provider applies its SQLite busy timeout for every call. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteQueryResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteQueryResult.java similarity index 91% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteQueryResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteQueryResult.java index 53370c098..ff14d3ec1 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteQueryResult.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteQueryResult.java @@ -10,7 +10,6 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import com.github.copilot.CopilotExperimental; import java.util.List; import java.util.Map; import javax.annotation.processing.Generated; @@ -18,10 +17,8 @@ /** * Query results including rows, columns, and rows affected, or a filesystem error if execution failed. * - * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 */ -@CopilotExperimental @javax.annotation.processing.Generated("copilot-sdk-codegen") @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteQueryType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteQueryType.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteQueryType.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteQueryType.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionError.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionError.java new file mode 100644 index 000000000..cbe170a29 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionError.java @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Classified SQLite transaction failure. busyOrLocked guarantees rollback; postCommitAmbiguous must never be retried. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionFsSqliteTransactionError( + @JsonProperty("errorClass") SessionFsSqliteTransactionErrorClass errorClass, + @JsonProperty("message") String message +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionErrorClass.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionErrorClass.java new file mode 100644 index 000000000..4e184a19a --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionErrorClass.java @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * SQLite transaction failure classification. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum SessionFsSqliteTransactionErrorClass { + /** The {@code busyOrLocked} variant. */ + BUSYORLOCKED("busyOrLocked"), + /** The {@code fatal} variant. */ + FATAL("fatal"), + /** The {@code postCommitAmbiguous} variant. */ + POSTCOMMITAMBIGUOUS("postCommitAmbiguous"); + + private final String value; + SessionFsSqliteTransactionErrorClass(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static SessionFsSqliteTransactionErrorClass fromValue(String value) { + for (SessionFsSqliteTransactionErrorClass v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown SessionFsSqliteTransactionErrorClass value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionParams.java new file mode 100644 index 000000000..f834d5595 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionParams.java @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * Statements to execute atomically. Providers apply busy handling for every call. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionFsSqliteTransactionParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + @JsonProperty("statements") List statements +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionResult.java new file mode 100644 index 000000000..f9c799b9b --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionResult.java @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * Per-statement results, or a classified transaction error. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionFsSqliteTransactionResult( + @JsonProperty("results") List results, + @JsonProperty("error") SessionFsSqliteTransactionError error +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionStatement.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionStatement.java new file mode 100644 index 000000000..f56f268ba --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsSqliteTransactionStatement.java @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Map; +import javax.annotation.processing.Generated; + +/** + * One statement in an atomic SQLite transaction. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionFsSqliteTransactionStatement( + /** SQL statement to execute. */ + @JsonProperty("query") String query, + /** How to execute the statement. */ + @JsonProperty("queryType") SessionFsSqliteQueryType queryType, + /** Optional named bind parameters. */ + @JsonProperty("params") Map params +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsStatParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsStatParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFsStatParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsStatParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsStatResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsStatResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFsStatResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsStatResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionFsWriteFileParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsWriteFileParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionFsWriteFileParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionFsWriteFileParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthGetStatusParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthGetStatusParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthGetStatusParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthGetStatusParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthGetStatusResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthGetStatusResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthGetStatusResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthGetStatusResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthSetCredentialsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthSetCredentialsParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthSetCredentialsParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthSetCredentialsParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthSetCredentialsResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthSetCredentialsResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthSetCredentialsResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionGitHubAuthSetCredentialsResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryAbortManualCompactionParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryAbortManualCompactionParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryAbortManualCompactionParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryAbortManualCompactionParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryAbortManualCompactionResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryAbortManualCompactionResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryAbortManualCompactionResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryAbortManualCompactionResult.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryApi.java new file mode 100644 index 000000000..ad44d864d --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryApi.java @@ -0,0 +1,170 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.github.copilot.CopilotExperimental; +import java.util.concurrent.CompletableFuture; +import javax.annotation.processing.Generated; + +/** + * API methods for the {@code history} namespace. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SessionHistoryApi { + + private static final com.fasterxml.jackson.databind.ObjectMapper MAPPER = RpcMapper.INSTANCE; + + private final RpcCaller caller; + private final String sessionId; + + /** @param caller the RPC transport function */ + SessionHistoryApi(RpcCaller caller, String sessionId) { + this.caller = caller; + this.sessionId = sessionId; + } + + /** + * Optional compaction parameters. + *

+ * Invokes the method with no params, applying the runtime defaults. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture compact() { + return compact(null); + } + + /** + * Optional compaction parameters. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture compact(SessionHistoryCompactParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = params == null ? MAPPER.createObjectNode() : MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.history.compact", _p, SessionHistoryCompactResult.class); + } + + /** + * Identifier of the event to truncate to; this event and all later events are removed. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture truncate(SessionHistoryTruncateParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.history.truncate", _p, SessionHistoryTruncateResult.class); + } + + /** + * Identifies the target session. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture listRewindPoints() { + return caller.invoke("session.history.listRewindPoints", java.util.Map.of("sessionId", this.sessionId), SessionHistoryListRewindPointsResult.class); + } + + /** + * Event boundary to preview for conversation-and-files rewind. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture previewRewind(SessionHistoryPreviewRewindParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.history.previewRewind", _p, SessionHistoryPreviewRewindResult.class); + } + + /** + * Boundary and mode for rewinding session history. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture rewind(SessionHistoryRewindParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.history.rewind", _p, SessionHistoryRewindResult.class); + } + + /** + * Identifies the target session. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture cancelBackgroundCompaction() { + return caller.invoke("session.history.cancelBackgroundCompaction", java.util.Map.of("sessionId", this.sessionId), SessionHistoryCancelBackgroundCompactionResult.class); + } + + /** + * Identifies the target session. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture abortManualCompaction() { + return caller.invoke("session.history.abortManualCompaction", java.util.Map.of("sessionId", this.sessionId), SessionHistoryAbortManualCompactionResult.class); + } + + /** + * Identifies the target session. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture summarizeForHandoff() { + return caller.invoke("session.history.summarizeForHandoff", java.util.Map.of("sessionId", this.sessionId), SessionHistorySummarizeForHandoffResult.class); + } + + /** + * Parameters for clearing the conversation and seeding the window that replaces it. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture clearContext(SessionHistoryClearContextParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.history.clearContext", _p, SessionHistoryClearContextResult.class); + } + +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryCancelBackgroundCompactionParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryCancelBackgroundCompactionParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryCancelBackgroundCompactionParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryCancelBackgroundCompactionParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryCancelBackgroundCompactionResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryCancelBackgroundCompactionResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryCancelBackgroundCompactionResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryCancelBackgroundCompactionResult.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryClearContextParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryClearContextParams.java new file mode 100644 index 000000000..e52c27ece --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryClearContextParams.java @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Parameters for clearing the conversation and seeding the window that replaces it. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionHistoryClearContextParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** First user message of the fresh context window. Required: a cleared window holding only system and developer messages is not a conversation a model can answer, so every clear seeds the window it creates. Delivered by the enclosing turn driver once the agentic loop exits, which is why the call must be made from inside a tool handler. */ + @JsonProperty("prompt") String prompt +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryClearContextResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryClearContextResult.java new file mode 100644 index 000000000..4b3d8502c --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryClearContextResult.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * What a successful clear removed. A clear that could not be applied rejects instead of reporting a count. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionHistoryClearContextResult( + /** Number of non-system, non-developer messages that were removed from the conversation. Zero only when the window already held no conversation. */ + @JsonProperty("messagesCleared") Long messagesCleared +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryCompactParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryCompactParams.java new file mode 100644 index 000000000..d25c80b55 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryCompactParams.java @@ -0,0 +1,56 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Request parameters for the {@code session.history.compact} RPC method. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionHistoryCompactParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Optional user-provided instructions to focus the compaction summary */ + @JsonProperty("customInstructions") String customInstructions, + /** What initiated this compaction request, recorded as the `trigger` on the persisted `session.compaction_start` / `session.compaction_complete` events. When absent, the compaction is persisted without trigger attribution (initiator unknown). */ + @JsonProperty("trigger") SessionHistoryCompactParamsTrigger trigger, + /** Context window token limit this compaction is targeting, recorded as the `tokenLimit` on the persisted `session.compaction_start` / `session.compaction_complete` events. Set it when the compaction targets a window other than the compacting model's own, e.g. switching to a model with a smaller context window: the compaction still runs on the current model, so the limit that motivated it would otherwise be lost. When absent, the events record the compacting model's own resolved limit. Attribution metadata only - it does not change how much the compaction removes. */ + @JsonProperty("tokenLimit") Long tokenLimit +) { + + /** What initiated this compaction request, recorded as the `trigger` on the persisted `session.compaction_start` / `session.compaction_complete` events. When absent, the compaction is persisted without trigger attribution (initiator unknown). */ + public enum SessionHistoryCompactParamsTrigger { + /** The {@code manual} variant. */ + MANUAL("manual"), + /** The {@code model_switch} variant. */ + MODEL_SWITCH("model_switch"); + + private final String value; + SessionHistoryCompactParamsTrigger(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static SessionHistoryCompactParamsTrigger fromValue(String value) { + for (SessionHistoryCompactParamsTrigger v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown SessionHistoryCompactParamsTrigger value: " + value); + } + } +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryCompactResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryCompactResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryCompactResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryCompactResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionProviderGetEndpointParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryListRewindPointsParams.java similarity index 89% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionProviderGetEndpointParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryListRewindPointsParams.java index 09e992a78..d780d76b3 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionProviderGetEndpointParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryListRewindPointsParams.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Optional model identifier to scope the endpoint snapshot to. + * Identifies the target session. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 @@ -23,7 +23,7 @@ @javax.annotation.processing.Generated("copilot-sdk-codegen") @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) -public record SessionProviderGetEndpointParams( +public record SessionHistoryListRewindPointsParams( /** Target session identifier */ @JsonProperty("sessionId") String sessionId ) { diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryListRewindPointsResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryListRewindPointsResult.java new file mode 100644 index 000000000..99dcc0fab --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryListRewindPointsResult.java @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * Rewind points and file-change-tracking availability for the session. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionHistoryListRewindPointsResult( + /** Whether this session captured file changes from its first turn. */ + @JsonProperty("fileChangeTrackingEnabled") Boolean fileChangeTrackingEnabled, + /** Why the listed points could not be produced, when applicable; the points list is empty whenever it is set. `unsupported-remote-session` is permanent for the session and comes with `fileChangeTrackingEnabled: false`. `session-busy` is transient and only ever reported by a session that *is* tracking (`fileChangeTrackingEnabled: true`), because the file-change captures cannot be read while work that may still mutate them is in flight; the same request succeeds once the session settles, so a client that wants points should retry rather than treat it as a failure. It is never `file-change-tracking-disabled`: an untracked local session still lists conversation-only points and reports that through `fileChangeTrackingEnabled: false`. */ + @JsonProperty("unavailableReason") HistoryRewindUnavailableReason unavailableReason, + /** Root user turns in chronological order. Empty when `unavailableReason` is set. */ + @JsonProperty("points") List points +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryPreviewRewindParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryPreviewRewindParams.java new file mode 100644 index 000000000..dd92709e2 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryPreviewRewindParams.java @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Event boundary to preview for conversation-and-files rewind. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionHistoryPreviewRewindParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** ID of the user.message event that begins the discarded suffix. */ + @JsonProperty("eventId") String eventId +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryPreviewRewindResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryPreviewRewindResult.java new file mode 100644 index 000000000..976597c4f --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryPreviewRewindResult.java @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * Files and aggregate changes for a prospective rewind. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionHistoryPreviewRewindResult( + /** Whether file restore is available for this session. This is authoritative: switch on it and read `reason` only when it is false. */ + @JsonProperty("available") Boolean available, + /** Why file restore is unavailable, when applicable. Populated only when `available` is false and never set when `available` is true. */ + @JsonProperty("reason") HistoryRewindUnavailableReason reason, + /** Number of unique files in the preview. */ + @JsonProperty("fileCount") Long fileCount, + /** Files ordered by path. */ + @JsonProperty("files") List files +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryRewindParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryRewindParams.java new file mode 100644 index 000000000..bf93f3e24 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryRewindParams.java @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Boundary and mode for rewinding session history. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionHistoryRewindParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** ID of the user.message event that begins the discarded suffix. */ + @JsonProperty("eventId") String eventId, + /** Whether to rewind only conversation history or also restore captured files. */ + @JsonProperty("mode") HistoryRewindMode mode +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryRewindResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryRewindResult.java new file mode 100644 index 000000000..d069ca992 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryRewindResult.java @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * Structured outcome of a rewind request. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionHistoryRewindResult( + /** Overall rewind outcome. This discriminates the result: it governs which of the remaining fields are populated, so consumers must switch on it before reading `eventsRemoved`, `restoredFiles`, `skippedFiles`, or `error`. See each field for the outcomes that populate it. */ + @JsonProperty("outcome") HistoryRewindOutcome outcome, + /** Number of persisted events removed by conversation truncation. Present only when truncation succeeded (outcomes `success`, `checkpoint-cleanup-failed`, and `snapshot-prune-failed`); omitted for every unavailable outcome (`session-busy`, `file-change-tracking-disabled`, `unsupported-remote-session`) and for `truncation-failed`, `files-rolled-back`, and `rollback-incomplete`. */ + @JsonProperty("eventsRemoved") Long eventsRemoved, + /** Absolute paths restored to their captured preimages. Always empty for conversation-only rewinds and for the unavailable outcomes (`session-busy`, `file-change-tracking-disabled`, `unsupported-remote-session`); only conversation-and-files outcomes that reached the file-restore stage populate it. */ + @JsonProperty("restoredFiles") List restoredFiles, + /** Captured files intentionally left unchanged. Always empty for conversation-only rewinds and for the unavailable outcomes (`session-busy`, `file-change-tracking-disabled`, `unsupported-remote-session`); only conversation-and-files outcomes that reached the file-restore stage populate it. */ + @JsonProperty("skippedFiles") List skippedFiles, + /** Failure detail. Set only for the failure and partial-failure outcomes (`files-rolled-back`, `rollback-incomplete`, `truncation-failed`, `checkpoint-cleanup-failed`, `snapshot-prune-failed`); omitted for `success` and for the unavailable outcomes (`session-busy`, `file-change-tracking-disabled`, `unsupported-remote-session`). */ + @JsonProperty("error") String error +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionHistorySummarizeForHandoffParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistorySummarizeForHandoffParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionHistorySummarizeForHandoffParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistorySummarizeForHandoffParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionHistorySummarizeForHandoffResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistorySummarizeForHandoffResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionHistorySummarizeForHandoffResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistorySummarizeForHandoffResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryTruncateParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryTruncateParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryTruncateParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryTruncateParams.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryTruncateResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryTruncateResult.java new file mode 100644 index 000000000..f5ae17d62 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryTruncateResult.java @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Number of events that were removed by the truncation. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionHistoryTruncateResult( + /** Number of events that were removed */ + @JsonProperty("eventsRemoved") Long eventsRemoved, + /** True when conversation truncation succeeded but post-truncation workspace checkpoint cleanup failed. History is already truncated; callers may still prune snapshots but should report a checkpoint-cleanup rather than a truncation failure. */ + @JsonProperty("checkpointCleanupFailed") Boolean checkpointCleanupFailed, + /** Failure detail when checkpointCleanupFailed is true. */ + @JsonProperty("checkpointCleanupError") String checkpointCleanupError +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionInstalledPlugin.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionInstalledPlugin.java similarity index 76% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionInstalledPlugin.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionInstalledPlugin.java index ee1bc7154..1109f5f23 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionInstalledPlugin.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionInstalledPlugin.java @@ -34,6 +34,8 @@ public record SessionInstalledPlugin( /** Path where the plugin is cached locally */ @JsonProperty("cache_path") String cachePath, /** Source descriptor for direct repo installs (when marketplace is empty) */ - @JsonProperty("source") Object source + @JsonProperty("source") Object source, + /** Per-plugin source fingerprint (a SHA-256 hash of the plugin's catalog source spec plus its resolved source subtree — NOT a Git commit SHA) captured at marketplace install/update time. Auto-update compares it against the freshly recomputed fingerprint to detect a content change that does not bump the version. Absent for pre-existing installs and for direct (non-marketplace) installs. */ + @JsonProperty("source_sha") String sourceSha ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionInstructionsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionInstructionsApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionInstructionsApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionInstructionsApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionInstructionsGetSourcesParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionInstructionsGetSourcesParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionInstructionsGetSourcesParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionInstructionsGetSourcesParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionInstructionsGetSourcesResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionInstructionsGetSourcesResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionInstructionsGetSourcesResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionInstructionsGetSourcesResult.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionInterruptMainTurnParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionInterruptMainTurnParams.java new file mode 100644 index 000000000..6e16ad1dd --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionInterruptMainTurnParams.java @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Parameters for interrupting the main agent turn. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionInterruptMainTurnParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** When true, the user's queued prompts are preserved and run as the next turn once the interrupted turn unwinds; when false (the default), the queue is cleared like a plain abort. */ + @JsonProperty("flushQueued") Boolean flushQueued +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionInterruptMainTurnResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionInterruptMainTurnResult.java new file mode 100644 index 000000000..a57804cf8 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionInterruptMainTurnResult.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Result of interrupting the main agent turn. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionInterruptMainTurnResult( + /** Whether an in-flight main agent turn was interrupted. False when the main loop was not processing. */ + @JsonProperty("interrupted") Boolean interrupted +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionApi.java new file mode 100644 index 000000000..a64f82afa --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionApi.java @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.github.copilot.CopilotExperimental; +import java.util.concurrent.CompletableFuture; +import javax.annotation.processing.Generated; + +/** + * API methods for the {@code limitPrediction} namespace. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SessionLimitPredictionApi { + + private static final com.fasterxml.jackson.databind.ObjectMapper MAPPER = RpcMapper.INSTANCE; + + private final RpcCaller caller; + private final String sessionId; + + /** @param caller the RPC transport function */ + SessionLimitPredictionApi(RpcCaller caller, String sessionId) { + this.caller = caller; + this.sessionId = sessionId; + } + + /** + * Parameters for predicting an AI-credit session limit. Omitting `modelId` uses the session's currently selected model. + *

+ * Invokes the method with no params, applying the runtime defaults. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture predict() { + return predict(null); + } + + /** + * Parameters for predicting an AI-credit session limit. Omitting `modelId` uses the session's currently selected model. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture predict(SessionLimitPredictionPredictParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = params == null ? MAPPER.createObjectNode() : MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.limitPrediction.predict", _p, SessionLimitPredictionResult.class); + } + +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionBaselineData.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionBaselineData.java new file mode 100644 index 000000000..2387c5498 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionBaselineData.java @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Baseline data provenance for a prediction. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionLimitPredictionBaselineData( + /** Start of the baseline data slice. */ + @JsonProperty("windowStart") String windowStart, + /** End of the baseline data slice. */ + @JsonProperty("windowEnd") String windowEnd +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionClientType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionClientType.java new file mode 100644 index 000000000..539602931 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionClientType.java @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * Client population used for the prediction baseline. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum SessionLimitPredictionClientType { + /** The {@code cli-interactive} variant. */ + CLI_INTERACTIVE("cli-interactive"), + /** The {@code cli-prompt} variant. */ + CLI_PROMPT("cli-prompt"); + + private final String value; + SessionLimitPredictionClientType(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static SessionLimitPredictionClientType fromValue(String value) { + for (SessionLimitPredictionClientType v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown SessionLimitPredictionClientType value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionDetails.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionDetails.java new file mode 100644 index 000000000..f4329bb71 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionDetails.java @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * Explainable AI-credit session-limit prediction. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionLimitPredictionDetails( + /** Client population used for the prediction. */ + @JsonProperty("clientType") SessionLimitPredictionClientType clientType, + /** Model identifier used for lookup. */ + @JsonProperty("modelId") String modelId, + /** Baseline fallback level used to create the prediction. */ + @JsonProperty("source") SessionLimitPredictionSource source, + /** Key matched at the source level, such as a model id, family id, or `global`. */ + @JsonProperty("sourceKey") String sourceKey, + /** Resolved model family when known. */ + @JsonProperty("family") String family, + /** Ordered usage tiers and their AI-credit caps. */ + @JsonProperty("tiers") List tiers, + /** Baseline data provenance. */ + @JsonProperty("baselineData") SessionLimitPredictionBaselineData baselineData, + /** Tier chosen as the recommended cap. */ + @JsonProperty("recommendedTier") SessionLimitPredictionTier recommendedTier, + /** Recommended maximum AI credits for this session. */ + @JsonProperty("recommendedCap") Double recommendedCap +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionPredictParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionPredictParams.java new file mode 100644 index 000000000..b02e82ca8 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionPredictParams.java @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Request parameters for the {@code session.limitPrediction.predict} RPC method. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionLimitPredictionPredictParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Optional model identifier override. If omitted, the session's current model is used. */ + @JsonProperty("modelId") String modelId, + /** Client type to size for. Defaults to `cli-interactive`. */ + @JsonProperty("clientType") SessionLimitPredictionClientType clientType +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionResult.java new file mode 100644 index 000000000..0c2f489d9 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionResult.java @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import javax.annotation.processing.Generated; + +/** + * Prediction result. Available results include prediction details; unavailable results include an explicit reason. + * + * @since 1.0.0 + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "kind", visible = true) +@JsonSubTypes({ + @JsonSubTypes.Type(value = SessionLimitPredictionResultAvailable.class, name = "available"), + @JsonSubTypes.Type(value = SessionLimitPredictionResultUnavailable.class, name = "unavailable") +}) +@JsonIgnoreProperties(ignoreUnknown = true) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public abstract class SessionLimitPredictionResult { + + /** + * Returns the discriminator value for this variant. + * + * @return the kind discriminator + */ + public abstract String getKind(); +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionResultAvailable.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionResultAvailable.java new file mode 100644 index 000000000..632018f4d --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionResultAvailable.java @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Variant {@code available} of {@link SessionLimitPredictionResult}. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SessionLimitPredictionResultAvailable extends SessionLimitPredictionResult { + + @JsonProperty("kind") + private final String kind = "available"; + + @Override + public String getKind() { return kind; } + + /** Predicted session limit details. */ + @JsonProperty("prediction") + private SessionLimitPredictionDetails prediction; + + public SessionLimitPredictionDetails getPrediction() { return prediction; } + public void setPrediction(SessionLimitPredictionDetails prediction) { this.prediction = prediction; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionResultUnavailable.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionResultUnavailable.java new file mode 100644 index 000000000..3f4b3ebe2 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionResultUnavailable.java @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Variant {@code unavailable} of {@link SessionLimitPredictionResult}. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SessionLimitPredictionResultUnavailable extends SessionLimitPredictionResult { + + @JsonProperty("kind") + private final String kind = "unavailable"; + + @Override + public String getKind() { return kind; } + + /** Reason no prediction is available. */ + @JsonProperty("reason") + private SessionLimitPredictionUnavailableReason reason; + + public SessionLimitPredictionUnavailableReason getReason() { return reason; } + public void setReason(SessionLimitPredictionUnavailableReason reason) { this.reason = reason; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionSource.java new file mode 100644 index 000000000..c22baa118 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionSource.java @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * Baseline fallback level used to create the prediction. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum SessionLimitPredictionSource { + /** The {@code model} variant. */ + MODEL("model"), + /** The {@code family} variant. */ + FAMILY("family"), + /** The {@code global} variant. */ + GLOBAL("global"); + + private final String value; + SessionLimitPredictionSource(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static SessionLimitPredictionSource fromValue(String value) { + for (SessionLimitPredictionSource v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown SessionLimitPredictionSource value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionTier.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionTier.java new file mode 100644 index 000000000..21d3de43c --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionTier.java @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * Semantic usage tier used for a recommended cap or additional headroom. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum SessionLimitPredictionTier { + /** The {@code recommended} variant. */ + RECOMMENDED("recommended"), + /** The {@code additional_headroom} variant. */ + ADDITIONAL_HEADROOM("additional_headroom"), + /** The {@code generous_headroom} variant. */ + GENEROUS_HEADROOM("generous_headroom"), + /** The {@code maximum_headroom} variant. */ + MAXIMUM_HEADROOM("maximum_headroom"); + + private final String value; + SessionLimitPredictionTier(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static SessionLimitPredictionTier fromValue(String value) { + for (SessionLimitPredictionTier v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown SessionLimitPredictionTier value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionTierOption.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionTierOption.java new file mode 100644 index 000000000..f468e53e0 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionTierOption.java @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Semantic usage tier and its AI-credit cap. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionLimitPredictionTierOption( + @JsonProperty("tier") SessionLimitPredictionTier tier, + /** AI-credit cap for this tier. */ + @JsonProperty("cap") Double cap +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionUnavailableReason.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionUnavailableReason.java new file mode 100644 index 000000000..76ee7c882 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitPredictionUnavailableReason.java @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * Reason a prediction could not be computed. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum SessionLimitPredictionUnavailableReason { + /** The {@code auto_unresolved} variant. */ + AUTO_UNRESOLVED("auto_unresolved"), + /** The {@code no_model} variant. */ + NO_MODEL("no_model"); + + private final String value; + SessionLimitPredictionUnavailableReason(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static SessionLimitPredictionUnavailableReason fromValue(String value) { + for (SessionLimitPredictionUnavailableReason v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown SessionLimitPredictionUnavailableReason value: " + value); + } +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionLimitsConfig.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitsConfig.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionLimitsConfig.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLimitsConfig.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionListFilter.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionListFilter.java new file mode 100644 index 000000000..c5b92cb04 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionListFilter.java @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Optional filter applied to the returned sessions + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionListFilter( + /** Match sessions whose context.cwd equals this value */ + @JsonProperty("cwd") String cwd, + /** Match sessions whose context.gitRoot equals this value */ + @JsonProperty("gitRoot") String gitRoot, + /** Match sessions whose context.repository equals this value */ + @JsonProperty("repository") String repository, + /** Match sessions whose context.branch equals this value */ + @JsonProperty("branch") String branch +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionLogLevel.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLogLevel.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionLogLevel.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLogLevel.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionLogParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLogParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionLogParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLogParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionLogResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLogResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionLogResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLogResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionLspApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLspApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionLspApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLspApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionLspInitializeParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLspInitializeParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionLspInitializeParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionLspInitializeParams.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionManagedPermissions.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionManagedPermissions.java new file mode 100644 index 000000000..79698b27c --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionManagedPermissions.java @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * Enterprise permission policy expressed with the runtime's managed permission-rule syntax. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionManagedPermissions( + /** When set to `disable`, prevents bypass/allow-all permission modes. */ + @JsonProperty("disableBypassPermissionsMode") DisableBypassPermissionsMode disableBypassPermissionsMode, + /** Permission rules that block matching operations. Deny has highest precedence. */ + @JsonProperty("deny") List deny, + /** Permission rules that require explicit human approval. */ + @JsonProperty("ask") List ask, + /** Permission rules that allow matching operations unless another managed source, deny, or ask rule restricts them. */ + @JsonProperty("allow") List allow +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionManagedSettings.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionManagedSettings.java new file mode 100644 index 000000000..ddba69d8b --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionManagedSettings.java @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Managed settings an SDK host may inject at session startup. Only permissions are accepted in this initial contract. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionManagedSettings( + @JsonProperty("permissions") SessionManagedPermissions permissions +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpApi.java similarity index 98% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpApi.java index 172f6e507..1a75b92c1 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpApi.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpApi.java @@ -205,7 +205,7 @@ public CompletableFuture configureGitHub(Sessio } /** - * Server name and configuration for an individual MCP server start. + * Server name and optional configuration for an individual MCP server start. Omit `config` for a config-free start-by-name of an already-configured server. *

* Note: the {@code sessionId} field in the params record is overridden * by the session-scoped wrapper; any value provided is ignored. diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsCallToolParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsCallToolParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsCallToolParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsCallToolParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsDiagnoseParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsDiagnoseParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsDiagnoseParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsDiagnoseParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsDiagnoseResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsDiagnoseResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsDiagnoseResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsDiagnoseResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsGetHostContextParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsGetHostContextParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsGetHostContextParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsGetHostContextParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsGetHostContextResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsGetHostContextResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsGetHostContextResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsGetHostContextResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsListToolsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsListToolsParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsListToolsParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsListToolsParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsListToolsResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsListToolsResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsListToolsResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsListToolsResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsReadResourceParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsReadResourceParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsReadResourceParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsReadResourceParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsReadResourceResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsReadResourceResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsReadResourceResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsReadResourceResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsSetHostContextParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsSetHostContextParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsSetHostContextParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpAppsSetHostContextParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpCancelSamplingExecutionParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpCancelSamplingExecutionParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpCancelSamplingExecutionParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpCancelSamplingExecutionParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpCancelSamplingExecutionResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpCancelSamplingExecutionResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpCancelSamplingExecutionResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpCancelSamplingExecutionResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpConfigureGitHubParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpConfigureGitHubParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpConfigureGitHubParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpConfigureGitHubParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpConfigureGitHubResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpConfigureGitHubResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpConfigureGitHubResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpConfigureGitHubResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpDisableParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpDisableParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpDisableParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpDisableParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpEnableParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpEnableParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpEnableParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpEnableParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpExecuteSamplingParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpExecuteSamplingParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpExecuteSamplingParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpExecuteSamplingParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpExecuteSamplingResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpExecuteSamplingResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpExecuteSamplingResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpExecuteSamplingResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpHeadersApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpHeadersApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpHeadersApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpHeadersApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpHeadersHandlePendingHeadersRefreshRequestParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpHeadersHandlePendingHeadersRefreshRequestParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpHeadersHandlePendingHeadersRefreshRequestParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpHeadersHandlePendingHeadersRefreshRequestParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpHeadersHandlePendingHeadersRefreshRequestResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpHeadersHandlePendingHeadersRefreshRequestResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpHeadersHandlePendingHeadersRefreshRequestResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpHeadersHandlePendingHeadersRefreshRequestResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpIsServerRunningParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpIsServerRunningParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpIsServerRunningParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpIsServerRunningParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpIsServerRunningResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpIsServerRunningResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpIsServerRunningResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpIsServerRunningResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpListParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpListParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpListParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpListParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpListResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpListResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpListResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpListResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpListToolsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpListToolsParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpListToolsParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpListToolsParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpListToolsResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpListToolsResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpListToolsResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpListToolsResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthApi.java similarity index 64% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthApi.java index 7b7f7b82b..1fdb292f8 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthApi.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthApi.java @@ -46,6 +46,22 @@ public CompletableFuture handlePendin return caller.invoke("session.mcp.oauth.handlePendingRequest", _p, SessionMcpOauthHandlePendingRequestResult.class); } + /** + * Identifies the MCP server whose persisted OAuth credentials were updated. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture authenticationStateChanged(SessionMcpOauthAuthenticationStateChangedParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.mcp.oauth.authenticationStateChanged", _p, Void.class); + } + /** * Remote MCP server name and optional overrides controlling reauthentication, OAuth client display name, callback success-page copy, and static OAuth client selection. *

@@ -62,4 +78,20 @@ public CompletableFuture login(SessionMcpOauthLoginP return caller.invoke("session.mcp.oauth.login", _p, SessionMcpOauthLoginResult.class); } + /** + * Pending MCP OAuth request id to respond to. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture respond(SessionMcpOauthRespondParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.mcp.oauth.respond", _p, SessionMcpOauthRespondResult.class); + } + } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthAuthenticationStateChangedParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthAuthenticationStateChangedParams.java new file mode 100644 index 000000000..b773e1bf7 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthAuthenticationStateChangedParams.java @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Identifies the MCP server whose persisted OAuth credentials were updated. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionMcpOauthAuthenticationStateChangedParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Name of the MCP server whose OAuth credentials were updated. Omit only when the host cannot identify the server. */ + @JsonProperty("serverName") String serverName, + /** Whether the target session must mint a session-scoped access token instead of reusing a shared access token persisted by another session. */ + @JsonProperty("refreshSessionToken") Boolean refreshSessionToken +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthHandlePendingRequestParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthHandlePendingRequestParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthHandlePendingRequestParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthHandlePendingRequestParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthHandlePendingRequestResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthHandlePendingRequestResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthHandlePendingRequestResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthHandlePendingRequestResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthLoginParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthLoginParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthLoginParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthLoginParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthLoginResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthLoginResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthLoginResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthLoginResult.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthRespondParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthRespondParams.java new file mode 100644 index 000000000..ca79468c1 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthRespondParams.java @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Pending MCP OAuth request id to respond to. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionMcpOauthRespondParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** OAuth request identifier from the mcp.oauth_required event */ + @JsonProperty("requestId") String requestId +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthRespondResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthRespondResult.java new file mode 100644 index 000000000..1b1267cb5 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpOauthRespondResult.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Indicates whether the pending MCP OAuth response was accepted. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionMcpOauthRespondResult( + /** Whether the response was accepted. False if the request was unknown, timed out, or already resolved. */ + @JsonProperty("success") Boolean success +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpRegisterExternalClientParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpRegisterExternalClientParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpRegisterExternalClientParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpRegisterExternalClientParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpReloadParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpReloadParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpReloadParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpReloadParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpReloadWithConfigParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpReloadWithConfigParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpReloadWithConfigParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpReloadWithConfigParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpReloadWithConfigResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpReloadWithConfigResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpReloadWithConfigResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpReloadWithConfigResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpRemoveGitHubParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpRemoveGitHubParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpRemoveGitHubParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpRemoveGitHubParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpRemoveGitHubResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpRemoveGitHubResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpRemoveGitHubResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpRemoveGitHubResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpResourcesApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpResourcesApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpResourcesApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpResourcesApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpResourcesListParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpResourcesListParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpResourcesListParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpResourcesListParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpResourcesListResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpResourcesListResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpResourcesListResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpResourcesListResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpResourcesListTemplatesParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpResourcesListTemplatesParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpResourcesListTemplatesParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpResourcesListTemplatesParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpResourcesListTemplatesResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpResourcesListTemplatesResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpResourcesListTemplatesResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpResourcesListTemplatesResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpResourcesReadParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpResourcesReadParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpResourcesReadParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpResourcesReadParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpResourcesReadResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpResourcesReadResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpResourcesReadResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpResourcesReadResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpRestartServerParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpRestartServerParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpRestartServerParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpRestartServerParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpSetEnvValueModeParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpSetEnvValueModeParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpSetEnvValueModeParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpSetEnvValueModeParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpSetEnvValueModeResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpSetEnvValueModeResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpSetEnvValueModeResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpSetEnvValueModeResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpStartServerParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpStartServerParams.java similarity index 82% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpStartServerParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpStartServerParams.java index e4c14e30e..9f6d5d73e 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpStartServerParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpStartServerParams.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Server name and configuration for an individual MCP server start. + * Server name and optional configuration for an individual MCP server start. Omit `config` for a config-free start-by-name of an already-configured server. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 @@ -28,7 +28,7 @@ public record SessionMcpStartServerParams( @JsonProperty("sessionId") String sessionId, /** Name of the MCP server to start */ @JsonProperty("serverName") String serverName, - /** MCP server configuration (stdio process or remote HTTP/SSE) */ + /** MCP server configuration (stdio process or remote HTTP/SSE). Omit to start the server with its already-registered configuration (config-free start-by-name). */ @JsonProperty("config") Object config ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpStopServerParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpStopServerParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpStopServerParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpStopServerParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpUnregisterExternalClientParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpUnregisterExternalClientParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMcpUnregisterExternalClientParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMcpUnregisterExternalClientParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataActivityParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataActivityParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataActivityParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataActivityParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataActivityResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataActivityResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataActivityResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataActivityResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataContextInfoParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataContextInfoParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataContextInfoParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataContextInfoParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataContextInfoResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataContextInfoResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataContextInfoResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataContextInfoResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataGetContextAttributionParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataGetContextAttributionParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataGetContextAttributionParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataGetContextAttributionParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataGetContextAttributionResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataGetContextAttributionResult.java similarity index 58% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataGetContextAttributionResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataGetContextAttributionResult.java index ef7fba44d..c27f37afb 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataGetContextAttributionResult.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataGetContextAttributionResult.java @@ -36,12 +36,47 @@ public record SessionMetadataGetContextAttributionResult( public record SessionMetadataGetContextAttributionResultContextAttribution( /** Total token count of the current context window the entries are measured against (system message + conversation messages + tool definitions — the same total reported by /context). Divide an entry's `tokens` by this to derive its share. */ @JsonProperty("totalTokens") Long totalTokens, + /** The concrete model id the entire breakdown was tokenized against (feeds the per-model token multiplier). Under `Auto` (Free/Student) this is the resolved model, not the literal `auto` sentinel, so totals are not undercounted. A single-model approximation of a potentially multi-model Auto session. */ + @JsonProperty("modelId") String modelId, + /** How `modelId` was chosen. Not a closed set — tolerate unknown values. Known values today: `autoResolved` (the model Auto resolved to), `selected` (the user's explicitly selected model), `default` (a fallback before any model is known). */ + @JsonProperty("modelSource") String modelSource, + /** Maximum prompt tokens the resolved model accepts — the denominator for a `##k/###k` context-usage display. Mirrors `SessionContextInfo.promptTokenLimit`. */ + @JsonProperty("promptTokenLimit") Long promptTokenLimit, + /** Prompt limit plus the model's output reserve: the full context window `categories.freeSpace` and `categories.buffer` are measured against. Mirrors `SessionContextInfo.limit`. */ + @JsonProperty("limit") Long limit, + /** Output reserve plus the tokens past the buffer-exhaustion blocking threshold. Mirrors `SessionContextInfo.bufferTokens`. */ + @JsonProperty("bufferTokens") Long bufferTokens, + /** Token count at which background compaction starts. Mirrors `SessionContextInfo.compactionThreshold`. */ + @JsonProperty("compactionThreshold") Long compactionThreshold, + /** The six normalized `/context` header buckets, computed from the same tokenization as `entries` so the two never disagree. Convenience rollups: `freeSpace` and `buffer` describe window capacity rather than occupied context, so the values do not sum to `totalTokens`. */ + @JsonProperty("categories") SessionMetadataGetContextAttributionResultContextAttributionCategories categories, /** Flat list of per-source attribution entries. Group by `kind` and render unrecognized kinds generically. Nesting and rollups are expressed via `parentId`. */ @JsonProperty("entries") List entries, /** Successful compaction history for the session. */ @JsonProperty("compactions") SessionMetadataGetContextAttributionResultContextAttributionCompactions compactions ) { + /** The six normalized `/context` header buckets, computed from the same tokenization as `entries` so the two never disagree. Convenience rollups: `freeSpace` and `buffer` describe window capacity rather than occupied context, so the values do not sum to `totalTokens`. */ + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record SessionMetadataGetContextAttributionResultContextAttributionCategories( + /** System prompt tokens, excluding custom instructions. */ + @JsonProperty("systemPrompt") Long systemPrompt, + /** Custom-instructions tokens (0 when none are configured). */ + @JsonProperty("customInstructions") Long customInstructions, + /** Non-MCP tool-definition tokens. */ + @JsonProperty("systemTools") Long systemTools, + /** MCP tool-definition tokens. */ + @JsonProperty("mcpTools") Long mcpTools, + /** Conversation (user/assistant/tool) message tokens. */ + @JsonProperty("messages") Long messages, + /** Remaining unused window capacity (clamped at 0). */ + @JsonProperty("freeSpace") Long freeSpace, + /** Output reserve plus post-blocking-threshold buffer. */ + @JsonProperty("buffer") Long buffer + ) { + } + @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public record SessionMetadataGetContextAttributionResultContextAttributionEntriesItem( diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataGetContextHeaviestMessagesParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataGetContextHeaviestMessagesParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataGetContextHeaviestMessagesParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataGetContextHeaviestMessagesParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataGetContextHeaviestMessagesResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataGetContextHeaviestMessagesResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataGetContextHeaviestMessagesResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataGetContextHeaviestMessagesResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataIsProcessingParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataIsProcessingParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataIsProcessingParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataIsProcessingParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataIsProcessingResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataIsProcessingResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataIsProcessingResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataIsProcessingResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataRecomputeContextTokensParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataRecomputeContextTokensParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataRecomputeContextTokensParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataRecomputeContextTokensParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataRecomputeContextTokensResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataRecomputeContextTokensResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataRecomputeContextTokensResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataRecomputeContextTokensResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataRecordContextChangeParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataRecordContextChangeParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataRecordContextChangeParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataRecordContextChangeParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataSetWorkingDirectoryParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataSetWorkingDirectoryParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataSetWorkingDirectoryParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataSetWorkingDirectoryParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataSetWorkingDirectoryResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataSetWorkingDirectoryResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataSetWorkingDirectoryResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataSetWorkingDirectoryResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataSnapshotParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataSnapshotParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataSnapshotParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataSnapshotParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataSnapshotResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataSnapshotResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataSnapshotResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMetadataSnapshotResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMode.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionMode.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionMode.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionModeApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModeApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionModeApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModeApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionModeGetParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModeGetParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionModeGetParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModeGetParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionModeSetParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModeSetParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionModeSetParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModeSetParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionModelApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelApi.java similarity index 79% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionModelApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelApi.java index 34be1729d..9d20e8627 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionModelApi.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelApi.java @@ -75,13 +75,31 @@ public CompletableFuture setReasoningEffor /** * Optional listing options. + *

+ * Invokes the method with no params, applying the runtime defaults. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 */ @CopilotExperimental public CompletableFuture list() { - return caller.invoke("session.model.list", java.util.Map.of("sessionId", this.sessionId), SessionModelListResult.class); + return list(null); + } + + /** + * Optional listing options. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture list(SessionModelListParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = params == null ? MAPPER.createObjectNode() : MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.model.list", _p, SessionModelListResult.class); } } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionModelGetCurrentParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelGetCurrentParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionModelGetCurrentParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelGetCurrentParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionModelGetCurrentResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelGetCurrentResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionModelGetCurrentResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelGetCurrentResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionModelListParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelListParams.java similarity index 79% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionModelListParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelListParams.java index 154ca5c22..dc521fe2e 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionModelListParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelListParams.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Optional listing options. + * Request parameters for the {@code session.model.list} RPC method. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 @@ -25,6 +25,8 @@ @JsonIgnoreProperties(ignoreUnknown = true) public record SessionModelListParams( /** Target session identifier */ - @JsonProperty("sessionId") String sessionId + @JsonProperty("sessionId") String sessionId, + /** If true, bypasses the per-session model list cache and re-fetches from CAPI. */ + @JsonProperty("skipCache") Boolean skipCache ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionModelListResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelListResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionModelListResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelListResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionModelPriceCategory.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelPriceCategory.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionModelPriceCategory.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelPriceCategory.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionModelSetReasoningEffortParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelSetReasoningEffortParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionModelSetReasoningEffortParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelSetReasoningEffortParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionModelSetReasoningEffortResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelSetReasoningEffortResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionModelSetReasoningEffortResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelSetReasoningEffortResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionModelSwitchToParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelSwitchToParams.java similarity index 71% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionModelSwitchToParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelSwitchToParams.java index 580ab3bab..fe49e2976 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionModelSwitchToParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelSwitchToParams.java @@ -28,7 +28,7 @@ public record SessionModelSwitchToParams( @JsonProperty("sessionId") String sessionId, /** Model selection id to switch to, as returned by `list`. A bare id (e.g. `claude-sonnet-4.6`) names a Copilot (CAPI) model; a provider-qualified id (`provider/id`, e.g. `acme/claude-sonnet`) targets a registry BYOK model. */ @JsonProperty("modelId") String modelId, - /** Reasoning effort level to use for the model. "none" disables reasoning. */ + /** Reasoning effort level to use for the model. CAPI values are model-defined and validated against the selected model; BYOK providers may define additional values. "none" disables reasoning. When omitted, no effort override is applied. */ @JsonProperty("reasoningEffort") String reasoningEffort, /** Reasoning summary mode to request for supported model clients */ @JsonProperty("reasoningSummary") ReasoningSummary reasoningSummary, @@ -37,6 +37,8 @@ public record SessionModelSwitchToParams( /** Override individual model capabilities resolved by the runtime */ @JsonProperty("modelCapabilities") ModelCapabilitiesOverride modelCapabilities, /** Explicit context tier for the selected model. `"default"` / `"long_context"` apply the requested tier; omit this field to use normal model behavior with no explicit tier. */ - @JsonProperty("contextTier") ContextTier contextTier + @JsonProperty("contextTier") ContextTier contextTier, + /** When true, defer this switch (enqueue it) if another model change is already queued, even when no turn is active — so it drains last (FIFO) and wins over the already-queued change. Intended for genuine user-initiated model selections; internal restore/reapply switches omit it and apply immediately when no turn is active. When no other model change is queued this has no effect (a switch still applies immediately unless a turn is active). */ + @JsonProperty("deferIfModelChangeQueued") Boolean deferIfModelChangeQueued ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionModelSwitchToResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelSwitchToResult.java similarity index 74% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionModelSwitchToResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelSwitchToResult.java index 89584bc29..030324a94 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionModelSwitchToResult.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionModelSwitchToResult.java @@ -25,6 +25,8 @@ @JsonIgnoreProperties(ignoreUnknown = true) public record SessionModelSwitchToResult( /** Currently active model identifier after the switch */ - @JsonProperty("modelId") String modelId + @JsonProperty("modelId") String modelId, + /** True when the switch was deferred (enqueued as a cancellable `/model` command) because a turn was active or another model change was already queued, rather than applied immediately. When true, the session's live model is unchanged until the queued change drains. */ + @JsonProperty("deferred") Boolean deferred ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionNameApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionNameApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionNameApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionNameApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionNameGetParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionNameGetParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionNameGetParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionNameGetParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionNameGetResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionNameGetResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionNameGetResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionNameGetResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionNameSetAutoParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionNameSetAutoParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionNameSetAutoParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionNameSetAutoParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionNameSetAutoResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionNameSetAutoResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionNameSetAutoResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionNameSetAutoResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionNameSetParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionNameSetParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionNameSetParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionNameSetParams.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptions.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptions.java new file mode 100644 index 000000000..cf253bff0 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptions.java @@ -0,0 +1,161 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import java.util.Map; +import javax.annotation.processing.Generated; + +/** + * Session construction options. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionOpenOptions( + /** Optional stable session identifier to use for a new session. */ + @JsonProperty("sessionId") String sessionId, + /** Optional human-friendly session name. */ + @JsonProperty("name") String name, + /** Initial model identifier. */ + @JsonProperty("model") String model, + /** Initial reasoning effort level. CAPI values are model-defined and validated against the selected model; BYOK providers may define additional values. When omitted, no effort override is applied. */ + @JsonProperty("reasoningEffort") String reasoningEffort, + /** Initial reasoning summary mode for supported model clients. */ + @JsonProperty("reasoningSummary") SessionOpenOptionsReasoningSummary reasoningSummary, + /** Initial output verbosity level for supported models. */ + @JsonProperty("verbosity") Verbosity verbosity, + /** Identifier of the client driving the session. */ + @JsonProperty("clientName") String clientName, + /** Structured client kind used for runtime behavior gates. */ + @JsonProperty("clientKind") String clientKind, + /** Identifier sent to LSP-style integrations. */ + @JsonProperty("lspClientName") String lspClientName, + /** Stable integration identifier for analytics. */ + @JsonProperty("integrationId") String integrationId, + /** ExP assignment ('flight') data injected by an SDK integrator, in the same JSON shape the Copilot CLI fetches from the experimentation service (CopilotExpAssignmentResponse). When supplied this is fed into the FeatureFlagService exactly like CLI-fetched assignments and ExP-backed flags wait for it. When absent the session does not block on ExP. */ + @JsonProperty("expAssignments") Object expAssignments, + /** Opt-in: self-fetch and enforce enterprise managed settings at session bootstrap. */ + @JsonProperty("enableManagedSettings") Boolean enableManagedSettings, + /** Permissions-only enterprise policy injected by the SDK host at session create or resume. Composes restrictively with self-fetched and device policy and is not persisted. */ + @JsonProperty("managedSettings") SessionManagedSettings managedSettings, + /** Opt in to capturing file changes for session rewind and session diff. Capture cannot reconstruct changes made before it was enabled. On create it starts capture from the first turn. It is also honored on resume: for a session that already has tracked prior turns, tracking continues automatically even if this is omitted; passing it on resume additionally enables tracking for an eligible session that has no prior root turn yet. Resuming a session whose prior root turns were never tracked has no restorable baseline, so tracking stays disabled for it and rewind reports file change tracking as unavailable; the resume itself still succeeds, so sessions that predate tracking remain loadable. The opt-in is only rejected when the session can never track (a subagent session, or one without local session storage). It is intentionally absent from the mutable options update because enabling it after edits have occurred would create an incomplete, misleading baseline. Subagents share the parent session's capture store and are not tracked as separate rewind points: a file a subagent writes is attributed to whichever root user turn was open when the capture was staged, just before the tool body ran. A turn cannot open while a staged capture is still in flight, so a subagent tool that staged under the spawning turn stays attributed to it however late the write lands, while a capture it stages after the user's next message belongs to that later turn. Attribution decides which turn's rewind point counts and file preview include that write; it does not narrow which rewinds revert it, because a rewind restores every capture from the selected turn onward, so the earlier spawning turn reverts it as well. */ + @JsonProperty("enableFileChangeTracking") Boolean enableFileChangeTracking, + /** Feature-flag values resolved by the host. */ + @JsonProperty("featureFlags") Map featureFlags, + /** Whether experimental behavior is enabled. */ + @JsonProperty("isExperimentalMode") Boolean isExperimentalMode, + /** Initial authentication info for the session. */ + @JsonProperty("authInfo") Object authInfo, + /** Custom model-provider configuration (BYOK). */ + @JsonProperty("provider") ProviderConfig provider, + /** Options scoped to the built-in CAPI (Copilot API) provider. */ + @JsonProperty("capi") CapiSessionOptions capi, + /** Named BYOK provider connections, additive to CAPI auth. Combining with `provider` is rejected. */ + @JsonProperty("providers") List providers, + /** BYOK model definitions added to the selectable model list, each referencing a provider name. */ + @JsonProperty("models") List models, + /** Working directory to anchor the session. */ + @JsonProperty("workingDirectory") String workingDirectory, + /** Additional directories the agent may access beyond the working directory. Each entry is granted to the session's file-access allow-list and surfaced to the model (system prompt context and `@`-mention completion). Absolute paths are recommended; a relative path is resolved against the session's working directory. Nonexistent or unresolvable entries are skipped with a warning. This is applied on both session creation and resume, and is not persisted: a resumed session that omits this option does not retain previously supplied directories (re-supply them, exactly as the CLI re-passes `--add-dir`). */ + @JsonProperty("additionalDirectories") List additionalDirectories, + /** Pre-resolved working-directory context for session startup. */ + @JsonProperty("workingDirectoryContext") SessionContext workingDirectoryContext, + /** Whether this session supports remote steering. */ + @JsonProperty("remoteSteerable") Boolean remoteSteerable, + /** Telemetry-only remote exporting flag. */ + @JsonProperty("remoteExporting") Boolean remoteExporting, + /** Telemetry-only remote-defaulted flag. */ + @JsonProperty("remoteDefaultedOn") Boolean remoteDefaultedOn, + /** Parent session ID for detached child telemetry rollup. */ + @JsonProperty("detachedFromSpawningParentSessionId") String detachedFromSpawningParentSessionId, + /** Parent engagement ID for detached child telemetry rollup. */ + @JsonProperty("detachedFromSpawningParentEngagementId") String detachedFromSpawningParentEngagementId, + /** Allowlist of available tool names. */ + @JsonProperty("availableTools") List availableTools, + /** Denylist of tool names. */ + @JsonProperty("excludedTools") List excludedTools, + /** Built-in subagent names to include in this session. When specified, only these built-ins are available, subject to runtime availability and exclusions. Custom agents with the same name remain available. */ + @JsonProperty("includedBuiltinAgents") List includedBuiltinAgents, + /** Built-in subagent names to exclude from this session. Excluded built-ins are hidden from agent discovery and cannot be dispatched unless a custom agent with the same name is available. */ + @JsonProperty("excludedBuiltinAgents") List excludedBuiltinAgents, + /** Whether shell-script safety heuristics are enabled. */ + @JsonProperty("enableScriptSafety") Boolean enableScriptSafety, + /** Per-session settings for built-in shell tools. */ + @JsonProperty("shell") ShellOptions shell, + /** Use shell.initProfile instead. Shell init profile. */ + @JsonProperty("shellInitProfile") String shellInitProfile, + /** PowerShell process flags applied to built-in and user-requested shell commands. */ + @JsonProperty("shellProcessFlags") List shellProcessFlags, + /** Resolved sandbox configuration. */ + @JsonProperty("sandboxConfig") SandboxConfig sandboxConfig, + /** Whether interactive shell sessions are logged. */ + @JsonProperty("logInteractiveShells") Boolean logInteractiveShells, + /** How MCP server environment values are interpreted. */ + @JsonProperty("envValueMode") SessionOpenOptionsEnvValueMode envValueMode, + /** MCP server names disabled for this session. Disabled servers are not started or authenticated on create or cold resume. */ + @JsonProperty("disabledMcpServers") List disabledMcpServers, + /** Whether to include instructions from every MCP server in the system prompt instead of only allowlisted servers. */ + @JsonProperty("allowAllMcpServerInstructions") Boolean allowAllMcpServerInstructions, + /** Additional directories to search for skills. */ + @JsonProperty("skillDirectories") List skillDirectories, + /** Skill IDs disabled for this session. */ + @JsonProperty("disabledSkills") List disabledSkills, + /** Installed plugins visible to the session. */ + @JsonProperty("installedPlugins") List installedPlugins, + /** Whether custom agents default to local-only execution. */ + @JsonProperty("customAgentsLocalOnly") Boolean customAgentsLocalOnly, + /** Whether to skip custom instruction sources. */ + @JsonProperty("skipCustomInstructions") Boolean skipCustomInstructions, + /** Instruction source IDs disabled for this session. */ + @JsonProperty("disabledInstructionSources") List disabledInstructionSources, + /** Whether commit-message coauthor trailers are enabled. */ + @JsonProperty("coauthorEnabled") Boolean coauthorEnabled, + /** Optional trajectory output file path. */ + @JsonProperty("trajectoryFile") String trajectoryFile, + /** Whether model responses stream as delta events. */ + @JsonProperty("enableStreaming") Boolean enableStreaming, + /** Experimental: enable native model citations (Anthropic models today), normalized onto the `assistant.message` event. Off by default; may change or be removed while the citations surface is experimental. */ + @JsonProperty("enableCitations") Boolean enableCitations, + /** Override URL for the Copilot API endpoint. */ + @JsonProperty("copilotUrl") String copilotUrl, + /** Whether ask_user is explicitly disabled. */ + @JsonProperty("askUserDisabled") Boolean askUserDisabled, + /** Whether auto-mode continuation is enabled. */ + @JsonProperty("continueOnAutoMode") Boolean continueOnAutoMode, + /** Whether the host is an interactive UI. */ + @JsonProperty("runningInInteractiveMode") Boolean runningInInteractiveMode, + /** Whether on-demand custom instruction discovery is enabled. */ + @JsonProperty("enableOnDemandInstructionDiscovery") Boolean enableOnDemandInstructionDiscovery, + /** Maximum decoded byte size of a single inline model-facing binary tool result persisted in session events (default 10 MB). */ + @JsonProperty("maxInlineBinaryBytes") Long maxInlineBinaryBytes, + /** Initial model capability overrides. */ + @JsonProperty("modelCapabilitiesOverrides") ModelCapabilitiesOverride modelCapabilitiesOverrides, + /** Initial session limits. */ + @JsonProperty("sessionLimits") SessionLimitsConfig sessionLimits, + /** Runtime context discriminator for agent filtering. */ + @JsonProperty("agentContext") String agentContext, + /** Override directory for session event logs. */ + @JsonProperty("eventsLogDirectory") String eventsLogDirectory, + /** Whether subagent callback events should be forwarded into the session event log sink. */ + @JsonProperty("eventsLogIncludesSubagents") Boolean eventsLogIncludesSubagents, + /** Override Copilot configuration directory. */ + @JsonProperty("configDir") String configDir, + /** Additional content-exclusion policies to merge into the session policy set. */ + @JsonProperty("additionalContentExclusionPolicies") List additionalContentExclusionPolicies, + /** Memory configuration for this session. */ + @JsonProperty("memory") MemoryConfiguration memory, + /** Capabilities enabled for this session. */ + @JsonProperty("sessionCapabilities") List sessionCapabilities +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsAdditionalContentExclusionPolicy.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsAdditionalContentExclusionPolicy.java new file mode 100644 index 000000000..bbc53711a --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsAdditionalContentExclusionPolicy.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * Content-exclusion policy supplied to `sessions.open` options, with rules, last-updated data, and scope. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionOpenOptionsAdditionalContentExclusionPolicy( + @JsonProperty("rules") List rules, + @JsonProperty("last_updated_at") Object lastUpdatedAt, + /** Allowed values for the `SessionOpenOptionsAdditionalContentExclusionPolicyScope` enumeration. */ + @JsonProperty("scope") SessionOpenOptionsAdditionalContentExclusionPolicyScope scope +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsAdditionalContentExclusionPolicyRule.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsAdditionalContentExclusionPolicyRule.java new file mode 100644 index 000000000..403550ae2 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsAdditionalContentExclusionPolicyRule.java @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * Single content-exclusion rule supplied to `sessions.open` options, with paths, match conditions, and source. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionOpenOptionsAdditionalContentExclusionPolicyRule( + @JsonProperty("paths") List paths, + @JsonProperty("ifAnyMatch") List ifAnyMatch, + @JsonProperty("ifNoneMatch") List ifNoneMatch, + /** Source descriptor for a `sessions.open` content-exclusion rule, with source name and type. */ + @JsonProperty("source") SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource source +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource.java new file mode 100644 index 000000000..9cfa5894c --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource.java @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Source descriptor for a `sessions.open` content-exclusion rule, with source name and type. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource( + @JsonProperty("name") String name, + @JsonProperty("type") String type +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsAdditionalContentExclusionPolicyScope.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsAdditionalContentExclusionPolicyScope.java new file mode 100644 index 000000000..66296cd19 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsAdditionalContentExclusionPolicyScope.java @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * Allowed values for the `SessionOpenOptionsAdditionalContentExclusionPolicyScope` enumeration. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum SessionOpenOptionsAdditionalContentExclusionPolicyScope { + /** The {@code repo} variant. */ + REPO("repo"), + /** The {@code all} variant. */ + ALL("all"); + + private final String value; + SessionOpenOptionsAdditionalContentExclusionPolicyScope(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static SessionOpenOptionsAdditionalContentExclusionPolicyScope fromValue(String value) { + for (SessionOpenOptionsAdditionalContentExclusionPolicyScope v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown SessionOpenOptionsAdditionalContentExclusionPolicyScope value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsEnvValueMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsEnvValueMode.java new file mode 100644 index 000000000..cfbfeaa74 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsEnvValueMode.java @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * How MCP server environment values are interpreted. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum SessionOpenOptionsEnvValueMode { + /** The {@code direct} variant. */ + DIRECT("direct"), + /** The {@code indirect} variant. */ + INDIRECT("indirect"); + + private final String value; + SessionOpenOptionsEnvValueMode(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static SessionOpenOptionsEnvValueMode fromValue(String value) { + for (SessionOpenOptionsEnvValueMode v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown SessionOpenOptionsEnvValueMode value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsReasoningSummary.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsReasoningSummary.java new file mode 100644 index 000000000..391e45a29 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOpenOptionsReasoningSummary.java @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * Initial reasoning summary mode for supported model clients. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum SessionOpenOptionsReasoningSummary { + /** The {@code none} variant. */ + NONE("none"), + /** The {@code concise} variant. */ + CONCISE("concise"), + /** The {@code detailed} variant. */ + DETAILED("detailed"); + + private final String value; + SessionOpenOptionsReasoningSummary(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static SessionOpenOptionsReasoningSummary fromValue(String value) { + for (SessionOpenOptionsReasoningSummary v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown SessionOpenOptionsReasoningSummary value: " + value); + } +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionOptionsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOptionsApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionOptionsApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOptionsApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionOptionsUpdateParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOptionsUpdateParams.java similarity index 93% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionOptionsUpdateParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOptionsUpdateParams.java index fb8c25b3d..080b47866 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionOptionsUpdateParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOptionsUpdateParams.java @@ -32,7 +32,7 @@ public record SessionOptionsUpdateParams( @JsonProperty("model") String model, /** Per-property model capability overrides for the selected model. */ @JsonProperty("modelCapabilitiesOverrides") ModelCapabilitiesOverride modelCapabilitiesOverrides, - /** Reasoning effort for the selected model (model-defined enum). */ + /** Reasoning effort for the selected model. CAPI values are model-defined and validated against the selected model; BYOK providers may define additional values. When omitted, no effort override is applied. */ @JsonProperty("reasoningEffort") String reasoningEffort, /** Reasoning summary mode for supported model clients. */ @JsonProperty("reasoningSummary") OptionsUpdateReasoningSummary reasoningSummary, @@ -66,9 +66,11 @@ public record SessionOptionsUpdateParams( @JsonProperty("toolFilterPrecedence") OptionsUpdateToolFilterPrecedence toolFilterPrecedence, /** Whether shell-script safety heuristics are enabled. */ @JsonProperty("enableScriptSafety") Boolean enableScriptSafety, - /** Shell init profile (`None` or `NonInteractive`). */ + /** Per-session settings for built-in shell tools. */ + @JsonProperty("shell") ShellOptions shell, + /** Use shell.initProfile instead. Shell init profile (`None` or `NonInteractive`). */ @JsonProperty("shellInitProfile") String shellInitProfile, - /** Per-shell process flags (e.g., `pwsh` arguments). */ + /** PowerShell process flags applied to built-in and user-requested shell commands. */ @JsonProperty("shellProcessFlags") List shellProcessFlags, /** Resolved sandbox configuration. */ @JsonProperty("sandboxConfig") SandboxConfig sandboxConfig, @@ -82,7 +84,7 @@ public record SessionOptionsUpdateParams( @JsonProperty("skillDirectories") List skillDirectories, /** Skill IDs that should be excluded from this session. */ @JsonProperty("disabledSkills") List disabledSkills, - /** Whether to discover custom instructions on demand after successful file views (AGENTS.md / CLAUDE.md / .github/copilot-instructions.md surfacing). Combined with `skipCustomInstructions` and the runtime-side `ON_DEMAND_INSTRUCTIONS` feature flag. */ + /** Whether to discover custom instructions on demand after successful file views (AGENTS.md / CLAUDE.md / .github/copilot-instructions.md surfacing). Combined with `skipCustomInstructions`. */ @JsonProperty("enableOnDemandInstructionDiscovery") Boolean enableOnDemandInstructionDiscovery, /** Maximum decoded byte size of a single model-facing binary tool result (e.g. an image) persisted inline in session events and re-presented to the model on later turns / resume. Larger results are persisted as a metadata-only marker and shown to the model as a short text note. Defaults to 10 MB. */ @JsonProperty("maxInlineBinaryBytes") Long maxInlineBinaryBytes, @@ -116,6 +118,8 @@ public record SessionOptionsUpdateParams( @JsonProperty("agentContext") String agentContext, /** Override directory for the session-events log. When unset, the runtime's default events log directory is used. */ @JsonProperty("eventsLogDirectory") String eventsLogDirectory, + /** Whether subagent callback events should be forwarded into the session event log sink. */ + @JsonProperty("eventsLogIncludesSubagents") Boolean eventsLogIncludesSubagents, /** Additional content-exclusion policies to merge into the session's policy set. */ @JsonProperty("additionalContentExclusionPolicies") List additionalContentExclusionPolicies, /** Whether to expose the `manage_schedule` tool to the agent. The runtime always owns the per-session schedule registry; this flag only controls tool exposure (typically gated to staff users). */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionOptionsUpdateResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOptionsUpdateResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionOptionsUpdateResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionOptionsUpdateResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsApi.java similarity index 94% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsApi.java index aba317708..25d2e3666 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsApi.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsApi.java @@ -162,14 +162,19 @@ public CompletableFuture setRequired(Sessio } /** - * No parameters; clears all session-scoped tool permission approvals. + * Clears session-scoped tool permission approvals, and optionally the location-scoped ones. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 */ @CopilotExperimental - public CompletableFuture resetSessionApprovals() { - return caller.invoke("session.permissions.resetSessionApprovals", java.util.Map.of("sessionId", this.sessionId), SessionPermissionsResetSessionApprovalsResult.class); + public CompletableFuture resetSessionApprovals(SessionPermissionsResetSessionApprovalsParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.permissions.resetSessionApprovals", _p, SessionPermissionsResetSessionApprovalsResult.class); } /** diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsConfigureParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsConfigureParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsConfigureParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsConfigureParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsConfigureResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsConfigureResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsConfigureResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsConfigureResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsFolderTrustAddTrustedParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsFolderTrustAddTrustedParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsFolderTrustAddTrustedParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsFolderTrustAddTrustedParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsFolderTrustAddTrustedResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsFolderTrustAddTrustedResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsFolderTrustAddTrustedResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsFolderTrustAddTrustedResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsFolderTrustApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsFolderTrustApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsFolderTrustApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsFolderTrustApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsFolderTrustIsTrustedParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsFolderTrustIsTrustedParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsFolderTrustIsTrustedParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsFolderTrustIsTrustedParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsFolderTrustIsTrustedResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsFolderTrustIsTrustedResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsFolderTrustIsTrustedResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsFolderTrustIsTrustedResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsGetAllowAllParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsGetAllowAllParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsGetAllowAllParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsGetAllowAllParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsGetAllowAllResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsGetAllowAllResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsGetAllowAllResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsGetAllowAllResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsHandlePendingPermissionRequestParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsHandlePendingPermissionRequestParams.java similarity index 82% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsHandlePendingPermissionRequestParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsHandlePendingPermissionRequestParams.java index 075dcee26..a4d2ba67e 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsHandlePendingPermissionRequestParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsHandlePendingPermissionRequestParams.java @@ -29,6 +29,8 @@ public record SessionPermissionsHandlePendingPermissionRequestParams( /** Request ID of the pending permission request */ @JsonProperty("requestId") String requestId, /** The client's response to the pending permission prompt */ - @JsonProperty("result") Object result + @JsonProperty("result") Object result, + /** Optional informational context describing how and where this response was made. Omit it to preserve legacy behavior without attributing an origin. */ + @JsonProperty("decisionContext") PermissionDecisionContext decisionContext ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsHandlePendingPermissionRequestResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsHandlePendingPermissionRequestResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsHandlePendingPermissionRequestResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsHandlePendingPermissionRequestResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsLocationsAddToolApprovalParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsLocationsAddToolApprovalParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsLocationsAddToolApprovalParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsLocationsAddToolApprovalParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsLocationsAddToolApprovalResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsLocationsAddToolApprovalResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsLocationsAddToolApprovalResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsLocationsAddToolApprovalResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsLocationsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsLocationsApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsLocationsApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsLocationsApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsLocationsApplyParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsLocationsApplyParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsLocationsApplyParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsLocationsApplyParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsLocationsApplyResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsLocationsApplyResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsLocationsApplyResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsLocationsApplyResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsLocationsResolveParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsLocationsResolveParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsLocationsResolveParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsLocationsResolveParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsLocationsResolveResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsLocationsResolveResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsLocationsResolveResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsLocationsResolveResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsModifyRulesParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsModifyRulesParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsModifyRulesParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsModifyRulesParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsModifyRulesResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsModifyRulesResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsModifyRulesResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsModifyRulesResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsNotifyPromptShownParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsNotifyPromptShownParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsNotifyPromptShownParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsNotifyPromptShownParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsNotifyPromptShownResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsNotifyPromptShownResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsNotifyPromptShownResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsNotifyPromptShownResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsAddParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsAddParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsAddParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsAddParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsAddResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsAddResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsAddResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsAddResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsIsPathWithinAllowedDirectoriesParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsIsPathWithinAllowedDirectoriesParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsIsPathWithinAllowedDirectoriesParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsIsPathWithinAllowedDirectoriesParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsIsPathWithinAllowedDirectoriesResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsIsPathWithinAllowedDirectoriesResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsIsPathWithinAllowedDirectoriesResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsIsPathWithinAllowedDirectoriesResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsIsPathWithinWorkspaceParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsIsPathWithinWorkspaceParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsIsPathWithinWorkspaceParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsIsPathWithinWorkspaceParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsIsPathWithinWorkspaceResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsIsPathWithinWorkspaceResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsIsPathWithinWorkspaceResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsIsPathWithinWorkspaceResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsListParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsListParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsListParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsListParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsListResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsListResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsListResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsListResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsUpdatePrimaryParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsUpdatePrimaryParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsUpdatePrimaryParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsUpdatePrimaryParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsUpdatePrimaryResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsUpdatePrimaryResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsUpdatePrimaryResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPathsUpdatePrimaryResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPendingRequestsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPendingRequestsParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPendingRequestsParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPendingRequestsParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPendingRequestsResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPendingRequestsResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPendingRequestsResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsPendingRequestsResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsResetSessionApprovalsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsResetSessionApprovalsParams.java similarity index 78% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsResetSessionApprovalsParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsResetSessionApprovalsParams.java index 881c081ab..68ef9814d 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsResetSessionApprovalsParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsResetSessionApprovalsParams.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * No parameters; clears all session-scoped tool permission approvals. + * Clears session-scoped tool permission approvals, and optionally the location-scoped ones. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 @@ -25,6 +25,8 @@ @JsonIgnoreProperties(ignoreUnknown = true) public record SessionPermissionsResetSessionApprovalsParams( /** Target session identifier */ - @JsonProperty("sessionId") String sessionId + @JsonProperty("sessionId") String sessionId, + /** Whether location-scoped approvals are cleared too. Defaults to `true`. */ + @JsonProperty("includeLocation") Boolean includeLocation ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsResetSessionApprovalsResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsResetSessionApprovalsResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsResetSessionApprovalsResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsResetSessionApprovalsResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetAllowAllParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetAllowAllParams.java similarity index 92% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetAllowAllParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetAllowAllParams.java index 7bde47bc8..f31646f76 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetAllowAllParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetAllowAllParams.java @@ -30,7 +30,7 @@ public record SessionPermissionsSetAllowAllParams( @JsonProperty("mode") PermissionsAllowAllMode mode, /** Legacy full allow-all toggle. Prefer `mode`; when `mode` is omitted, `enabled: true` is treated as `mode: "on"` and any other value is treated as `mode: "off"`. */ @JsonProperty("enabled") Boolean enabled, - /** Optional model id for the `auto` mode auto-approval LLM judging. Only meaningful when `mode` is `auto`; ignored otherwise. When omitted, the session's active model is used. */ + /** Optional model id for the `auto` mode auto-approval LLM judging. Only meaningful when `mode` is `auto`; ignored otherwise. When omitted, the session resolves a default judge model: `gpt-5.5` for CAPI sessions and the session's active model for BYOK sessions. */ @JsonProperty("model") String model, /** Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers. */ @JsonProperty("source") PermissionsSetAllowAllSource source diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetAllowAllResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetAllowAllResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetAllowAllResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetAllowAllResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetApproveAllParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetApproveAllParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetApproveAllParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetApproveAllParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetApproveAllResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetApproveAllResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetApproveAllResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetApproveAllResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetRequiredParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetRequiredParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetRequiredParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetRequiredParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetRequiredResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetRequiredResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetRequiredResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsSetRequiredResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsUrlsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsUrlsApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsUrlsApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsUrlsApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsUrlsSetUnrestrictedModeParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsUrlsSetUnrestrictedModeParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsUrlsSetUnrestrictedModeParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsUrlsSetUnrestrictedModeParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsUrlsSetUnrestrictedModeResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsUrlsSetUnrestrictedModeResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsUrlsSetUnrestrictedModeResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPermissionsUrlsSetUnrestrictedModeResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPlanApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPlanApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPlanApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPlanApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPlanDeleteParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPlanDeleteParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPlanDeleteParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPlanDeleteParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPlanReadParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPlanReadParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPlanReadParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPlanReadParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPlanReadResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPlanReadResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPlanReadResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPlanReadResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPlanReadSqlTodosParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPlanReadSqlTodosParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPlanReadSqlTodosParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPlanReadSqlTodosParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPlanReadSqlTodosResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPlanReadSqlTodosResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPlanReadSqlTodosResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPlanReadSqlTodosResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPlanReadSqlTodosWithDependenciesParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPlanReadSqlTodosWithDependenciesParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPlanReadSqlTodosWithDependenciesParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPlanReadSqlTodosWithDependenciesParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPlanReadSqlTodosWithDependenciesResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPlanReadSqlTodosWithDependenciesResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPlanReadSqlTodosWithDependenciesResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPlanReadSqlTodosWithDependenciesResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPlanUpdateParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPlanUpdateParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPlanUpdateParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPlanUpdateParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPluginsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPluginsApi.java similarity index 63% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPluginsApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPluginsApi.java index 0b1e4dfef..fa4da43dc 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPluginsApi.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPluginsApi.java @@ -19,6 +19,8 @@ @javax.annotation.processing.Generated("copilot-sdk-codegen") public final class SessionPluginsApi { + private static final com.fasterxml.jackson.databind.ObjectMapper MAPPER = RpcMapper.INSTANCE; + private final RpcCaller caller; private final String sessionId; @@ -41,13 +43,31 @@ public CompletableFuture list() { /** * Optional flags controlling which side effects the reload performs. + *

+ * Invokes the method with no params, applying the runtime defaults. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 */ @CopilotExperimental public CompletableFuture reload() { - return caller.invoke("session.plugins.reload", java.util.Map.of("sessionId", this.sessionId), Void.class); + return reload(null); + } + + /** + * Optional flags controlling which side effects the reload performs. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture reload(SessionPluginsReloadParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = params == null ? MAPPER.createObjectNode() : MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.plugins.reload", _p, Void.class); } } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPluginsListParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPluginsListParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPluginsListParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPluginsListParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionPluginsListResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPluginsListResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionPluginsListResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPluginsListResult.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPluginsReloadParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPluginsReloadParams.java new file mode 100644 index 000000000..b844a21c8 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionPluginsReloadParams.java @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Request parameters for the {@code session.plugins.reload} RPC method. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionPluginsReloadParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Reload MCP server connections after refreshing plugins. Defaults to true. */ + @JsonProperty("reloadMcp") Boolean reloadMcp, + /** Re-run custom-agent discovery after refreshing plugins. Defaults to true. */ + @JsonProperty("reloadCustomAgents") Boolean reloadCustomAgents, + /** Re-load user, plugin, and (subject to `deferRepoHooks`) repo hooks. Defaults to true. Has no effect when the host has not registered a hook reloader (e.g. remote sessions). */ + @JsonProperty("reloadHooks") Boolean reloadHooks, + /** Re-discover and relaunch subprocess extensions (including plugin-shipped extensions) after refreshing plugins. Defaults to true. Has no effect when the session has no active extension controller (e.g. extensions were not requested for the session). */ + @JsonProperty("reloadExtensions") Boolean reloadExtensions, + /** When true, skip repo-level hooks during the hook reload. Use before folder trust is confirmed; load them post-trust via `sessions.loadDeferredRepoHooks`. */ + @JsonProperty("deferRepoHooks") Boolean deferRepoHooks +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionProviderAddParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionProviderAddParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionProviderAddParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionProviderAddParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionProviderAddResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionProviderAddResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionProviderAddResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionProviderAddResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionProviderApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionProviderApi.java similarity index 70% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionProviderApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionProviderApi.java index 4a6fdc186..b4c6b8ccd 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionProviderApi.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionProviderApi.java @@ -32,13 +32,31 @@ public final class SessionProviderApi { /** * Optional model identifier to scope the endpoint snapshot to. + *

+ * Invokes the method with no params, applying the runtime defaults. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 */ @CopilotExperimental public CompletableFuture getEndpoint() { - return caller.invoke("session.provider.getEndpoint", java.util.Map.of("sessionId", this.sessionId), SessionProviderGetEndpointResult.class); + return getEndpoint(null); + } + + /** + * Optional model identifier to scope the endpoint snapshot to. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture getEndpoint(SessionProviderGetEndpointParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = params == null ? MAPPER.createObjectNode() : MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.provider.getEndpoint", _p, SessionProviderGetEndpointResult.class); } /** diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionProviderGetEndpointParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionProviderGetEndpointParams.java new file mode 100644 index 000000000..c885b47cd --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionProviderGetEndpointParams.java @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Request parameters for the {@code session.provider.getEndpoint} RPC method. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionProviderGetEndpointParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Model identifier the caller intends to use against the returned endpoint. Used to pick the correct wire shape. Omit to use whichever model the session is currently using. */ + @JsonProperty("modelId") String modelId +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionProviderGetEndpointResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionProviderGetEndpointResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionProviderGetEndpointResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionProviderGetEndpointResult.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueApi.java new file mode 100644 index 000000000..6e40bdfeb --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueApi.java @@ -0,0 +1,286 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.github.copilot.CopilotExperimental; +import java.util.concurrent.CompletableFuture; +import javax.annotation.processing.Generated; + +/** + * API methods for the {@code queue} namespace. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SessionQueueApi { + + private static final com.fasterxml.jackson.databind.ObjectMapper MAPPER = RpcMapper.INSTANCE; + + private final RpcCaller caller; + private final String sessionId; + + /** @param caller the RPC transport function */ + SessionQueueApi(RpcCaller caller, String sessionId) { + this.caller = caller; + this.sessionId = sessionId; + } + + /** + * Identifies the target session. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture pendingItems() { + return caller.invoke("session.queue.pendingItems", java.util.Map.of("sessionId", this.sessionId), SessionQueuePendingItemsResult.class); + } + + /** + * Identifies the target session. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture snapshot() { + return caller.invoke("session.queue.snapshot", java.util.Map.of("sessionId", this.sessionId), SessionQueueSnapshotResult.class); + } + + /** + * Parameters for moving a queued item by stable id. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture moveItem(SessionQueueMoveItemParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.queue.moveItem", _p, SessionQueueMoveItemResult.class); + } + + /** + * Parameters for inserting a queued message at a public visible position. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture insertAt(SessionQueueInsertAtParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.queue.insertAt", _p, SessionQueueInsertAtResult.class); + } + + /** + * Parameters for removing a queued item by stable id. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture removeAt(SessionQueueRemoveAtParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.queue.removeAt", _p, SessionQueueRemoveAtResult.class); + } + + /** + * Parameters for editing a single queued message. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture updateText(SessionQueueUpdateTextParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.queue.updateText", _p, SessionQueueUpdateTextResult.class); + } + + /** + * Parameters for duplicating a queued item. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture duplicateAt(SessionQueueDuplicateAtParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.queue.duplicateAt", _p, SessionQueueDuplicateAtResult.class); + } + + /** + * Parameters for acquiring or releasing the queued-lane drain pause. Acquisition is exclusive and non-idempotent: `paused: true` against an already-paused session fails with `queue_already_paused`. The pause is never released automatically — it is not tied to the caller's lifetime, so a client that exits without sending `paused: false` leaves the lane frozen. Release is unowned: `paused: false` clears the pause for any caller, including one that never acquired it. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture setDrainPaused(SessionQueueSetDrainPausedParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.queue.setDrainPaused", _p, Void.class); + } + + /** + * Parameters for steering a queued message into a live turn. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture sendNow(SessionQueueSendNowParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.queue.sendNow", _p, SessionQueueSendNowResult.class); + } + + /** + * Identifies the target session. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture hasPending() { + return caller.invoke("session.queue.hasPending", java.util.Map.of("sessionId", this.sessionId), SessionQueueHasPendingResult.class); + } + + /** + * Inputs for starting a deferred-idle drain. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture beginDeferredIdleDrain(SessionQueueBeginDeferredIdleDrainParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.queue.beginDeferredIdleDrain", _p, SessionQueueBeginDeferredIdleDrainResult.class); + } + + /** + * Inputs for completing a deferred-idle drain. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture finishDeferredIdleDrain(SessionQueueFinishDeferredIdleDrainParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.queue.finishDeferredIdleDrain", _p, SessionQueueFinishDeferredIdleDrainResult.class); + } + + /** + * Inputs for marking session.idle deferred in native state. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture deferSessionIdle(SessionQueueDeferSessionIdleParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.queue.deferSessionIdle", _p, Void.class); + } + + /** + * Identifies the target session. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture removeMostRecent() { + return caller.invoke("session.queue.removeMostRecent", java.util.Map.of("sessionId", this.sessionId), SessionQueueRemoveMostRecentResult.class); + } + + /** + * Identifies the target session. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture clear() { + return caller.invoke("session.queue.clear", java.util.Map.of("sessionId", this.sessionId), Void.class); + } + + /** + * Internal filter for consuming queued system notifications. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture consumeSystemNotifications(SessionQueueConsumeSystemNotificationsParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.queue.consumeSystemNotifications", _p, SessionQueueConsumeSystemNotificationsResult.class); + } + + /** + * Identifies the target session. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture enqueueResumePending() { + return caller.invoke("session.queue.enqueueResumePending", java.util.Map.of("sessionId", this.sessionId), SessionQueueEnqueueResumePendingResult.class); + } + + /** + * Identifies the target session. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture process() { + return caller.invoke("session.queue.process", java.util.Map.of("sessionId", this.sessionId), Void.class); + } + +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueBeginDeferredIdleDrainParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueBeginDeferredIdleDrainParams.java new file mode 100644 index 000000000..4973e3107 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueBeginDeferredIdleDrainParams.java @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Inputs for starting a deferred-idle drain. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionQueueBeginDeferredIdleDrainParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Whether the host still has active background work. */ + @JsonProperty("activeBackgroundWork") Boolean activeBackgroundWork +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueBeginDeferredIdleDrainResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueBeginDeferredIdleDrainResult.java new file mode 100644 index 000000000..77e6decb9 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueBeginDeferredIdleDrainResult.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Whether a deferred-idle drain should run. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionQueueBeginDeferredIdleDrainResult( + /** True when the host should run finishDeferredIdleDrain asynchronously. */ + @JsonProperty("shouldDrain") Boolean shouldDrain +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueClearParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueClearParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueClearParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueClearParams.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueConsumeSystemNotificationsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueConsumeSystemNotificationsParams.java new file mode 100644 index 000000000..6449ce766 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueConsumeSystemNotificationsParams.java @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Internal filter for consuming queued system notifications. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionQueueConsumeSystemNotificationsParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Opaque runtime-owned filter object. */ + @JsonProperty("filter") Object filter +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueConsumeSystemNotificationsResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueConsumeSystemNotificationsResult.java new file mode 100644 index 000000000..bbc371588 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueConsumeSystemNotificationsResult.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Indicates whether a user-facing pending item was removed. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionQueueConsumeSystemNotificationsResult( + /** True if a user-facing pending item was removed (LIFO across both queues); false when no removable items remained. */ + @JsonProperty("removed") Boolean removed +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueDeferSessionIdleParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueDeferSessionIdleParams.java new file mode 100644 index 000000000..7b3dff9ef --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueDeferSessionIdleParams.java @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Inputs for marking session.idle deferred in native state. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionQueueDeferSessionIdleParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Whether the deferred idle was caused by an aborted foreground turn. */ + @JsonProperty("aborted") Boolean aborted +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsListParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueDuplicateAtParams.java similarity index 84% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsListParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueDuplicateAtParams.java index 6cd085b38..bf16f9d35 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionCommandsListParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueDuplicateAtParams.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Optional filters controlling which command sources to include in the listing. + * Parameters for duplicating a queued item. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 @@ -23,8 +23,9 @@ @javax.annotation.processing.Generated("copilot-sdk-codegen") @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) -public record SessionCommandsListParams( +public record SessionQueueDuplicateAtParams( /** Target session identifier */ - @JsonProperty("sessionId") String sessionId + @JsonProperty("sessionId") String sessionId, + @JsonProperty("id") String id ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueDuplicateAtResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueDuplicateAtResult.java new file mode 100644 index 000000000..0be932f95 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueDuplicateAtResult.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Result of duplicating a queued item. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionQueueDuplicateAtResult( + /** Fresh stable opaque id assigned to the duplicate. */ + @JsonProperty("id") String id +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueEnqueueResumePendingParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueEnqueueResumePendingParams.java new file mode 100644 index 000000000..1a0ec546a --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueEnqueueResumePendingParams.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Identifies the target session. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionQueueEnqueueResumePendingParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueEnqueueResumePendingResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueEnqueueResumePendingResult.java new file mode 100644 index 000000000..324765b4a --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueEnqueueResumePendingResult.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Result of enqueueing the resume-pending wake item. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionQueueEnqueueResumePendingResult( + /** True when a wake item was newly queued. */ + @JsonProperty("queued") Boolean queued +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueFinishDeferredIdleDrainParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueFinishDeferredIdleDrainParams.java new file mode 100644 index 000000000..b6b29057d --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueFinishDeferredIdleDrainParams.java @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Inputs for completing a deferred-idle drain. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionQueueFinishDeferredIdleDrainParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Whether the host still has active background work. */ + @JsonProperty("activeBackgroundWork") Boolean activeBackgroundWork, + /** Whether native queued work remains. */ + @JsonProperty("hasPending") Boolean hasPending +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueFinishDeferredIdleDrainResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueFinishDeferredIdleDrainResult.java new file mode 100644 index 000000000..1e6cc5257 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueFinishDeferredIdleDrainResult.java @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Action selected by the native deferred-idle drain. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionQueueFinishDeferredIdleDrainResult( + /** One of none, processQueue, or emitSessionIdle. */ + @JsonProperty("action") String action, + /** Whether the deferred idle was caused by an aborted foreground turn. */ + @JsonProperty("aborted") Boolean aborted +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryCompactParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueHasPendingParams.java similarity index 92% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryCompactParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueHasPendingParams.java index 9dc6a0ee9..e587fec4c 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryCompactParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueHasPendingParams.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Optional compaction parameters. + * Identifies the target session. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 @@ -23,7 +23,7 @@ @javax.annotation.processing.Generated("copilot-sdk-codegen") @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) -public record SessionHistoryCompactParams( +public record SessionQueueHasPendingParams( /** Target session identifier */ @JsonProperty("sessionId") String sessionId ) { diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueHasPendingResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueHasPendingResult.java new file mode 100644 index 000000000..5373856a4 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueHasPendingResult.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Whether the native queue has pending work. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionQueueHasPendingResult( + /** True when queued or immediate native work is pending. */ + @JsonProperty("hasPending") Boolean hasPending +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueInsertAtParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueInsertAtParams.java new file mode 100644 index 000000000..981aefb5f --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueInsertAtParams.java @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Parameters for inserting a queued message at a public visible position. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionQueueInsertAtParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Zero-based position in the public visible queue. Values outside the queue clamp to an end. */ + @JsonProperty("position") Long position, + @JsonProperty("message") QueueInsertMessage message +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueInsertAtResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueInsertAtResult.java new file mode 100644 index 000000000..1d4805e8c --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueInsertAtResult.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Result of inserting a queued message. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionQueueInsertAtResult( + /** Fresh stable opaque id assigned to the inserted item. */ + @JsonProperty("id") String id +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueMoveItemParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueMoveItemParams.java new file mode 100644 index 000000000..0a584f50c --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueMoveItemParams.java @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Parameters for moving a queued item by stable id. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionQueueMoveItemParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Stable opaque queued-item id. */ + @JsonProperty("id") String id, + /** Zero-based target position in the public visible queue. Values outside the queue clamp to an end. */ + @JsonProperty("toPosition") Long toPosition +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueMoveItemResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueMoveItemResult.java new file mode 100644 index 000000000..431a08175 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueMoveItemResult.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Result of moving a queued item. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionQueueMoveItemResult( + /** True when the item changed position; false when it was already at the requested position. */ + @JsonProperty("changed") Boolean changed +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueuePendingItemsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueuePendingItemsParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionQueuePendingItemsParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueuePendingItemsParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueuePendingItemsResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueuePendingItemsResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionQueuePendingItemsResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueuePendingItemsResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionAgentListParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueProcessParams.java similarity index 96% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionAgentListParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueProcessParams.java index 44636f59d..8c8edfe55 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionAgentListParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueProcessParams.java @@ -23,7 +23,7 @@ @javax.annotation.processing.Generated("copilot-sdk-codegen") @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) -public record SessionAgentListParams( +public record SessionQueueProcessParams( /** Target session identifier */ @JsonProperty("sessionId") String sessionId ) { diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueRemoveAtParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueRemoveAtParams.java new file mode 100644 index 000000000..bc7cd3e12 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueRemoveAtParams.java @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Parameters for removing a queued item by stable id. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionQueueRemoveAtParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + @JsonProperty("id") String id +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueRemoveAtResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueRemoveAtResult.java new file mode 100644 index 000000000..0f5d95487 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueRemoveAtResult.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Result of removing a queued item. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionQueueRemoveAtResult( + /** True when the addressed item was removed. */ + @JsonProperty("removed") Boolean removed +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueRemoveMostRecentParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueRemoveMostRecentParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueRemoveMostRecentParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueRemoveMostRecentParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueRemoveMostRecentResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueRemoveMostRecentResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionQueueRemoveMostRecentResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueRemoveMostRecentResult.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSendNowParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSendNowParams.java new file mode 100644 index 000000000..6381636a8 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSendNowParams.java @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Parameters for steering a queued message into a live turn. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionQueueSendNowParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + @JsonProperty("id") String id +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSendNowResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSendNowResult.java new file mode 100644 index 000000000..584bd59d1 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSendNowResult.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Result of trying to steer a queued message into a live turn. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionQueueSendNowResult( + /** True when the item was accepted into the steering lane; false when no main turn was live. */ + @JsonProperty("steered") Boolean steered +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSetDrainPausedParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSetDrainPausedParams.java new file mode 100644 index 000000000..f51e33ea1 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSetDrainPausedParams.java @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Parameters for acquiring or releasing the queued-lane drain pause. Acquisition is exclusive and non-idempotent: `paused: true` against an already-paused session fails with `queue_already_paused`. The pause is never released automatically — it is not tied to the caller's lifetime, so a client that exits without sending `paused: false` leaves the lane frozen. Release is unowned: `paused: false` clears the pause for any caller, including one that never acquired it. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionQueueSetDrainPausedParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + @JsonProperty("paused") Boolean paused +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSnapshotParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSnapshotParams.java new file mode 100644 index 000000000..dff5db8a8 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSnapshotParams.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Identifies the target session. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionQueueSnapshotParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSnapshotResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSnapshotResult.java new file mode 100644 index 000000000..7ae1076d6 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueSnapshotResult.java @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * Internal snapshot of native queue state for local session orchestration. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionQueueSnapshotResult( + /** User-facing pending items in FIFO order. */ + @JsonProperty("items") List items, + /** Immediate steering messages waiting for an active turn. */ + @JsonProperty("steeringMessages") List steeringMessages, + /** Insertion orders for queued items, aligned with `items`. */ + @JsonProperty("itemOrders") List itemOrders, + /** Insertion orders for immediate steering messages, aligned with `steeringMessages`. */ + @JsonProperty("steeringMessageOrders") List steeringMessageOrders +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueUpdateTextParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueUpdateTextParams.java new file mode 100644 index 000000000..139a5ba2a --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueUpdateTextParams.java @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Parameters for editing a single queued message. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionQueueUpdateTextParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + @JsonProperty("id") String id, + @JsonProperty("prompt") String prompt, + @JsonProperty("displayPrompt") String displayPrompt +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueUpdateTextResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueUpdateTextResult.java new file mode 100644 index 000000000..3809f5bd6 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionQueueUpdateTextResult.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Result of editing a queued message. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionQueueUpdateTextResult( + /** True when the stored text changed. */ + @JsonProperty("updated") Boolean updated +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionRemoteApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionRemoteApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionRemoteApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionRemoteApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionRemoteDisableParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionRemoteDisableParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionRemoteDisableParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionRemoteDisableParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionRemoteEnableParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionRemoteEnableParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionRemoteEnableParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionRemoteEnableParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionRemoteEnableResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionRemoteEnableResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionRemoteEnableResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionRemoteEnableResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionRemoteNotifySteerableChangedParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionRemoteNotifySteerableChangedParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionRemoteNotifySteerableChangedParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionRemoteNotifySteerableChangedParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionRpc.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionRpc.java similarity index 83% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionRpc.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionRpc.java index 3ec3464d0..05cfd396d 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionRpc.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionRpc.java @@ -85,6 +85,8 @@ public final class SessionRpc { public final SessionMetadataApi metadata; /** API methods for the {@code settings} namespace. */ public final SessionSettingsApi settings; + /** API methods for the {@code contentExclusion} namespace. */ + public final SessionContentExclusionApi contentExclusion; /** API methods for the {@code shell} namespace. */ public final SessionShellApi shell; /** API methods for the {@code history} namespace. */ @@ -95,6 +97,8 @@ public final class SessionRpc { public final SessionEventLogApi eventLog; /** API methods for the {@code usage} namespace. */ public final SessionUsageApi usage; + /** API methods for the {@code limitPrediction} namespace. */ + public final SessionLimitPredictionApi limitPrediction; /** API methods for the {@code remote} namespace. */ public final SessionRemoteApi remote; /** API methods for the {@code visibility} namespace. */ @@ -139,11 +143,13 @@ public SessionRpc(RpcCaller caller, String sessionId) { this.permissions = new SessionPermissionsApi(caller, sessionId); this.metadata = new SessionMetadataApi(caller, sessionId); this.settings = new SessionSettingsApi(caller, sessionId); + this.contentExclusion = new SessionContentExclusionApi(caller, sessionId); this.shell = new SessionShellApi(caller, sessionId); this.history = new SessionHistoryApi(caller, sessionId); this.queue = new SessionQueueApi(caller, sessionId); this.eventLog = new SessionEventLogApi(caller, sessionId); this.usage = new SessionUsageApi(caller, sessionId); + this.limitPrediction = new SessionLimitPredictionApi(caller, sessionId); this.remote = new SessionRemoteApi(caller, sessionId); this.visibility = new SessionVisibilityApi(caller, sessionId); this.schedule = new SessionScheduleApi(caller, sessionId); @@ -192,6 +198,22 @@ public CompletableFuture sendMessages(SessionSendMess return caller.invoke("session.sendMessages", _p, SessionSendMessagesResult.class); } + /** + * Internal request for sending a system notification. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture sendSystemNotification(SessionSendSystemNotificationParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.sendSystemNotification", _p, Void.class); + } + /** * Parameters for aborting the current turn *

@@ -208,6 +230,33 @@ public CompletableFuture abort(SessionAbortParams params) { return caller.invoke("session.abort", _p, SessionAbortResult.class); } + /** + * Parameters for interrupting the main agent turn. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture interruptMainTurn(SessionInterruptMainTurnParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.interruptMainTurn", _p, SessionInterruptMainTurnResult.class); + } + + /** + * Identifies the target session. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture cancelAllBackgroundAgents() { + return caller.invoke("session.cancelAllBackgroundAgents", java.util.Map.of("sessionId", this.sessionId), Void.class); + } + /** * Parameters for shutting down the session *

diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddAtParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddAtParams.java new file mode 100644 index 000000000..0a099bdf4 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddAtParams.java @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Register an absolute-time scheduled prompt. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionScheduleAddAtParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Epoch milliseconds when the prompt should fire. */ + @JsonProperty("at") Long at, + /** Prompt text to enqueue when the schedule fires. */ + @JsonProperty("prompt") String prompt, + /** Whether the schedule should re-arm after each tick. Defaults to false. */ + @JsonProperty("recurring") Boolean recurring, + /** Optional display-only prompt label. */ + @JsonProperty("displayPrompt") String displayPrompt +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddAtResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddAtResult.java new file mode 100644 index 000000000..7952fdc88 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddAtResult.java @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Result of registering or re-arming a scheduled prompt. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionScheduleAddAtResult( + /** The registered or updated schedule entry. */ + @JsonProperty("entry") ScheduleEntry entry, + /** User-facing validation error, when registration failed. */ + @JsonProperty("error") String error +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddCronParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddCronParams.java new file mode 100644 index 000000000..08a9cd33b --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddCronParams.java @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Register a cron scheduled prompt. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionScheduleAddCronParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** 5-field cron expression. */ + @JsonProperty("cron") String cron, + /** Prompt text to enqueue when the schedule fires. */ + @JsonProperty("prompt") String prompt, + /** Whether the schedule should re-arm after each tick. Defaults to true. */ + @JsonProperty("recurring") Boolean recurring, + /** Optional display-only prompt label. */ + @JsonProperty("displayPrompt") String displayPrompt, + /** IANA timezone for evaluating the cron expression. */ + @JsonProperty("tz") String tz +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddCronResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddCronResult.java new file mode 100644 index 000000000..193dea1b4 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddCronResult.java @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Result of registering or re-arming a scheduled prompt. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionScheduleAddCronResult( + /** The registered or updated schedule entry. */ + @JsonProperty("entry") ScheduleEntry entry, + /** User-facing validation error, when registration failed. */ + @JsonProperty("error") String error +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddParams.java new file mode 100644 index 000000000..31580758c --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddParams.java @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Register a relative-interval scheduled prompt. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionScheduleAddParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Human-readable interval such as `30s`, `5m`, or `2h`. */ + @JsonProperty("interval") String interval, + /** Prompt text to enqueue when the schedule fires. */ + @JsonProperty("prompt") String prompt, + /** Whether the schedule should re-arm after each tick. Defaults to true. */ + @JsonProperty("recurring") Boolean recurring, + /** Optional display-only prompt label. */ + @JsonProperty("displayPrompt") String displayPrompt +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddResult.java new file mode 100644 index 000000000..021c784c3 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddResult.java @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Result of registering or re-arming a scheduled prompt. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionScheduleAddResult( + /** The registered or updated schedule entry. */ + @JsonProperty("entry") ScheduleEntry entry, + /** User-facing validation error, when registration failed. */ + @JsonProperty("error") String error +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddSelfPacedParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddSelfPacedParams.java new file mode 100644 index 000000000..17a89c1b2 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddSelfPacedParams.java @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Register a self-paced scheduled prompt. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionScheduleAddSelfPacedParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Prompt text to enqueue when the schedule fires. */ + @JsonProperty("prompt") String prompt, + /** Optional display-only prompt label. */ + @JsonProperty("displayPrompt") String displayPrompt +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddSelfPacedResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddSelfPacedResult.java new file mode 100644 index 000000000..65f8745ba --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleAddSelfPacedResult.java @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Result of registering or re-arming a scheduled prompt. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionScheduleAddSelfPacedResult( + /** The registered or updated schedule entry. */ + @JsonProperty("entry") ScheduleEntry entry, + /** User-facing validation error, when registration failed. */ + @JsonProperty("error") String error +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleApi.java similarity index 57% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleApi.java index 690328176..f983f84f7 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesApi.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleApi.java @@ -12,12 +12,12 @@ import javax.annotation.processing.Generated; /** - * API methods for the {@code workspaces} namespace. + * API methods for the {@code schedule} namespace. * * @since 1.0.0 */ @javax.annotation.processing.Generated("copilot-sdk-codegen") -public final class SessionWorkspacesApi { +public final class SessionScheduleApi { private static final com.fasterxml.jackson.databind.ObjectMapper MAPPER = RpcMapper.INSTANCE; @@ -25,7 +25,7 @@ public final class SessionWorkspacesApi { private final String sessionId; /** @param caller the RPC transport function */ - SessionWorkspacesApi(RpcCaller caller, String sessionId) { + SessionScheduleApi(RpcCaller caller, String sessionId) { this.caller = caller; this.sessionId = sessionId; } @@ -37,8 +37,8 @@ public final class SessionWorkspacesApi { * @since 1.0.0 */ @CopilotExperimental - public CompletableFuture getWorkspace() { - return caller.invoke("session.workspaces.getWorkspace", java.util.Map.of("sessionId", this.sessionId), SessionWorkspacesGetWorkspaceResult.class); + public CompletableFuture list() { + return caller.invoke("session.schedule.list", java.util.Map.of("sessionId", this.sessionId), SessionScheduleListResult.class); } /** @@ -48,12 +48,23 @@ public CompletableFuture getWorkspace() { * @since 1.0.0 */ @CopilotExperimental - public CompletableFuture listFiles() { - return caller.invoke("session.workspaces.listFiles", java.util.Map.of("sessionId", this.sessionId), SessionWorkspacesListFilesResult.class); + public CompletableFuture hydrate() { + return caller.invoke("session.schedule.hydrate", java.util.Map.of("sessionId", this.sessionId), Void.class); } /** - * Relative path of the workspace file to read. + * Identifies the target session. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture hasSelfPaced() { + return caller.invoke("session.schedule.hasSelfPaced", java.util.Map.of("sessionId", this.sessionId), SessionScheduleHasSelfPacedResult.class); + } + + /** + * Register a relative-interval scheduled prompt. *

* Note: the {@code sessionId} field in the params record is overridden * by the session-scoped wrapper; any value provided is ignored. @@ -62,14 +73,14 @@ public CompletableFuture listFiles() { * @since 1.0.0 */ @CopilotExperimental - public CompletableFuture readFile(SessionWorkspacesReadFileParams params) { + public CompletableFuture add(SessionScheduleAddParams params) { com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); _p.put("sessionId", this.sessionId); - return caller.invoke("session.workspaces.readFile", _p, SessionWorkspacesReadFileResult.class); + return caller.invoke("session.schedule.add", _p, SessionScheduleAddResult.class); } /** - * Relative path and UTF-8 content for the workspace file to create or overwrite. + * Register a cron scheduled prompt. *

* Note: the {@code sessionId} field in the params record is overridden * by the session-scoped wrapper; any value provided is ignored. @@ -78,25 +89,30 @@ public CompletableFuture readFile(SessionWorksp * @since 1.0.0 */ @CopilotExperimental - public CompletableFuture createFile(SessionWorkspacesCreateFileParams params) { + public CompletableFuture addCron(SessionScheduleAddCronParams params) { com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); _p.put("sessionId", this.sessionId); - return caller.invoke("session.workspaces.createFile", _p, Void.class); + return caller.invoke("session.schedule.addCron", _p, SessionScheduleAddCronResult.class); } /** - * Identifies the target session. + * Register an absolute-time scheduled prompt. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 */ @CopilotExperimental - public CompletableFuture listCheckpoints() { - return caller.invoke("session.workspaces.listCheckpoints", java.util.Map.of("sessionId", this.sessionId), SessionWorkspacesListCheckpointsResult.class); + public CompletableFuture addAt(SessionScheduleAddAtParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.schedule.addAt", _p, SessionScheduleAddAtResult.class); } /** - * Checkpoint number to read. + * Register a self-paced scheduled prompt. *

* Note: the {@code sessionId} field in the params record is overridden * by the session-scoped wrapper; any value provided is ignored. @@ -105,14 +121,14 @@ public CompletableFuture listCheckpoints * @since 1.0.0 */ @CopilotExperimental - public CompletableFuture readCheckpoint(SessionWorkspacesReadCheckpointParams params) { + public CompletableFuture addSelfPaced(SessionScheduleAddSelfPacedParams params) { com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); _p.put("sessionId", this.sessionId); - return caller.invoke("session.workspaces.readCheckpoint", _p, SessionWorkspacesReadCheckpointResult.class); + return caller.invoke("session.schedule.addSelfPaced", _p, SessionScheduleAddSelfPacedResult.class); } /** - * Pasted content to save as a UTF-8 file in the session workspace. + * Re-arm a self-paced scheduled prompt. *

* Note: the {@code sessionId} field in the params record is overridden * by the session-scoped wrapper; any value provided is ignored. @@ -121,14 +137,14 @@ public CompletableFuture readCheckpoint(S * @since 1.0.0 */ @CopilotExperimental - public CompletableFuture saveLargePaste(SessionWorkspacesSaveLargePasteParams params) { + public CompletableFuture rearmSelfPaced(SessionScheduleRearmSelfPacedParams params) { com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); _p.put("sessionId", this.sessionId); - return caller.invoke("session.workspaces.saveLargePaste", _p, SessionWorkspacesSaveLargePasteResult.class); + return caller.invoke("session.schedule.rearmSelfPaced", _p, SessionScheduleRearmSelfPacedResult.class); } /** - * Parameters for computing a workspace diff. + * Identifier of the scheduled prompt to remove. *

* Note: the {@code sessionId} field in the params record is overridden * by the session-scoped wrapper; any value provided is ignored. @@ -137,10 +153,10 @@ public CompletableFuture saveLargePaste(S * @since 1.0.0 */ @CopilotExperimental - public CompletableFuture diff(SessionWorkspacesDiffParams params) { + public CompletableFuture stop(SessionScheduleStopParams params) { com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); _p.put("sessionId", this.sessionId); - return caller.invoke("session.workspaces.diff", _p, SessionWorkspacesDiffResult.class); + return caller.invoke("session.schedule.stop", _p, SessionScheduleStopResult.class); } } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleHasSelfPacedParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleHasSelfPacedParams.java new file mode 100644 index 000000000..7eb31df7f --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleHasSelfPacedParams.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Identifies the target session. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionScheduleHasSelfPacedParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleHasSelfPacedResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleHasSelfPacedResult.java new file mode 100644 index 000000000..84c8e7a50 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleHasSelfPacedResult.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Whether the session currently has an active self-paced schedule. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionScheduleHasSelfPacedResult( + /** True when at least one active schedule is self-paced. */ + @JsonProperty("hasSelfPaced") Boolean hasSelfPaced +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleHydrateParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleHydrateParams.java new file mode 100644 index 000000000..32ec85c7d --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleHydrateParams.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Identifies the target session. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionScheduleHydrateParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleListParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleListParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleListParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleListParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleListResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleListResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleListResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleListResult.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleRearmSelfPacedParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleRearmSelfPacedParams.java new file mode 100644 index 000000000..d1999311c --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleRearmSelfPacedParams.java @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Re-arm a self-paced scheduled prompt. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionScheduleRearmSelfPacedParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Id of the self-paced scheduled prompt. */ + @JsonProperty("id") Long id, + /** Epoch milliseconds when the prompt should next fire. */ + @JsonProperty("at") Long at +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleRearmSelfPacedResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleRearmSelfPacedResult.java new file mode 100644 index 000000000..0280fbacc --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleRearmSelfPacedResult.java @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Result of registering or re-arming a scheduled prompt. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionScheduleRearmSelfPacedResult( + /** The registered or updated schedule entry. */ + @JsonProperty("entry") ScheduleEntry entry, + /** User-facing validation error, when registration failed. */ + @JsonProperty("error") String error +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleStopParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleStopParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleStopParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleStopParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleStopResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleStopResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleStopResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionScheduleStopResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSendMessagesParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSendMessagesParams.java similarity index 84% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionSendMessagesParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSendMessagesParams.java index 2d66b4fe1..294319204 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSendMessagesParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSendMessagesParams.java @@ -42,7 +42,7 @@ public record SessionSendMessagesParams( @JsonProperty("traceparent") String traceparent, /** W3C Trace Context tracestate header for distributed tracing */ @JsonProperty("tracestate") String tracestate, - /** If true, await completion of the agentic loop for this turn before returning. Defaults to false (fire-and-forget). When true, the result still contains the same `messageIds`; the caller can rely on the agent having processed the messages before the call resolves. */ + /** If true, await completion of the agentic loop for this turn before returning. Defaults to false (fire-and-forget). When true, the result still contains the same `messageIds`; the caller can rely on the agent having processed the messages before the call resolves. Transport-dependent tail semantics: on a LOCAL (in-process) session the wait additionally blocks until the completed turn's event tail has been dispatched to this session's in-process subscribers, so a subsequent read of subscriber state already reflects the turn; on a REMOTE session the wait resolves once the loop completes and mirrored delivery follows over the wire. Callers that need the stronger local guarantee on remote sessions should await the event stream explicitly. */ @JsonProperty("wait") Boolean wait_ ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSendMessagesResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSendMessagesResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionSendMessagesResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSendMessagesResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSendParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSendParams.java similarity index 82% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionSendParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSendParams.java index 7762c5683..f19c85ebe 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSendParams.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSendParams.java @@ -42,7 +42,7 @@ public record SessionSendParams( @JsonProperty("billable") Boolean billable, /** If set, the request will fail if the named tool is not available when this message is among the user messages at the start of the current exchange */ @JsonProperty("requiredTool") String requiredTool, - /** Optional provenance tag copied to the resulting user.message event. Must match one of three forms: the literal `system`, `command-` for messages originating from a command (e.g. slash command, Mission Control command), or `schedule-` for messages originating from a scheduled job. */ + /** Optional provenance tag copied to the resulting user.message event. Must be `user`, `system`, `command-` for command-originated messages, `schedule-` for scheduled prompts, or `agent-` for prompts sent by another agent. */ @JsonProperty("source") String source, /** The UI mode the agent was in when this message was sent. Defaults to the session's current mode. */ @JsonProperty("agentMode") SendAgentMode agentMode, @@ -52,7 +52,7 @@ public record SessionSendParams( @JsonProperty("traceparent") String traceparent, /** W3C Trace Context tracestate header for distributed tracing */ @JsonProperty("tracestate") String tracestate, - /** If true, await completion of the agentic loop for this message before returning. Defaults to false (fire-and-forget). When true, the result still contains the same `messageId`; the caller can rely on the agent having processed the message before the call resolves. */ + /** If true, await completion of the agentic loop for this message before returning. Defaults to false (fire-and-forget). When true, the result still contains the same `messageId`; the caller can rely on the agent having processed the message before the call resolves. Transport-dependent tail semantics: on a LOCAL (in-process) session the wait additionally blocks until the completed turn's event tail has been dispatched to this session's in-process subscribers, so a subsequent read of subscriber state already reflects the turn; on a REMOTE session the wait resolves once the loop completes and mirrored delivery follows over the wire. Callers that need the stronger local guarantee on remote sessions should await the event stream explicitly. */ @JsonProperty("wait") Boolean wait_ ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSendResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSendResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionSendResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSendResult.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSendSystemNotificationParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSendSystemNotificationParams.java new file mode 100644 index 000000000..762fe6ef3 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSendSystemNotificationParams.java @@ -0,0 +1,36 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Internal request for sending a system notification. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionSendSystemNotificationParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Notification text to deliver to the model. */ + @JsonProperty("message") String message, + /** Optional structured notification kind. */ + @JsonProperty("kind") Object kind, + /** Internal delivery options, including passive policy. */ + @JsonProperty("options") Object options +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsBuiltInToolAvailabilitySnapshot.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsBuiltInToolAvailabilitySnapshot.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsBuiltInToolAvailabilitySnapshot.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsBuiltInToolAvailabilitySnapshot.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsEvaluatePredicateParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsEvaluatePredicateParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsEvaluatePredicateParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsEvaluatePredicateParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsEvaluatePredicateResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsEvaluatePredicateResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsEvaluatePredicateResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsEvaluatePredicateResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsJobSnapshot.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsJobSnapshot.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsJobSnapshot.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsJobSnapshot.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsModelSnapshot.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsModelSnapshot.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsModelSnapshot.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsModelSnapshot.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsOnlineEvaluationSnapshot.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsOnlineEvaluationSnapshot.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsOnlineEvaluationSnapshot.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsOnlineEvaluationSnapshot.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsPredicateName.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsPredicateName.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsPredicateName.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsPredicateName.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsRepoSnapshot.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsRepoSnapshot.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsRepoSnapshot.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsRepoSnapshot.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsSnapshotParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsSnapshotParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsSnapshotParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsSnapshotParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsSnapshotResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsSnapshotResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsSnapshotResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsSnapshotResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsValidationSnapshot.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsValidationSnapshot.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsValidationSnapshot.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSettingsValidationSnapshot.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionShellApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionShellApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionShellApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionShellApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionShellCancelUserRequestedParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionShellCancelUserRequestedParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionShellCancelUserRequestedParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionShellCancelUserRequestedParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionShellCancelUserRequestedResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionShellCancelUserRequestedResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionShellCancelUserRequestedResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionShellCancelUserRequestedResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionShellExecParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionShellExecParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionShellExecParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionShellExecParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionShellExecResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionShellExecResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionShellExecResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionShellExecResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionShellExecuteUserRequestedParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionShellExecuteUserRequestedParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionShellExecuteUserRequestedParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionShellExecuteUserRequestedParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionShellExecuteUserRequestedResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionShellExecuteUserRequestedResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionShellExecuteUserRequestedResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionShellExecuteUserRequestedResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionShellKillParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionShellKillParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionShellKillParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionShellKillParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionShellKillResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionShellKillResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionShellKillResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionShellKillResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionShutdownParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionShutdownParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionShutdownParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionShutdownParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsDisableParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsDisableParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsDisableParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsDisableParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsEnableParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsEnableParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsEnableParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsEnableParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsEnsureLoadedParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsEnsureLoadedParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsEnsureLoadedParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsEnsureLoadedParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsGetInvokedParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsGetInvokedParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsGetInvokedParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsGetInvokedParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsGetInvokedResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsGetInvokedResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsGetInvokedResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsGetInvokedResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsListParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsListParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsListParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsListParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsListResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsListResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsListResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsListResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsReloadParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsReloadParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsReloadParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsReloadParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsReloadResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsReloadResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsReloadResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSkillsReloadResult.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSource.java new file mode 100644 index 000000000..2914e3c30 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSource.java @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * Which session sources to include. Defaults to `local` for backward compatibility. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum SessionSource { + /** The {@code local} variant. */ + LOCAL("local"), + /** The {@code remote} variant. */ + REMOTE("remote"), + /** The {@code all} variant. */ + ALL("all"); + + private final String value; + SessionSource(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static SessionSource fromValue(String value) { + for (SessionSource v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown SessionSource value: " + value); + } +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionSuspendParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSuspendParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionSuspendParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionSuspendParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksCancelParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksCancelParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksCancelParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksCancelParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksCancelResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksCancelResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksCancelResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksCancelResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksGetCurrentPromotableParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksGetCurrentPromotableParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksGetCurrentPromotableParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksGetCurrentPromotableParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksGetCurrentPromotableResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksGetCurrentPromotableResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksGetCurrentPromotableResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksGetCurrentPromotableResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksGetProgressParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksGetProgressParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksGetProgressParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksGetProgressParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksGetProgressResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksGetProgressResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksGetProgressResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksGetProgressResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksListParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksListParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksListParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksListParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksListResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksListResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksListResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksListResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksPromoteCurrentToBackgroundParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksPromoteCurrentToBackgroundParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksPromoteCurrentToBackgroundParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksPromoteCurrentToBackgroundParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksPromoteCurrentToBackgroundResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksPromoteCurrentToBackgroundResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksPromoteCurrentToBackgroundResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksPromoteCurrentToBackgroundResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksPromoteToBackgroundParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksPromoteToBackgroundParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksPromoteToBackgroundParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksPromoteToBackgroundParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksPromoteToBackgroundResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksPromoteToBackgroundResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksPromoteToBackgroundResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksPromoteToBackgroundResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksRefreshParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksRefreshParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksRefreshParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksRefreshParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksRemoveParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksRemoveParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksRemoveParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksRemoveParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksRemoveResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksRemoveResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksRemoveResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksRemoveResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksSendMessageParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksSendMessageParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksSendMessageParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksSendMessageParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksSendMessageResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksSendMessageResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksSendMessageResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksSendMessageResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksStartAgentParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksStartAgentParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksStartAgentParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksStartAgentParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksStartAgentResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksStartAgentResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksStartAgentResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksStartAgentResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksWaitForPendingParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksWaitForPendingParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionTasksWaitForPendingParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTasksWaitForPendingParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionTelemetryApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTelemetryApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionTelemetryApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTelemetryApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionTelemetryGetEngagementIdParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTelemetryGetEngagementIdParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionTelemetryGetEngagementIdParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTelemetryGetEngagementIdParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionTelemetryGetEngagementIdResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTelemetryGetEngagementIdResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionTelemetryGetEngagementIdResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTelemetryGetEngagementIdResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionTelemetrySetFeatureOverridesParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTelemetrySetFeatureOverridesParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionTelemetrySetFeatureOverridesParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionTelemetrySetFeatureOverridesParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionToolsApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionToolsApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionToolsGetCurrentMetadataParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsGetCurrentMetadataParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionToolsGetCurrentMetadataParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsGetCurrentMetadataParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionToolsGetCurrentMetadataResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsGetCurrentMetadataResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionToolsGetCurrentMetadataResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsGetCurrentMetadataResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionToolsHandlePendingToolCallParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsHandlePendingToolCallParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionToolsHandlePendingToolCallParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsHandlePendingToolCallParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionToolsHandlePendingToolCallResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsHandlePendingToolCallResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionToolsHandlePendingToolCallResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsHandlePendingToolCallResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionToolsInitializeAndValidateParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsInitializeAndValidateParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionToolsInitializeAndValidateParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsInitializeAndValidateParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionToolsUpdateSubagentSettingsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsUpdateSubagentSettingsParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionToolsUpdateSubagentSettingsParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionToolsUpdateSubagentSettingsParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionUiApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionUiApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionUiElicitationParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiElicitationParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionUiElicitationParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiElicitationParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionUiElicitationResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiElicitationResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionUiElicitationResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiElicitationResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionUiEphemeralQueryParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiEphemeralQueryParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionUiEphemeralQueryParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiEphemeralQueryParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionUiEphemeralQueryResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiEphemeralQueryResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionUiEphemeralQueryResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiEphemeralQueryResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingAutoModeSwitchParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingAutoModeSwitchParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingAutoModeSwitchParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingAutoModeSwitchParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingAutoModeSwitchResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingAutoModeSwitchResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingAutoModeSwitchResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingAutoModeSwitchResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingElicitationParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingElicitationParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingElicitationParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingElicitationParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingElicitationResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingElicitationResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingElicitationResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingElicitationResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingExitPlanModeParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingExitPlanModeParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingExitPlanModeParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingExitPlanModeParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingExitPlanModeResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingExitPlanModeResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingExitPlanModeResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingExitPlanModeResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingSamplingParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingSamplingParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingSamplingParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingSamplingParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingSamplingResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingSamplingResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingSamplingResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingSamplingResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingSessionLimitsExhaustedParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingSessionLimitsExhaustedParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingSessionLimitsExhaustedParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingSessionLimitsExhaustedParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingSessionLimitsExhaustedResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingSessionLimitsExhaustedResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingSessionLimitsExhaustedResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingSessionLimitsExhaustedResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingUserInputParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingUserInputParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingUserInputParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingUserInputParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingUserInputResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingUserInputResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingUserInputResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiHandlePendingUserInputResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionUiRegisterDirectAutoModeSwitchHandlerParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiRegisterDirectAutoModeSwitchHandlerParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionUiRegisterDirectAutoModeSwitchHandlerParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiRegisterDirectAutoModeSwitchHandlerParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionUiRegisterDirectAutoModeSwitchHandlerResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiRegisterDirectAutoModeSwitchHandlerResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionUiRegisterDirectAutoModeSwitchHandlerResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiRegisterDirectAutoModeSwitchHandlerResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionUiUnregisterDirectAutoModeSwitchHandlerParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiUnregisterDirectAutoModeSwitchHandlerParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionUiUnregisterDirectAutoModeSwitchHandlerParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiUnregisterDirectAutoModeSwitchHandlerParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionUiUnregisterDirectAutoModeSwitchHandlerResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiUnregisterDirectAutoModeSwitchHandlerResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionUiUnregisterDirectAutoModeSwitchHandlerResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUiUnregisterDirectAutoModeSwitchHandlerResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionUsageApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUsageApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionUsageApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUsageApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionUsageGetMetricsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUsageGetMetricsParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionUsageGetMetricsParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUsageGetMetricsParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionUsageGetMetricsResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUsageGetMetricsResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionUsageGetMetricsResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionUsageGetMetricsResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionVisibilityApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionVisibilityApi.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionVisibilityApi.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionVisibilityApi.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionVisibilityGetParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionVisibilityGetParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionVisibilityGetParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionVisibilityGetParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionVisibilityGetResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionVisibilityGetResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionVisibilityGetResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionVisibilityGetResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionVisibilitySetParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionVisibilitySetParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionVisibilitySetParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionVisibilitySetParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionVisibilitySetResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionVisibilitySetResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionVisibilitySetResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionVisibilitySetResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionVisibilityStatus.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionVisibilityStatus.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionVisibilityStatus.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionVisibilityStatus.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkingDirectoryContext.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkingDirectoryContext.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkingDirectoryContext.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkingDirectoryContext.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkingDirectoryContextHostType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkingDirectoryContextHostType.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkingDirectoryContextHostType.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkingDirectoryContextHostType.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesAddSummaryParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesAddSummaryParams.java new file mode 100644 index 000000000..2a1c247bf --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesAddSummaryParams.java @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Compaction summary checkpoint to persist. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionWorkspacesAddSummaryParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Summary title shown in checkpoint listings. */ + @JsonProperty("title") String title, + /** Markdown summary content to persist. */ + @JsonProperty("content") String content +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesAddSummaryResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesAddSummaryResult.java new file mode 100644 index 000000000..a50a0b5f5 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesAddSummaryResult.java @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.util.Map; +import javax.annotation.processing.Generated; + +/** + * Persisted summary metadata and refreshed workspace metadata. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionWorkspacesAddSummaryResult( + @JsonProperty("summary") Map summary, + @JsonProperty("workspace") Map workspace +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesApi.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesApi.java new file mode 100644 index 000000000..aaacb046f --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesApi.java @@ -0,0 +1,259 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.github.copilot.CopilotExperimental; +import java.util.concurrent.CompletableFuture; +import javax.annotation.processing.Generated; + +/** + * API methods for the {@code workspaces} namespace. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SessionWorkspacesApi { + + private static final com.fasterxml.jackson.databind.ObjectMapper MAPPER = RpcMapper.INSTANCE; + + private final RpcCaller caller; + private final String sessionId; + + /** @param caller the RPC transport function */ + SessionWorkspacesApi(RpcCaller caller, String sessionId) { + this.caller = caller; + this.sessionId = sessionId; + } + + /** + * Identifies the target session. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture getWorkspace() { + return caller.invoke("session.workspaces.getWorkspace", java.util.Map.of("sessionId", this.sessionId), SessionWorkspacesGetWorkspaceResult.class); + } + + /** + * Workspace metadata fields to update. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture updateMetadata(SessionWorkspacesUpdateMetadataParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.workspaces.updateMetadata", _p, SessionWorkspacesUpdateMetadataResult.class); + } + + /** + * Optional session context used when creating a local workspace. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture ensure(SessionWorkspacesEnsureParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.workspaces.ensure", _p, SessionWorkspacesEnsureResult.class); + } + + /** + * Identifies the target session. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture listFiles() { + return caller.invoke("session.workspaces.listFiles", java.util.Map.of("sessionId", this.sessionId), SessionWorkspacesListFilesResult.class); + } + + /** + * Relative path of the workspace file to read. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture readFile(SessionWorkspacesReadFileParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.workspaces.readFile", _p, SessionWorkspacesReadFileResult.class); + } + + /** + * Relative path and UTF-8 content for the workspace file to create or overwrite. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture createFile(SessionWorkspacesCreateFileParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.workspaces.createFile", _p, Void.class); + } + + /** + * Identifies the target session. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture listCheckpoints() { + return caller.invoke("session.workspaces.listCheckpoints", java.util.Map.of("sessionId", this.sessionId), SessionWorkspacesListCheckpointsResult.class); + } + + /** + * Checkpoint number to read. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture readCheckpoint(SessionWorkspacesReadCheckpointParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.workspaces.readCheckpoint", _p, SessionWorkspacesReadCheckpointResult.class); + } + + /** + * Compaction summary checkpoint to persist. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture addSummary(SessionWorkspacesAddSummaryParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.workspaces.addSummary", _p, SessionWorkspacesAddSummaryResult.class); + } + + /** + * Rollback point for local workspace summaries. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture truncateSummaries(SessionWorkspacesTruncateSummariesParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.workspaces.truncateSummaries", _p, SessionWorkspacesTruncateSummariesResult.class); + } + + /** + * Identifies the target session. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture readAutopilotObjective() { + return caller.invoke("session.workspaces.readAutopilotObjective", java.util.Map.of("sessionId", this.sessionId), SessionWorkspacesReadAutopilotObjectiveResult.class); + } + + /** + * Autopilot objective file content to persist. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture writeAutopilotObjective(SessionWorkspacesWriteAutopilotObjectiveParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.workspaces.writeAutopilotObjective", _p, SessionWorkspacesWriteAutopilotObjectiveResult.class); + } + + /** + * Identifies the target session. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture deleteAutopilotObjective() { + return caller.invoke("session.workspaces.deleteAutopilotObjective", java.util.Map.of("sessionId", this.sessionId), SessionWorkspacesDeleteAutopilotObjectiveResult.class); + } + + /** + * Identifies the target session. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture autopilotObjectiveExists() { + return caller.invoke("session.workspaces.autopilotObjectiveExists", java.util.Map.of("sessionId", this.sessionId), SessionWorkspacesAutopilotObjectiveExistsResult.class); + } + + /** + * Pasted content to save as a UTF-8 file in the session workspace. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture saveLargePaste(SessionWorkspacesSaveLargePasteParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.workspaces.saveLargePaste", _p, SessionWorkspacesSaveLargePasteResult.class); + } + + /** + * Parameters for computing a workspace diff. + *

+ * Note: the {@code sessionId} field in the params record is overridden + * by the session-scoped wrapper; any value provided is ignored. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ + @CopilotExperimental + public CompletableFuture diff(SessionWorkspacesDiffParams params) { + com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); + _p.put("sessionId", this.sessionId); + return caller.invoke("session.workspaces.diff", _p, SessionWorkspacesDiffResult.class); + } + +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesAutopilotObjectiveExistsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesAutopilotObjectiveExistsParams.java new file mode 100644 index 000000000..fc4b25c9d --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesAutopilotObjectiveExistsParams.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Identifies the target session. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionWorkspacesAutopilotObjectiveExistsParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesAutopilotObjectiveExistsResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesAutopilotObjectiveExistsResult.java new file mode 100644 index 000000000..8fe0a849d --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesAutopilotObjectiveExistsResult.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Whether the autopilot objective file exists. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionWorkspacesAutopilotObjectiveExistsResult( + /** True when the objective file exists. */ + @JsonProperty("exists") Boolean exists +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesCreateFileParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesCreateFileParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesCreateFileParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesCreateFileParams.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesDeleteAutopilotObjectiveParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesDeleteAutopilotObjectiveParams.java new file mode 100644 index 000000000..81f59d7a0 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesDeleteAutopilotObjectiveParams.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Identifies the target session. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionWorkspacesDeleteAutopilotObjectiveParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryTruncateResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesDeleteAutopilotObjectiveResult.java similarity index 81% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryTruncateResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesDeleteAutopilotObjectiveResult.java index 5b5043b60..3fa3f35f2 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryTruncateResult.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesDeleteAutopilotObjectiveResult.java @@ -14,7 +14,7 @@ import javax.annotation.processing.Generated; /** - * Number of events that were removed by the truncation. + * Result of deleting the autopilot objective file. * * @apiNote This method is experimental and may change in a future version. * @since 1.0.0 @@ -23,8 +23,8 @@ @javax.annotation.processing.Generated("copilot-sdk-codegen") @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) -public record SessionHistoryTruncateResult( - /** Number of events that were removed */ - @JsonProperty("eventsRemoved") Long eventsRemoved +public record SessionWorkspacesDeleteAutopilotObjectiveResult( + /** True when a file was deleted. */ + @JsonProperty("deleted") Boolean deleted ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesDiffParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesDiffParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesDiffParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesDiffParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesDiffResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesDiffResult.java similarity index 64% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesDiffResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesDiffResult.java index a3c8ce96e..21beab3ea 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesDiffResult.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesDiffResult.java @@ -33,7 +33,9 @@ public record SessionWorkspacesDiffResult( @JsonProperty("changes") List changes, /** Default branch used for a branch diff, when branch mode was requested. */ @JsonProperty("baseBranch") String baseBranch, - /** Whether a requested branch diff fell back to unstaged changes because branch diff failed. */ - @JsonProperty("isFallback") Boolean isFallback + /** Whether the requested diff fell back to unstaged changes, either because branch diff failed or session diff was unavailable. */ + @JsonProperty("isFallback") Boolean isFallback, + /** Why the session diff could not be produced, when applicable. Set only when `session` mode was requested and `isFallback` is true, so a client can tell the permanent `file-change-tracking-disabled` apart from the transient `session-busy`, which the same request answers once the session settles. Never set for `unstaged` or `branch` mode, and never `unsupported-remote-session`: a remote session's captures live on its own host, so a `session`-mode diff is rejected for one rather than answered with a controller-side fallback. */ + @JsonProperty("unavailableReason") HistoryRewindUnavailableReason unavailableReason ) { } diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesEnsureParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesEnsureParams.java new file mode 100644 index 000000000..aaa71621f --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesEnsureParams.java @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Optional session context used when creating a local workspace. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionWorkspacesEnsureParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Opaque workspace context supplied by the session host. */ + @JsonProperty("context") Object context +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesEnsureResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesEnsureResult.java new file mode 100644 index 000000000..4a810fe12 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesEnsureResult.java @@ -0,0 +1,57 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.time.OffsetDateTime; +import javax.annotation.processing.Generated; + +/** + * Current workspace metadata for the session, including its absolute filesystem path when available. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionWorkspacesEnsureResult( + /** Current workspace metadata, or null if not available */ + @JsonProperty("workspace") SessionWorkspacesEnsureResultWorkspace workspace, + /** Absolute filesystem path to the workspace directory. Omitted when the session has no workspace (e.g. remote sessions). */ + @JsonProperty("path") String path +) { + + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record SessionWorkspacesEnsureResultWorkspace( + @JsonProperty("id") String id, + @JsonProperty("cwd") String cwd, + @JsonProperty("git_root") String gitRoot, + @JsonProperty("repository") String repository, + /** Allowed values for the `WorkspacesWorkspaceDetailsHostType` enumeration. */ + @JsonProperty("host_type") WorkspacesWorkspaceDetailsHostType hostType, + @JsonProperty("branch") String branch, + @JsonProperty("name") String name, + @JsonProperty("client_name") String clientName, + @JsonProperty("user_named") Boolean userNamed, + @JsonProperty("summary_count") Long summaryCount, + @JsonProperty("created_at") OffsetDateTime createdAt, + @JsonProperty("updated_at") OffsetDateTime updatedAt, + @JsonProperty("remote_steerable") Boolean remoteSteerable, + @JsonProperty("mc_task_id") String mcTaskId, + @JsonProperty("mc_session_id") String mcSessionId, + @JsonProperty("mc_last_event_id") String mcLastEventId, + @JsonProperty("chronicle_sync_dismissed") Boolean chronicleSyncDismissed + ) { + } +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesGetWorkspaceParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesGetWorkspaceParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesGetWorkspaceParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesGetWorkspaceParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesGetWorkspaceResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesGetWorkspaceResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesGetWorkspaceResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesGetWorkspaceResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesListCheckpointsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesListCheckpointsParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesListCheckpointsParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesListCheckpointsParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesListCheckpointsResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesListCheckpointsResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesListCheckpointsResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesListCheckpointsResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesListFilesParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesListFilesParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesListFilesParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesListFilesParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesListFilesResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesListFilesResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesListFilesResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesListFilesResult.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadAutopilotObjectiveParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadAutopilotObjectiveParams.java new file mode 100644 index 000000000..67d03a954 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadAutopilotObjectiveParams.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Identifies the target session. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionWorkspacesReadAutopilotObjectiveParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadAutopilotObjectiveResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadAutopilotObjectiveResult.java new file mode 100644 index 000000000..7b2e157b5 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadAutopilotObjectiveResult.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Autopilot objective file content, or null when missing. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionWorkspacesReadAutopilotObjectiveResult( + /** Autopilot objective file content, or null when missing. */ + @JsonProperty("content") String content +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadCheckpointParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadCheckpointParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadCheckpointParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadCheckpointParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadCheckpointResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadCheckpointResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadCheckpointResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadCheckpointResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadFileParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadFileParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadFileParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadFileParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadFileResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadFileResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadFileResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesReadFileResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesSaveLargePasteParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesSaveLargePasteParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesSaveLargePasteParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesSaveLargePasteParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesSaveLargePasteResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesSaveLargePasteResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesSaveLargePasteResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesSaveLargePasteResult.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesTruncateSummariesParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesTruncateSummariesParams.java new file mode 100644 index 000000000..43d392e48 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesTruncateSummariesParams.java @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Rollback point for local workspace summaries. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionWorkspacesTruncateSummariesParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Number of newest summaries to keep. */ + @JsonProperty("keepCount") Long keepCount +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesTruncateSummariesResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesTruncateSummariesResult.java new file mode 100644 index 000000000..caa44d0b7 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesTruncateSummariesResult.java @@ -0,0 +1,57 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.time.OffsetDateTime; +import javax.annotation.processing.Generated; + +/** + * Current workspace metadata for the session, including its absolute filesystem path when available. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionWorkspacesTruncateSummariesResult( + /** Current workspace metadata, or null if not available */ + @JsonProperty("workspace") SessionWorkspacesTruncateSummariesResultWorkspace workspace, + /** Absolute filesystem path to the workspace directory. Omitted when the session has no workspace (e.g. remote sessions). */ + @JsonProperty("path") String path +) { + + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record SessionWorkspacesTruncateSummariesResultWorkspace( + @JsonProperty("id") String id, + @JsonProperty("cwd") String cwd, + @JsonProperty("git_root") String gitRoot, + @JsonProperty("repository") String repository, + /** Allowed values for the `WorkspacesWorkspaceDetailsHostType` enumeration. */ + @JsonProperty("host_type") WorkspacesWorkspaceDetailsHostType hostType, + @JsonProperty("branch") String branch, + @JsonProperty("name") String name, + @JsonProperty("client_name") String clientName, + @JsonProperty("user_named") Boolean userNamed, + @JsonProperty("summary_count") Long summaryCount, + @JsonProperty("created_at") OffsetDateTime createdAt, + @JsonProperty("updated_at") OffsetDateTime updatedAt, + @JsonProperty("remote_steerable") Boolean remoteSteerable, + @JsonProperty("mc_task_id") String mcTaskId, + @JsonProperty("mc_session_id") String mcSessionId, + @JsonProperty("mc_last_event_id") String mcLastEventId, + @JsonProperty("chronicle_sync_dismissed") Boolean chronicleSyncDismissed + ) { + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesUpdateMetadataParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesUpdateMetadataParams.java new file mode 100644 index 000000000..af45fead5 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesUpdateMetadataParams.java @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Workspace metadata fields to update. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionWorkspacesUpdateMetadataParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Opaque workspace context supplied by the session host. */ + @JsonProperty("context") Object context, + /** Optional workspace display name override. */ + @JsonProperty("name") String name +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesUpdateMetadataResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesUpdateMetadataResult.java new file mode 100644 index 000000000..84ec13661 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesUpdateMetadataResult.java @@ -0,0 +1,57 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.time.OffsetDateTime; +import javax.annotation.processing.Generated; + +/** + * Current workspace metadata for the session, including its absolute filesystem path when available. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionWorkspacesUpdateMetadataResult( + /** Current workspace metadata, or null if not available */ + @JsonProperty("workspace") SessionWorkspacesUpdateMetadataResultWorkspace workspace, + /** Absolute filesystem path to the workspace directory. Omitted when the session has no workspace (e.g. remote sessions). */ + @JsonProperty("path") String path +) { + + @JsonIgnoreProperties(ignoreUnknown = true) + @JsonInclude(JsonInclude.Include.NON_NULL) + public record SessionWorkspacesUpdateMetadataResultWorkspace( + @JsonProperty("id") String id, + @JsonProperty("cwd") String cwd, + @JsonProperty("git_root") String gitRoot, + @JsonProperty("repository") String repository, + /** Allowed values for the `WorkspacesWorkspaceDetailsHostType` enumeration. */ + @JsonProperty("host_type") WorkspacesWorkspaceDetailsHostType hostType, + @JsonProperty("branch") String branch, + @JsonProperty("name") String name, + @JsonProperty("client_name") String clientName, + @JsonProperty("user_named") Boolean userNamed, + @JsonProperty("summary_count") Long summaryCount, + @JsonProperty("created_at") OffsetDateTime createdAt, + @JsonProperty("updated_at") OffsetDateTime updatedAt, + @JsonProperty("remote_steerable") Boolean remoteSteerable, + @JsonProperty("mc_task_id") String mcTaskId, + @JsonProperty("mc_session_id") String mcSessionId, + @JsonProperty("mc_last_event_id") String mcLastEventId, + @JsonProperty("chronicle_sync_dismissed") Boolean chronicleSyncDismissed + ) { + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesWriteAutopilotObjectiveParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesWriteAutopilotObjectiveParams.java new file mode 100644 index 000000000..fb116ed30 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesWriteAutopilotObjectiveParams.java @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Autopilot objective file content to persist. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionWorkspacesWriteAutopilotObjectiveParams( + /** Target session identifier */ + @JsonProperty("sessionId") String sessionId, + /** Autopilot objective file content. */ + @JsonProperty("content") String content +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesWriteAutopilotObjectiveResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesWriteAutopilotObjectiveResult.java new file mode 100644 index 000000000..9b69713e9 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionWorkspacesWriteAutopilotObjectiveResult.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Result of writing the autopilot objective file. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionWorkspacesWriteAutopilotObjectiveResult( + /** Filesystem operation performed. */ + @JsonProperty("operation") String operation +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsBulkDeleteParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsBulkDeleteParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsBulkDeleteParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsBulkDeleteParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsBulkDeleteResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsBulkDeleteResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsBulkDeleteResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsBulkDeleteResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsCheckInUseParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsCheckInUseParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsCheckInUseParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsCheckInUseParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsCheckInUseResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsCheckInUseResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsCheckInUseResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsCheckInUseResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsCloseParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsCloseParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsCloseParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsCloseParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsConfigureSessionExtensionsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsConfigureSessionExtensionsParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsConfigureSessionExtensionsParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsConfigureSessionExtensionsParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsConnectParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsConnectParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsConnectParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsConnectParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsConnectResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsConnectResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsConnectResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsConnectResult.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsDeleteParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsDeleteParams.java new file mode 100644 index 000000000..788811e34 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsDeleteParams.java @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Session ID to delete from disk. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionsDeleteParams( + /** Session ID to delete */ + @JsonProperty("sessionId") String sessionId, + /** Internal resolved session directory path to delete */ + @JsonProperty("sessionPath") String sessionPath +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsEnrichMetadataParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsEnrichMetadataParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsEnrichMetadataParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsEnrichMetadataParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsEnrichMetadataResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsEnrichMetadataResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsEnrichMetadataResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsEnrichMetadataResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsFindByPrefixParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsFindByPrefixParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsFindByPrefixParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsFindByPrefixParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsFindByPrefixResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsFindByPrefixResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsFindByPrefixResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsFindByPrefixResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsFindByTaskIdParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsFindByTaskIdParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsFindByTaskIdParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsFindByTaskIdParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsFindByTaskIdResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsFindByTaskIdResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsFindByTaskIdResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsFindByTaskIdResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsForkParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsForkParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsForkParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsForkParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsForkResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsForkResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsForkResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsForkResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsGetBoardEntryCountParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsGetBoardEntryCountParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsGetBoardEntryCountParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsGetBoardEntryCountParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsGetBoardEntryCountResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsGetBoardEntryCountResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsGetBoardEntryCountResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsGetBoardEntryCountResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsGetEventFilePathParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsGetEventFilePathParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsGetEventFilePathParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsGetEventFilePathParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsGetEventFilePathResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsGetEventFilePathResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsGetEventFilePathResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsGetEventFilePathResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsGetLastForContextParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsGetLastForContextParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsGetLastForContextParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsGetLastForContextParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsGetLastForContextResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsGetLastForContextResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsGetLastForContextResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsGetLastForContextResult.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsGetMetadataParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsGetMetadataParams.java new file mode 100644 index 000000000..cfa2e6326 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsGetMetadataParams.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Session ID whose persisted metadata should be read. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionsGetMetadataParams( + /** Session ID to inspect */ + @JsonProperty("sessionId") String sessionId +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsGetMetadataResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsGetMetadataResult.java new file mode 100644 index 000000000..6546b00fe --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsGetMetadataResult.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Persisted local session metadata when the session exists. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionsGetMetadataResult( + /** Local session metadata, omitted when the session does not exist. */ + @JsonProperty("session") LocalSessionMetadataValue session +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsGetPersistedRemoteSteerableParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsGetPersistedRemoteSteerableParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsGetPersistedRemoteSteerableParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsGetPersistedRemoteSteerableParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsGetPersistedRemoteSteerableResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsGetPersistedRemoteSteerableResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsGetPersistedRemoteSteerableResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsGetPersistedRemoteSteerableResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsGetRemoteControlStatusResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsGetRemoteControlStatusResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsGetRemoteControlStatusResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsGetRemoteControlStatusResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsGetSizesResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsGetSizesResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsGetSizesResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsGetSizesResult.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsListNonEmptySessionIdsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsListNonEmptySessionIdsParams.java new file mode 100644 index 000000000..4e453121b --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsListNonEmptySessionIdsParams.java @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Limit for non-empty local session IDs. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionsListNonEmptySessionIdsParams( + /** Maximum number of session IDs to return. */ + @JsonProperty("limit") Long limit +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsListNonEmptySessionIdsResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsListNonEmptySessionIdsResult.java new file mode 100644 index 000000000..51cc266e6 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsListNonEmptySessionIdsResult.java @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * Recent local session IDs that contain user-visible history. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionsListNonEmptySessionIdsResult( + /** Session IDs ordered newest-first. */ + @JsonProperty("sessionIds") List sessionIds +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsListParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsListParams.java new file mode 100644 index 000000000..61e01b1c7 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsListParams.java @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Optional source filter, metadata-load limit, and context filter applied to the returned sessions. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionsListParams( + /** Which session sources to include. Defaults to `local` for backward compatibility. */ + @JsonProperty("source") SessionSource source, + /** When provided, only the first N local sessions (sorted by modification time, newest first) load full metadata; remaining sessions return basic info only. Use 0 to return only basic info for every local session. Has no effect on remote entries (which always carry their full shape). */ + @JsonProperty("metadataLimit") Long metadataLimit, + /** Optional filter applied to the returned sessions */ + @JsonProperty("filter") SessionListFilter filter, + /** When true, include detached maintenance sessions. Defaults to false for user-facing session lists. */ + @JsonProperty("includeDetached") Boolean includeDetached, + /** Only meaningful when `source` includes remote. When true, propagates errors from the remote service instead of silently returning an empty remote list. Defaults to false. */ + @JsonProperty("throwOnError") Boolean throwOnError +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsListResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsListResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsListResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsListResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsLoadDeferredRepoHooksParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsLoadDeferredRepoHooksParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsLoadDeferredRepoHooksParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsLoadDeferredRepoHooksParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsLoadDeferredRepoHooksResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsLoadDeferredRepoHooksResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsLoadDeferredRepoHooksResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsLoadDeferredRepoHooksResult.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenAttach.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenAttach.java new file mode 100644 index 000000000..1554425a7 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenAttach.java @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Parameters for attaching to an already-active session by ID. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SessionsOpenAttach extends SessionsOpenParams { + + @JsonProperty("kind") + private final String kind = "attach"; + + @Override + public String getKind() { return kind; } + + /** Session ID to attach to. */ + @JsonProperty("sessionId") + private String sessionId; + + public String getSessionId() { return sessionId; } + public void setSessionId(String sessionId) { this.sessionId = sessionId; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenCloud.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenCloud.java new file mode 100644 index 000000000..8e4a74bd8 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenCloud.java @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Parameters for creating a new cloud session. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SessionsOpenCloud extends SessionsOpenParams { + + @JsonProperty("kind") + private final String kind = "cloud"; + + @Override + public String getKind() { return kind; } + + /** Repository for the cloud session. */ + @JsonProperty("repository") + private RemoteSessionRepository repository; + + /** Optional owner (user or organization login) to associate with the cloud session when no repository is provided. Ignored when `repository` is set (the repo's owner takes precedence). */ + @JsonProperty("owner") + private String owner; + + /** Session options for cloud session creation. */ + @JsonProperty("options") + private SessionOpenOptions options; + + /** In-process callback invoked when the cloud task is created (before connection). Marked internal because a function reference cannot cross the JSON-RPC boundary. Disappears in the SDK migration: the field is purely cosmetic (it flips a single CLI phase label from 'creating' to 'connecting') and the wire-clean version just drops the intermediate phase. */ + @JsonProperty("onTaskCreated") + private Object onTaskCreated; + + public RemoteSessionRepository getRepository() { return repository; } + public void setRepository(RemoteSessionRepository repository) { this.repository = repository; } + + public String getOwner() { return owner; } + public void setOwner(String owner) { this.owner = owner; } + + public SessionOpenOptions getOptions() { return options; } + public void setOptions(SessionOpenOptions options) { this.options = options; } + + public Object getOnTaskCreated() { return onTaskCreated; } + public void setOnTaskCreated(Object onTaskCreated) { this.onTaskCreated = onTaskCreated; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenCreate.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenCreate.java new file mode 100644 index 000000000..0394cffa6 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenCreate.java @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Parameters for creating a new local session. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SessionsOpenCreate extends SessionsOpenParams { + + @JsonProperty("kind") + private final String kind = "create"; + + @Override + public String getKind() { return kind; } + + /** Session construction options. */ + @JsonProperty("options") + private SessionOpenOptions options; + + /** Whether to emit session.start during creation. Defaults to true. */ + @JsonProperty("emitStart") + private Boolean emitStart; + + public SessionOpenOptions getOptions() { return options; } + public void setOptions(SessionOpenOptions options) { this.options = options; } + + public Boolean getEmitStart() { return emitStart; } + public void setEmitStart(Boolean emitStart) { this.emitStart = emitStart; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenHandoff.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenHandoff.java new file mode 100644 index 000000000..bb67c4338 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenHandoff.java @@ -0,0 +1,65 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Parameters for fetching a remote session and handing it off to a new local session. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SessionsOpenHandoff extends SessionsOpenParams { + + @JsonProperty("kind") + private final String kind = "handoff"; + + @Override + public String getKind() { return kind; } + + /** Remote session metadata for the session to hand off (typically obtained from `sessions.list` with `source: "remote"`). */ + @JsonProperty("metadata") + private RemoteSessionMetadataValue metadata; + + /** Session construction options for the new local session. */ + @JsonProperty("options") + private SessionOpenOptions options; + + /** Task type determines the handoff strategy (CCA fetches events; CLI prepares a transient session). */ + @JsonProperty("taskType") + private SessionsOpenHandoffTaskType taskType; + + /** In-process progress callback `(update) => void` invoked for each handoff step. Marked internal because a function reference cannot cross the JSON-RPC boundary. The host-side `handoffSession` is already declared as `AsyncGenerator`; the schema layer flattens it because it does not yet support streaming methods. The wire-clean replacement is to expose the AsyncGenerator directly (or use vscode-jsonrpc `$/progress` notifications) once the schema/transport layer supports it. */ + @JsonProperty("onProgress") + private Object onProgress; + + /** In-process confirmation callback `(request) => boolean | Promise` invoked when the handoff needs the caller to confirm a non-fatal blocker (e.g. a repository mismatch between the current working directory and the remote session). Returning `true` proceeds with the handoff; returning `false` (or omitting the callback) aborts it. Marked internal because a function reference cannot cross the JSON-RPC boundary, for the same reasons as `onProgress`. */ + @JsonProperty("onConfirm") + private Object onConfirm; + + public RemoteSessionMetadataValue getMetadata() { return metadata; } + public void setMetadata(RemoteSessionMetadataValue metadata) { this.metadata = metadata; } + + public SessionOpenOptions getOptions() { return options; } + public void setOptions(SessionOpenOptions options) { this.options = options; } + + public SessionsOpenHandoffTaskType getTaskType() { return taskType; } + public void setTaskType(SessionsOpenHandoffTaskType taskType) { this.taskType = taskType; } + + public Object getOnProgress() { return onProgress; } + public void setOnProgress(Object onProgress) { this.onProgress = onProgress; } + + public Object getOnConfirm() { return onConfirm; } + public void setOnConfirm(Object onConfirm) { this.onConfirm = onConfirm; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenHandoffTaskType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenHandoffTaskType.java new file mode 100644 index 000000000..39d7eff42 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenHandoffTaskType.java @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * Task type determines the handoff strategy (CCA fetches events; CLI prepares a transient session). + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum SessionsOpenHandoffTaskType { + /** The {@code cca} variant. */ + CCA("cca"), + /** The {@code cli} variant. */ + CLI("cli"); + + private final String value; + SessionsOpenHandoffTaskType(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static SessionsOpenHandoffTaskType fromValue(String value) { + for (SessionsOpenHandoffTaskType v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown SessionsOpenHandoffTaskType value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenParams.java new file mode 100644 index 000000000..dd9795024 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenParams.java @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import javax.annotation.processing.Generated; + +/** + * Open a session by creating, resuming, attaching, connecting to a remote, or handing off. + * + * @since 1.0.0 + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "kind", visible = true) +@JsonSubTypes({ + @JsonSubTypes.Type(value = SessionsOpenCreate.class, name = "create"), + @JsonSubTypes.Type(value = SessionsOpenResume.class, name = "resume"), + @JsonSubTypes.Type(value = SessionsOpenResumeLast.class, name = "resumeLast"), + @JsonSubTypes.Type(value = SessionsOpenAttach.class, name = "attach"), + @JsonSubTypes.Type(value = SessionsOpenRemote.class, name = "remote"), + @JsonSubTypes.Type(value = SessionsOpenCloud.class, name = "cloud"), + @JsonSubTypes.Type(value = SessionsOpenHandoff.class, name = "handoff") +}) +@JsonIgnoreProperties(ignoreUnknown = true) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public abstract class SessionsOpenParams { + + /** + * Returns the discriminator value for this variant. + * + * @return the kind discriminator + */ + public abstract String getKind(); +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenProgress.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenProgress.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenProgress.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenProgress.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenProgressStatus.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenProgressStatus.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenProgressStatus.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenProgressStatus.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenProgressStep.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenProgressStep.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenProgressStep.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenProgressStep.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenRemote.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenRemote.java new file mode 100644 index 000000000..a51660d80 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenRemote.java @@ -0,0 +1,51 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Parameters for connecting to a live remote session. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SessionsOpenRemote extends SessionsOpenParams { + + @JsonProperty("kind") + private final String kind = "remote"; + + @Override + public String getKind() { return kind; } + + /** Remote session identifier to connect to. */ + @JsonProperty("remoteSessionId") + private String remoteSessionId; + + /** Repository context for the remote session. */ + @JsonProperty("repository") + private RemoteSessionRepository repository; + + /** Session options for the connection. */ + @JsonProperty("options") + private SessionOpenOptions options; + + public String getRemoteSessionId() { return remoteSessionId; } + public void setRemoteSessionId(String remoteSessionId) { this.remoteSessionId = remoteSessionId; } + + public RemoteSessionRepository getRepository() { return repository; } + public void setRepository(RemoteSessionRepository repository) { this.repository = repository; } + + public SessionOpenOptions getOptions() { return options; } + public void setOptions(SessionOpenOptions options) { this.options = options; } +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenResult.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenResume.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenResume.java new file mode 100644 index 000000000..664e5a005 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenResume.java @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Parameters for resuming a specific local session. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SessionsOpenResume extends SessionsOpenParams { + + @JsonProperty("kind") + private final String kind = "resume"; + + @Override + public String getKind() { return kind; } + + /** Session ID or unique prefix to resume. */ + @JsonProperty("sessionId") + private String sessionId; + + /** Session resume options. */ + @JsonProperty("options") + private SessionOpenOptions options; + + /** Whether to emit session.resume after loading. Defaults to true. */ + @JsonProperty("resume") + private Boolean resume; + + /** Suppress workspace.yaml metadata writeback when resuming from an incidental cwd. */ + @JsonProperty("suppressResumeWorkspaceMetadataWriteback") + private Boolean suppressResumeWorkspaceMetadataWriteback; + + public String getSessionId() { return sessionId; } + public void setSessionId(String sessionId) { this.sessionId = sessionId; } + + public SessionOpenOptions getOptions() { return options; } + public void setOptions(SessionOpenOptions options) { this.options = options; } + + public Boolean getResume() { return resume; } + public void setResume(Boolean resume) { this.resume = resume; } + + public Boolean getSuppressResumeWorkspaceMetadataWriteback() { return suppressResumeWorkspaceMetadataWriteback; } + public void setSuppressResumeWorkspaceMetadataWriteback(Boolean suppressResumeWorkspaceMetadataWriteback) { this.suppressResumeWorkspaceMetadataWriteback = suppressResumeWorkspaceMetadataWriteback; } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenResumeLast.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenResumeLast.java new file mode 100644 index 000000000..a93afe774 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenResumeLast.java @@ -0,0 +1,51 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * Parameters for resuming the most relevant local session. + * + * @since 1.0.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public final class SessionsOpenResumeLast extends SessionsOpenParams { + + @JsonProperty("kind") + private final String kind = "resumeLast"; + + @Override + public String getKind() { return kind; } + + /** Working-directory context used to choose the most relevant session. */ + @JsonProperty("context") + private SessionContext context; + + /** Session resume options. */ + @JsonProperty("options") + private SessionOpenOptions options; + + /** Suppress workspace.yaml metadata writeback when resuming from an incidental cwd. */ + @JsonProperty("suppressResumeWorkspaceMetadataWriteback") + private Boolean suppressResumeWorkspaceMetadataWriteback; + + public SessionContext getContext() { return context; } + public void setContext(SessionContext context) { this.context = context; } + + public SessionOpenOptions getOptions() { return options; } + public void setOptions(SessionOpenOptions options) { this.options = options; } + + public Boolean getSuppressResumeWorkspaceMetadataWriteback() { return suppressResumeWorkspaceMetadataWriteback; } + public void setSuppressResumeWorkspaceMetadataWriteback(Boolean suppressResumeWorkspaceMetadataWriteback) { this.suppressResumeWorkspaceMetadataWriteback = suppressResumeWorkspaceMetadataWriteback; } +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenStatus.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenStatus.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenStatus.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsOpenStatus.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsPruneOldParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsPruneOldParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsPruneOldParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsPruneOldParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsPruneOldResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsPruneOldResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsPruneOldResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsPruneOldResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsRegisterExtensionToolsOnSessionOptions.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsRegisterExtensionToolsOnSessionOptions.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsRegisterExtensionToolsOnSessionOptions.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsRegisterExtensionToolsOnSessionOptions.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsRegisterExtensionToolsOnSessionParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsRegisterExtensionToolsOnSessionParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsRegisterExtensionToolsOnSessionParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsRegisterExtensionToolsOnSessionParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsRegisterExtensionToolsOnSessionResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsRegisterExtensionToolsOnSessionResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsRegisterExtensionToolsOnSessionResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsRegisterExtensionToolsOnSessionResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsReleaseLockParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsReleaseLockParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsReleaseLockParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsReleaseLockParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsReloadPluginHooksParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsReloadPluginHooksParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsReloadPluginHooksParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsReloadPluginHooksParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsSaveParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsSaveParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsSaveParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsSaveParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsSetAdditionalPluginsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsSetAdditionalPluginsParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsSetAdditionalPluginsParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsSetAdditionalPluginsParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsSetRemoteControlSteeringParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsSetRemoteControlSteeringParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsSetRemoteControlSteeringParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsSetRemoteControlSteeringParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsSetRemoteControlSteeringResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsSetRemoteControlSteeringResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsSetRemoteControlSteeringResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsSetRemoteControlSteeringResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsStartRemoteControlParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsStartRemoteControlParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsStartRemoteControlParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsStartRemoteControlParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsStartRemoteControlResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsStartRemoteControlResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsStartRemoteControlResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsStartRemoteControlResult.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsStopRemoteControlParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsStopRemoteControlParams.java new file mode 100644 index 000000000..3cd2065d6 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsStopRemoteControlParams.java @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.CopilotExperimental; +import javax.annotation.processing.Generated; + +/** + * Request parameters for the {@code sessions.stopRemoteControl} RPC method. + * + * @apiNote This method is experimental and may change in a future version. + * @since 1.0.0 + */ +@CopilotExperimental +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record SessionsStopRemoteControlParams( + /** When provided, the stop is rejected unless the singleton currently points at this session id (compare-and-swap semantics). */ + @JsonProperty("expectedSessionId") String expectedSessionId, + /** When true, the singleton is unconditionally torn down regardless of `expectedSessionId`. Use during shutdown or explicit `/remote off`. */ + @JsonProperty("force") Boolean force +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsStopRemoteControlResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsStopRemoteControlResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsStopRemoteControlResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsStopRemoteControlResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsTransferRemoteControlParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsTransferRemoteControlParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsTransferRemoteControlParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsTransferRemoteControlParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionsTransferRemoteControlResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsTransferRemoteControlResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SessionsTransferRemoteControlResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SessionsTransferRemoteControlResult.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ShellInitProfile.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ShellInitProfile.java new file mode 100644 index 000000000..7d27a55b5 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ShellInitProfile.java @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * Controls automatic non-interactive profile loading where supported. Explicit initScripts are unaffected. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum ShellInitProfile { + /** The {@code none} variant. */ + NONE("none"), + /** The {@code non-interactive} variant. */ + NON_INTERACTIVE("non-interactive"); + + private final String value; + ShellInitProfile(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static ShellInitProfile fromValue(String value) { + for (ShellInitProfile v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown ShellInitProfile value: " + value); + } +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ShellInitScript.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ShellInitScript.java new file mode 100644 index 000000000..31789619e --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ShellInitScript.java @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import javax.annotation.processing.Generated; + +/** + * A host-provided script sourced before each built-in shell command when its shell target matches the active shell. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record ShellInitScript( + /** Path to the script to source. */ + @JsonProperty("path") String path, + /** Built-in shell that may source this script. */ + @JsonProperty("shell") ShellInitScriptShell shell +) { +} diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ShellInitScriptShell.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ShellInitScriptShell.java new file mode 100644 index 000000000..63d7ba7dc --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ShellInitScriptShell.java @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import javax.annotation.processing.Generated; + +/** + * Supported built-in shells for initialization scripts. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +public enum ShellInitScriptShell { + /** The {@code bash} variant. */ + BASH("bash"), + /** The {@code powershell} variant. */ + POWERSHELL("powershell"); + + private final String value; + ShellInitScriptShell(String value) { this.value = value; } + @com.fasterxml.jackson.annotation.JsonValue + public String getValue() { return value; } + @com.fasterxml.jackson.annotation.JsonCreator + public static ShellInitScriptShell fromValue(String value) { + for (ShellInitScriptShell v : values()) { + if (v.value.equals(value)) return v; + } + throw new IllegalArgumentException("Unknown ShellInitScriptShell value: " + value); + } +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ShellKillSignal.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ShellKillSignal.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ShellKillSignal.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ShellKillSignal.java diff --git a/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ShellOptions.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ShellOptions.java new file mode 100644 index 000000000..596de0ef9 --- /dev/null +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ShellOptions.java @@ -0,0 +1,42 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +// AUTO-GENERATED FILE - DO NOT EDIT +// Generated from: api.schema.json + +package com.github.copilot.generated.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import javax.annotation.processing.Generated; + +/** + * Per-session settings for built-in shell tools. + * + * @since 1.0.0 + */ +@javax.annotation.processing.Generated("copilot-sdk-codegen") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public record ShellOptions( + /** Controls automatic non-interactive profile loading where supported. Explicit initScripts are unaffected. */ + @JsonProperty("initProfile") ShellInitProfile initProfile, + /** Ordered host-provided script paths sourced before each built-in shell command when the +entry's shell target matches the active shell. Use these for rc files, environment setup scripts, +or other custom scripts. A script that returns a nonzero status is reported, and later scripts +and the user command continue while the shell remains running. Because scripts are sourced into +the command shell, `exit`, `exec`, failures under `set -e`, or other shell-terminating behavior +can prevent continuation. Script standard output is preserved; Bash script stderr is discarded, +PowerShell exception messages are replaced, and runtime-generated failure notices omit +configured script paths. When sandboxing is enabled, each script must already be readable under +the active sandbox filesystem policy. Pass an empty array to clear the list. */ + @JsonProperty("initScripts") List initScripts, + /** Flags passed to the active built-in shell process on startup, replacing its default flags. +When omitted, the built-in Bash shell uses `--norc --noprofile`, +and the built-in PowerShell shell uses `-NoProfile -NoLogo`. */ + @JsonProperty("processFlags") List processFlags +) { +} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ShutdownType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ShutdownType.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ShutdownType.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ShutdownType.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/Skill.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/Skill.java similarity index 92% rename from java/src/generated/java/com/github/copilot/generated/rpc/Skill.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/Skill.java index 4ad411687..88e7eb116 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/Skill.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/Skill.java @@ -23,6 +23,8 @@ public record Skill( /** Unique identifier for the skill */ @JsonProperty("name") String name, + /** Canonical slash command name used to invoke the skill, without the leading '/' */ + @JsonProperty("commandName") String commandName, /** Description of what the skill does */ @JsonProperty("description") String description, /** Source location type (e.g., project, personal-copilot, plugin, builtin) */ diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SkillDiscoveryPath.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillDiscoveryPath.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SkillDiscoveryPath.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillDiscoveryPath.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SkillDiscoveryScope.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillDiscoveryScope.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SkillDiscoveryScope.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillDiscoveryScope.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SkillSource.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillSource.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SkillSource.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillSource.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SkillsConfigSetDisabledSkillsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillsConfigSetDisabledSkillsParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SkillsConfigSetDisabledSkillsParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillsConfigSetDisabledSkillsParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SkillsDiscoverParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillsDiscoverParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SkillsDiscoverParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillsDiscoverParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SkillsDiscoverResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillsDiscoverResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SkillsDiscoverResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillsDiscoverResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SkillsGetDiscoveryPathsParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillsGetDiscoveryPathsParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SkillsGetDiscoveryPathsParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillsGetDiscoveryPathsParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SkillsGetDiscoveryPathsResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillsGetDiscoveryPathsResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SkillsGetDiscoveryPathsResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillsGetDiscoveryPathsResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SkillsInvokedSkill.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillsInvokedSkill.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SkillsInvokedSkill.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SkillsInvokedSkill.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandAgentPromptResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandAgentPromptResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandAgentPromptResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandAgentPromptResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandCompletedResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandCompletedResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandCompletedResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandCompletedResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInfo.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInfo.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInfo.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInfo.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInput.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInput.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInput.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInput.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInputChoice.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInputChoice.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInputChoice.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInputChoice.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInputCompletion.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInputCompletion.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInputCompletion.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInputCompletion.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInvocationResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInvocationResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInvocationResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandInvocationResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandKind.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandKind.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandKind.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandKind.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandSelectSubcommandOption.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandSelectSubcommandOption.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandSelectSubcommandOption.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandSelectSubcommandOption.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandSelectSubcommandResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandSelectSubcommandResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandSelectSubcommandResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandSelectSubcommandResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandTextResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandTextResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SlashCommandTextResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SlashCommandTextResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SubagentSettingsEntry.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SubagentSettingsEntry.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SubagentSettingsEntry.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SubagentSettingsEntry.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SubagentSettingsEntryContextTier.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/SubagentSettingsEntryContextTier.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/SubagentSettingsEntryContextTier.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/SubagentSettingsEntryContextTier.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/Tool.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/Tool.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/Tool.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/Tool.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ToolsListParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ToolsListParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ToolsListParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ToolsListParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/ToolsListResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/ToolsListResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/ToolsListResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/ToolsListResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/UIAutoModeSwitchResponse.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UIAutoModeSwitchResponse.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/UIAutoModeSwitchResponse.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/UIAutoModeSwitchResponse.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/UIElicitationResponse.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UIElicitationResponse.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/UIElicitationResponse.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/UIElicitationResponse.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/UIElicitationResponseAction.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UIElicitationResponseAction.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/UIElicitationResponseAction.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/UIElicitationResponseAction.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/UIElicitationSchema.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UIElicitationSchema.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/UIElicitationSchema.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/UIElicitationSchema.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/UIExitPlanModeAction.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UIExitPlanModeAction.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/UIExitPlanModeAction.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/UIExitPlanModeAction.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/UIExitPlanModeResponse.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UIExitPlanModeResponse.java similarity index 76% rename from java/src/generated/java/com/github/copilot/generated/rpc/UIExitPlanModeResponse.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/UIExitPlanModeResponse.java index d74d1b504..b65b28fc1 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/UIExitPlanModeResponse.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UIExitPlanModeResponse.java @@ -28,6 +28,8 @@ public record UIExitPlanModeResponse( /** Whether subsequent edits should be auto-approved without confirmation. */ @JsonProperty("autoApproveEdits") Boolean autoApproveEdits, /** Feedback from the user when they declined the plan or requested changes. */ - @JsonProperty("feedback") String feedback + @JsonProperty("feedback") String feedback, + /** When true, the agent is instructed to end its turn without starting implementation so the client can restore the session model and auto-submit a fresh implementation turn on it. Set only when a distinct plan configuration (a different model, reasoning effort, or context tier) actually ran the planning turn. */ + @JsonProperty("deferImplementation") Boolean deferImplementation ) { } diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/UIHandlePendingSamplingResponse.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UIHandlePendingSamplingResponse.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/UIHandlePendingSamplingResponse.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/UIHandlePendingSamplingResponse.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/UISessionLimitsExhaustedResponse.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UISessionLimitsExhaustedResponse.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/UISessionLimitsExhaustedResponse.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/UISessionLimitsExhaustedResponse.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/UISessionLimitsExhaustedResponseAction.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UISessionLimitsExhaustedResponseAction.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/UISessionLimitsExhaustedResponseAction.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/UISessionLimitsExhaustedResponseAction.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/UIUserInputResponse.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UIUserInputResponse.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/UIUserInputResponse.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/UIUserInputResponse.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsCodeChanges.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsCodeChanges.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsCodeChanges.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsCodeChanges.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsModelMetric.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsModelMetric.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsModelMetric.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsModelMetric.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsModelMetricRequests.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsModelMetricRequests.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsModelMetricRequests.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsModelMetricRequests.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsModelMetricTokenDetail.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsModelMetricTokenDetail.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsModelMetricTokenDetail.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsModelMetricTokenDetail.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsModelMetricUsage.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsModelMetricUsage.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsModelMetricUsage.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsModelMetricUsage.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsTokenDetail.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsTokenDetail.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsTokenDetail.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/UsageMetricsTokenDetail.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/UserSettingMetadata.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingMetadata.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/UserSettingMetadata.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingMetadata.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/UserSettingsGetResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsGetResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/UserSettingsGetResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsGetResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/UserSettingsSetParams.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsSetParams.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/UserSettingsSetParams.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsSetParams.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/UserSettingsSetResult.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsSetResult.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/UserSettingsSetResult.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/UserSettingsSetResult.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/Verbosity.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/Verbosity.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/Verbosity.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/Verbosity.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/WorkspaceDiffFileChange.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkspaceDiffFileChange.java similarity index 88% rename from java/src/generated/java/com/github/copilot/generated/rpc/WorkspaceDiffFileChange.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkspaceDiffFileChange.java index f091bc279..e63b92b56 100644 --- a/java/src/generated/java/com/github/copilot/generated/rpc/WorkspaceDiffFileChange.java +++ b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkspaceDiffFileChange.java @@ -21,7 +21,7 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public record WorkspaceDiffFileChange( - /** Path to the changed file, relative to the workspace root. */ + /** Path to the changed file, relative to the workspace root when the file lives under it. A file changed outside the workspace root keeps a `../`-relative path, or an absolute path when no relative path exists (for example a different Windows drive). */ @JsonProperty("path") String path, /** Unified diff content for the file. Empty when the diff was truncated. */ @JsonProperty("diff") String diff, diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/WorkspaceDiffFileChangeType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkspaceDiffFileChangeType.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/WorkspaceDiffFileChangeType.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkspaceDiffFileChangeType.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/WorkspaceDiffMode.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkspaceDiffMode.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/WorkspaceDiffMode.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkspaceDiffMode.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/WorkspaceSummaryHostType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkspaceSummaryHostType.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/WorkspaceSummaryHostType.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkspaceSummaryHostType.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/WorkspacesCheckpoints.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkspacesCheckpoints.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/WorkspacesCheckpoints.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkspacesCheckpoints.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/WorkspacesWorkspaceDetailsHostType.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkspacesWorkspaceDetailsHostType.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/WorkspacesWorkspaceDetailsHostType.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/WorkspacesWorkspaceDetailsHostType.java diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/package-info.java b/java/sdk/src/generated/java/com/github/copilot/generated/rpc/package-info.java similarity index 100% rename from java/src/generated/java/com/github/copilot/generated/rpc/package-info.java rename to java/sdk/src/generated/java/com/github/copilot/generated/rpc/package-info.java diff --git a/java/src/main/java/com/github/copilot/AllowCopilotExperimental.java b/java/sdk/src/main/java/com/github/copilot/AllowCopilotExperimental.java similarity index 100% rename from java/src/main/java/com/github/copilot/AllowCopilotExperimental.java rename to java/sdk/src/main/java/com/github/copilot/AllowCopilotExperimental.java diff --git a/java/src/main/java/com/github/copilot/CliServerManager.java b/java/sdk/src/main/java/com/github/copilot/CliServerManager.java similarity index 100% rename from java/src/main/java/com/github/copilot/CliServerManager.java rename to java/sdk/src/main/java/com/github/copilot/CliServerManager.java diff --git a/java/src/main/java/com/github/copilot/ConnectionState.java b/java/sdk/src/main/java/com/github/copilot/ConnectionState.java similarity index 100% rename from java/src/main/java/com/github/copilot/ConnectionState.java rename to java/sdk/src/main/java/com/github/copilot/ConnectionState.java diff --git a/java/src/main/java/com/github/copilot/CopilotClient.java b/java/sdk/src/main/java/com/github/copilot/CopilotClient.java similarity index 80% rename from java/src/main/java/com/github/copilot/CopilotClient.java rename to java/sdk/src/main/java/com/github/copilot/CopilotClient.java index 7244c8c0a..cdd1b9ff3 100644 --- a/java/src/main/java/com/github/copilot/CopilotClient.java +++ b/java/sdk/src/main/java/com/github/copilot/CopilotClient.java @@ -5,8 +5,11 @@ package com.github.copilot; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.net.URI; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -21,8 +24,15 @@ import java.util.logging.Level; import java.util.logging.Logger; +import com.github.copilot.ffi.FfiRuntimeHost; +import com.github.copilot.ffi.NativeRuntimeLoader; import com.github.copilot.rpc.CopilotClientMode; import com.github.copilot.rpc.CopilotClientOptions; +import com.github.copilot.rpc.InProcessRuntimeConnection; +import com.github.copilot.rpc.RuntimeConnection; +import com.github.copilot.rpc.StdioRuntimeConnection; +import com.github.copilot.rpc.TcpRuntimeConnection; +import com.github.copilot.rpc.UriRuntimeConnection; import com.github.copilot.rpc.CreateSessionResponse; import com.github.copilot.generated.rpc.SessionOptionsUpdateParams; import com.github.copilot.generated.rpc.SessionInstalledPlugin; @@ -111,7 +121,9 @@ public final class CopilotClient implements AutoCloseable { private volatile boolean disposed = false; private final String optionsHost; private final Integer optionsPort; + private final RuntimeConnection runtimeConnection; private final String effectiveConnectionToken; + private final Runnable closeHook; private volatile List modelsCache; private final Object modelsCacheLock = new Object(); @@ -131,7 +143,30 @@ public CopilotClient() { * if mutually exclusive options are provided */ public CopilotClient(CopilotClientOptions options) { + this(options, null); + } + + CopilotClient(CopilotClientOptions options, Runnable closeHook) { this.options = options != null ? options : new CopilotClientOptions(); + this.closeHook = closeHook; + + // Resolve the transport: an explicit RuntimeConnection wins; otherwise the + // COPILOT_SDK_DEFAULT_CONNECTION env var, or the individual transport options. + RuntimeConnection requestedConnection = this.options.getConnection(); + if (requestedConnection != null) { + validateEnvironmentOptions(this.options, requestedConnection); + validateConnectionConflicts(this.options, requestedConnection); + applyConnection(this.options, requestedConnection); + } else { + requestedConnection = resolveDefaultConnection(this.options); + validateEnvironmentOptions(this.options, requestedConnection); + // When the env var overrides inference (e.g. inprocess), validate that + // no legacy transport options conflict with the resolved connection. + if (requestedConnection != null) { + validateConnectionConflicts(this.options, requestedConnection); + } + } + this.runtimeConnection = requestedConnection; // When cliUrl is set, auto-correct useStdio since we're connecting via TCP if (this.options.getCliUrl() != null && !this.options.getCliUrl().isEmpty()) { @@ -199,6 +234,275 @@ public CopilotClient(CopilotClientOptions options) { this.serverManager.setConnectionToken(this.effectiveConnectionToken); } + /** + * Environment variable that overrides the transport used when the caller does + * not set {@link CopilotClientOptions#setConnection(RuntimeConnection)}. + * Accepts {@code "inprocess"} or {@code "stdio"} (case-insensitive); unset + * keeps the transport selected by the individual transport options. Any other + * value is an error. Ignored when a connection is set explicitly. + */ + static final String DEFAULT_CONNECTION_ENV_VAR = "COPILOT_SDK_DEFAULT_CONNECTION"; + + /** + * Resolves the connection to use when the caller did not set one, honoring + * {@link #DEFAULT_CONNECTION_ENV_VAR} and otherwise inferring the transport + * from the individual transport options. + */ + private static RuntimeConnection resolveDefaultConnection(CopilotClientOptions options) { + return resolveDefaultConnection(options, System.getenv(DEFAULT_CONNECTION_ENV_VAR)); + } + + /** + * Resolves the default connection from an explicit environment-variable value. + * Package-private so tests can supply the value directly. + */ + static RuntimeConnection resolveDefaultConnection(CopilotClientOptions options, String envValue) { + if (envValue != null && !envValue.isEmpty()) { + if ("inprocess".equalsIgnoreCase(envValue)) { + // Explicit subprocess options take precedence over the env var default. + if (options.getCliUrl() != null && !options.getCliUrl().isEmpty()) { + return inferConnectionFromOptions(options); + } + if (options.getCliPath() != null && !options.getCliPath().isEmpty()) { + return inferConnectionFromOptions(options); + } + if (options.getPort() != 0) { + return inferConnectionFromOptions(options); + } + if (!options.isUseStdio() || options.getTcpConnectionToken() != null) { + return inferConnectionFromOptions(options); + } + return RuntimeConnection.forInProcess(); + } + if (!"stdio".equalsIgnoreCase(envValue)) { + throw new IllegalArgumentException("Invalid " + DEFAULT_CONNECTION_ENV_VAR + " value '" + envValue + + "'. Expected 'inprocess', 'stdio', or unset."); + } + } + + return inferConnectionFromOptions(options); + } + + /** + * Maps the individual transport options onto the equivalent + * {@link RuntimeConnection}, preserving the behavior of clients written before + * connections existed. + */ + private static RuntimeConnection inferConnectionFromOptions(CopilotClientOptions options) { + String cliUrl = options.getCliUrl(); + List args = options.getCliArgs() != null ? Arrays.asList(options.getCliArgs()) : null; + if (cliUrl != null && !cliUrl.isEmpty()) { + return RuntimeConnection.forUri(cliUrl).setConnectionToken(options.getTcpConnectionToken()); + } + if (options.isUseStdio()) { + StdioRuntimeConnection stdio = RuntimeConnection.forStdio(options.getCliPath()); + if (args != null) { + stdio.setArgs(args); + } + return stdio; + } + TcpRuntimeConnection tcp = RuntimeConnection.forTcp().setPath(options.getCliPath()).setPort(options.getPort()) + .setConnectionToken(options.getTcpConnectionToken()); + if (args != null) { + tcp.setArgs(args); + } + return tcp; + } + + /** + * Rejects transport options that contradict the configured connection. Values + * that match what the connection implies are accepted so that constructing + * several clients from the same options instance stays valid. + */ + private static void validateConnectionConflicts(CopilotClientOptions options, RuntimeConnection connection) { + String impliedPath = null; + String impliedUrl = null; + String impliedToken = null; + int impliedPort = 0; + boolean impliedUseStdio = true; + List impliedArgs = null; + + if (connection instanceof StdioRuntimeConnection stdio) { + impliedPath = stdio.getPath(); + impliedArgs = stdio.getArgs(); + } else if (connection instanceof TcpRuntimeConnection tcp) { + impliedPath = tcp.getPath(); + impliedPort = tcp.getPort(); + impliedToken = tcp.getConnectionToken(); + impliedArgs = tcp.getArgs(); + impliedUseStdio = false; + } else if (connection instanceof UriRuntimeConnection uri) { + impliedUrl = uri.getUrl(); + impliedToken = uri.getConnectionToken(); + impliedUseStdio = false; + } + + rejectConflict("CliPath", options.getCliPath() != null && !options.getCliPath().equals(impliedPath)); + rejectConflict("CliUrl", options.getCliUrl() != null && !options.getCliUrl().isEmpty() + && !options.getCliUrl().equals(impliedUrl)); + rejectConflict("Port", options.getPort() != 0 && options.getPort() != impliedPort); + rejectConflict("TcpConnectionToken", + options.getTcpConnectionToken() != null && !options.getTcpConnectionToken().equals(impliedToken)); + rejectConflict("UseStdio", !options.isUseStdio() && impliedUseStdio); + rejectConflict("CliArgs", options.getCliArgs() != null + && !Arrays.asList(options.getCliArgs()).equals(impliedArgs == null ? List.of() : impliedArgs)); + } + + private static void rejectConflict(String optionName, boolean conflicting) { + if (conflicting) { + throw new IllegalArgumentException("CopilotClientOptions." + optionName + + " cannot be combined with CopilotClientOptions.setConnection(); configure the transport on the" + + " RuntimeConnection instead."); + } + } + + /** + * Projects the configured connection onto the individual transport options so + * that the rest of the client sees a single, consistent view of the transport. + */ + private static void applyConnection(CopilotClientOptions options, RuntimeConnection connection) { + if (connection instanceof StdioRuntimeConnection stdio) { + options.setUseStdio(true); + if (stdio.getPath() != null) { + options.setCliPath(stdio.getPath()); + } + applyConnectionArgs(options, stdio.getArgs()); + } else if (connection instanceof TcpRuntimeConnection tcp) { + options.setUseStdio(false); + if (tcp.getPath() != null) { + options.setCliPath(tcp.getPath()); + } + options.setPort(tcp.getPort()); + if (tcp.getConnectionToken() != null) { + options.setTcpConnectionToken(tcp.getConnectionToken()); + } + applyConnectionArgs(options, tcp.getArgs()); + } else if (connection instanceof UriRuntimeConnection uri) { + options.setUseStdio(false); + options.setCliUrl(uri.getUrl()); + if (uri.getConnectionToken() != null) { + options.setTcpConnectionToken(uri.getConnectionToken()); + } + } + } + + private static void applyConnectionArgs(CopilotClientOptions options, List args) { + if (args != null) { + options.setCliArgs(args.toArray(new String[0])); + } + } + + /** + * Rejects per-process options that the in-process transport cannot honor. These + * options are lowered onto a child process, but the in-process runtime runs + * inside the shared host process, whose single environment and working + * directory cannot carry per-client values. + */ + private static void validateEnvironmentOptions(CopilotClientOptions options, RuntimeConnection connection) { + if (!(connection instanceof InProcessRuntimeConnection)) { + return; + } + + rejectInProcessOption("Environment", options.getEnvironment() != null && !options.getEnvironment().isEmpty(), + "set the variables on the host process environment instead"); + rejectInProcessOption("Telemetry", options.getTelemetry() != null, + "configure telemetry through the host process environment instead"); + rejectInProcessOption("Cwd", options.getCwd() != null, + "set the process working directory before creating the client instead"); + rejectInProcessOption("CliArgs", options.getCliArgs() != null && options.getCliArgs().length > 0, + "use the typed client options instead"); + } + + private static void rejectInProcessOption(String optionName, boolean present, String remedy) { + if (present) { + throw new IllegalArgumentException("CopilotClientOptions." + optionName + + " is not supported with RuntimeConnection.forInProcess(): the in-process runtime shares the host" + + " process, so per-client values cannot be honored; " + remedy + "."); + } + } + + /** + * Duplex streams of an in-process runtime, together with the resource that owns + * its lifetime. + * + * @param receiveStream + * stream carrying messages from the runtime + * @param sendStream + * stream carrying messages to the runtime + * @param host + * resource closed when the client stops + */ + record InProcessTransport(InputStream receiveStream, OutputStream sendStream, AutoCloseable host) { + } + + /** + * Opens the transport for the in-process runtime. Package-private so tests can + * substitute a fake for the native runtime. + */ + @FunctionalInterface + interface InProcessTransportFactory { + /** + * Opens the in-process transport. + * + * @param options + * client options used to configure the runtime + * @return the opened transport + * @throws IOException + * if the runtime cannot be started + */ + InProcessTransport open(CopilotClientOptions options) throws IOException; + } + + private volatile InProcessTransportFactory inProcessTransportFactory = CopilotClient::openInProcessTransport; + + /** + * Returns the resolved connection describing how this client reaches the + * runtime. Package-private test seam. + * + * @return the resolved connection + */ + RuntimeConnection getRuntimeConnection() { + return runtimeConnection; + } + + /** + * Replaces the in-process transport factory. Package-private test seam. + * + * @param factory + * the factory to use + */ + void setInProcessTransportFactory(InProcessTransportFactory factory) { + this.inProcessTransportFactory = java.util.Objects.requireNonNull(factory, "factory must not be null"); + } + + private static InProcessTransport openInProcessTransport(CopilotClientOptions options) throws IOException { + FfiRuntimeHost host = new FfiRuntimeHost(); + try { + host.start(resolveInProcessEntrypoint(), options); + } catch (RuntimeException | Error e) { + host.close(); + throw e; + } + return new InProcessTransport(host.getReceiveStream(), host.getSendStream(), host); + } + + /** + * Resolves the runtime entrypoint handed to the in-process host. The copilot + * CLI executable is resolved from the same bundled location as + * {@code runtime.node} — no environment variables or PATH search. + */ + private static String resolveInProcessEntrypoint() throws IOException { + return NativeRuntimeLoader.resolveEntrypoint().toString(); + } + + private static void closeRuntimeHost(AutoCloseable host) { + try { + host.close(); + } catch (Exception e) { + LOG.log(Level.FINE, "Error closing in-process runtime host", e); + } + } + /** * Starts the Copilot client and connects to the server. * @@ -227,11 +531,15 @@ private CompletableFuture startCore() { private Connection startCoreBody() { Process process = null; + JsonRpcClient rpc = null; + InProcessTransport inProcessTransport = null; long startNanos = System.nanoTime(); try { - JsonRpcClient rpc; - - if (optionsHost != null && optionsPort != null) { + if (runtimeConnection instanceof InProcessRuntimeConnection) { + // In-process runtime hosted in this process (no child process) + inProcessTransport = inProcessTransportFactory.open(options); + rpc = JsonRpcClient.fromStreams(inProcessTransport.receiveStream(), inProcessTransport.sendStream()); + } else if (optionsHost != null && optionsPort != null) { // External server (TCP) rpc = serverManager.connectToServer(null, optionsHost, optionsPort); } else { @@ -245,11 +553,13 @@ private Connection startCoreBody() { LoggingHelpers.logTiming(LOG, Level.FINE, "CopilotClient.start transport setup complete. Elapsed={Elapsed}", startNanos); - Connection connection = new Connection(rpc, process, new ServerRpc(rpc::invoke)); + JsonRpcClient connectedRpc = rpc; + Connection connection = new Connection(connectedRpc, process, new ServerRpc(connectedRpc::invoke), + inProcessTransport == null ? null : inProcessTransport.host()); // Register handlers for server-to-client calls RpcHandlerDispatcher dispatcher = new RpcHandlerDispatcher(sessions, lifecycleManager::dispatch, executor); - dispatcher.registerHandlers(rpc); + dispatcher.registerHandlers(connectedRpc); // Register the LLM inference request handler when configured. com.github.copilot.CopilotRequestHandler requestHandler = this.options.getRequestHandler(); @@ -257,7 +567,7 @@ private Connection startCoreBody() { if (hasLlmInference) { LlmInferenceAdapter llmAdapter = new LlmInferenceAdapter(requestHandler, () -> connection.serverRpc().llmInference, executor); - llmAdapter.registerHandlers(rpc); + llmAdapter.registerHandlers(connectedRpc); } // Register the GitHub telemetry forwarding handler when configured. @@ -265,7 +575,7 @@ private Connection startCoreBody() { .getOnGitHubTelemetry(); if (onGitHubTelemetry != null) { GitHubTelemetryAdapter telemetryAdapter = new GitHubTelemetryAdapter(onGitHubTelemetry); - telemetryAdapter.registerHandlers(rpc); + telemetryAdapter.registerHandlers(connectedRpc); } // Verify protocol version @@ -273,6 +583,15 @@ private Connection startCoreBody() { LoggingHelpers.logTiming(LOG, Level.FINE, "CopilotClient.start protocol verification complete. Elapsed={Elapsed}", startNanos); + var builtinPluginDirectories = options.getBuiltinPluginDirectories(); + if (builtinPluginDirectories != null && !builtinPluginDirectories.isEmpty()) { + var paths = new ArrayList(builtinPluginDirectories.size()); + for (var path : builtinPluginDirectories) { + paths.add(path.toString()); + } + connection.rpc.invoke("plugins.builtin.set", Map.of("paths", paths), Void.class).join(); + } + // Register as the runtime's LLM inference provider once connected. if (hasLlmInference) { connection.serverRpc().llmInference.setProvider().join(); @@ -289,6 +608,16 @@ private Connection startCoreBody() { if (process != null) { cleanupCliProcess(process, true); } + if (rpc != null) { + try { + rpc.close(); + } catch (Exception closeError) { + LOG.log(Level.FINE, "Error closing RPC after failed startup", closeError); + } + } + if (inProcessTransport != null) { + closeRuntimeHost(inProcessTransport.host()); + } String stderr = serverManager.getStderrOutput(); if (!stderr.isEmpty()) { throw new CompletionException(new IOException( @@ -438,7 +767,7 @@ private CompletableFuture cleanupConnection(boolean gracefulRuntimeShutdow } CompletableFuture shutdownFuture = CompletableFuture.completedFuture(null); - if (gracefulRuntimeShutdown && connection.process != null) { + if (gracefulRuntimeShutdown && (connection.process != null || connection.runtimeHost != null)) { long runtimeShutdownStartNanos = System.nanoTime(); shutdownFuture = connection.rpc.invoke("runtime.shutdown", Map.of(), Void.class) .orTimeout(RUNTIME_SHUTDOWN_TIMEOUT_SECONDS, TimeUnit.SECONDS) @@ -465,6 +794,9 @@ private CompletableFuture cleanupConnection(boolean gracefulRuntimeShutdow if (connection.process != null) { cleanupCliProcess(connection.process, !gracefulRuntimeShutdown || error != null); } + if (connection.runtimeHost != null) { + closeRuntimeHost(connection.runtimeHost); + } return (Void) null; }); }).thenCompose(result -> result); @@ -593,7 +925,7 @@ public CompletableFuture createSession(SessionConfig config) { registeredIdHolder[0] = localSessionId; } - var request = SessionRequestBuilder.buildCreateRequest(config, localSessionId); + var request = SessionRequestBuilder.buildCreateRequest(config, localSessionId, options.getMode()); if (extracted.wireSystemMessage() != config.getSystemMessage()) { request.setSystemMessage(extracted.wireSystemMessage()); } @@ -754,7 +1086,7 @@ public CompletableFuture resumeSession(String sessionId, ResumeS if (extracted.transformCallbacks() != null) { session.registerTransformCallbacks(extracted.transformCallbacks()); } - var request = SessionRequestBuilder.buildResumeRequest(sessionId, config); + var request = SessionRequestBuilder.buildResumeRequest(sessionId, config, options.getMode()); if (extracted.wireSystemMessage() != config.getSystemMessage()) { request.setSystemMessage(extracted.wireSystemMessage()); } @@ -941,6 +1273,7 @@ CompletableFuture updateSessionOptionsForMode(CopilotSession session, Bool null, // excludedBuiltinAgents null, // toolFilterPrecedence null, // enableScriptSafety + null, // shell null, // shellInitProfile null, // shellProcessFlags null, // sandboxConfig @@ -966,6 +1299,7 @@ CompletableFuture updateSessionOptionsForMode(CopilotSession session, Bool null, // enableReasoningSummaries null, // agentContext null, // eventsLogDirectory + null, // eventsLogIncludesSubagents null, // additionalContentExclusionPolicies patchSchedule, // manageScheduleEnabled null, // sessionCapabilities @@ -1351,6 +1685,9 @@ public void close() { LOG.log(Level.FINE, "Error during close", e); } finally { shutdownOwnedExecutor(); + if (closeHook != null) { + closeHook.run(); + } } } @@ -1390,7 +1727,8 @@ private void shutdownOwnedExecutor() { } } - private static record Connection(JsonRpcClient rpc, Process process, ServerRpc serverRpc) { + private static record Connection(JsonRpcClient rpc, Process process, ServerRpc serverRpc, + AutoCloseable runtimeHost) { }; } diff --git a/java/src/main/java/com/github/copilot/CopilotExperimental.java b/java/sdk/src/main/java/com/github/copilot/CopilotExperimental.java similarity index 100% rename from java/src/main/java/com/github/copilot/CopilotExperimental.java rename to java/sdk/src/main/java/com/github/copilot/CopilotExperimental.java diff --git a/java/src/main/java/com/github/copilot/CopilotExperimentalProcessor.java b/java/sdk/src/main/java/com/github/copilot/CopilotExperimentalProcessor.java similarity index 100% rename from java/src/main/java/com/github/copilot/CopilotExperimentalProcessor.java rename to java/sdk/src/main/java/com/github/copilot/CopilotExperimentalProcessor.java diff --git a/java/src/main/java/com/github/copilot/CopilotRequestContext.java b/java/sdk/src/main/java/com/github/copilot/CopilotRequestContext.java similarity index 100% rename from java/src/main/java/com/github/copilot/CopilotRequestContext.java rename to java/sdk/src/main/java/com/github/copilot/CopilotRequestContext.java diff --git a/java/src/main/java/com/github/copilot/CopilotRequestHandler.java b/java/sdk/src/main/java/com/github/copilot/CopilotRequestHandler.java similarity index 100% rename from java/src/main/java/com/github/copilot/CopilotRequestHandler.java rename to java/sdk/src/main/java/com/github/copilot/CopilotRequestHandler.java diff --git a/java/src/main/java/com/github/copilot/CopilotRequestTransport.java b/java/sdk/src/main/java/com/github/copilot/CopilotRequestTransport.java similarity index 100% rename from java/src/main/java/com/github/copilot/CopilotRequestTransport.java rename to java/sdk/src/main/java/com/github/copilot/CopilotRequestTransport.java diff --git a/java/src/main/java/com/github/copilot/CopilotSession.java b/java/sdk/src/main/java/com/github/copilot/CopilotSession.java similarity index 97% rename from java/src/main/java/com/github/copilot/CopilotSession.java rename to java/sdk/src/main/java/com/github/copilot/CopilotSession.java index 4826b3309..ca2adf462 100644 --- a/java/src/main/java/com/github/copilot/CopilotSession.java +++ b/java/sdk/src/main/java/com/github/copilot/CopilotSession.java @@ -78,6 +78,7 @@ import com.github.copilot.rpc.ElicitationSchema; import com.github.copilot.rpc.BearerTokenProvider; import com.github.copilot.rpc.GetMessagesResponse; +import com.github.copilot.rpc.AgentStopHookInput; import com.github.copilot.rpc.HookInvocation; import com.github.copilot.rpc.InputOptions; import com.github.copilot.rpc.MessageOptions; @@ -109,6 +110,7 @@ import com.github.copilot.rpc.UserInputRequest; import com.github.copilot.rpc.UserInputResponse; import com.github.copilot.rpc.UserPromptSubmittedHookInput; +import com.github.copilot.rpc.UserPromptTransformedHookInput; /** * Represents a single conversation session with the Copilot CLI. @@ -184,6 +186,7 @@ public final class CopilotSession implements AutoCloseable { private final Map commandHandlers = new ConcurrentHashMap<>(); private final Map bearerTokenProviders = new ConcurrentHashMap<>(); private final AtomicReference permissionHandler = new AtomicReference<>(); + private volatile boolean managedSettingsEnabled; private final AtomicReference mcpAuthHandler = new AtomicReference<>(); private final AtomicReference userInputHandler = new AtomicReference<>(); private final AtomicReference elicitationHandler = new AtomicReference<>(); @@ -1012,6 +1015,7 @@ private void executePermissionAndRespondAsync(String requestId, PermissionReques try { var invocation = new PermissionInvocation(); invocation.setSessionId(sessionId); + invocation.setManagedSettingsEnabled(managedSettingsEnabled); handler.handle(permissionRequest, invocation).thenAccept(result -> { try { PermissionRequestResultKind kind = new PermissionRequestResultKind(result.getKind()); @@ -1021,18 +1025,19 @@ private void executePermissionAndRespondAsync(String requestId, PermissionReques return; } getRpc().permissions.handlePendingPermissionRequest( - new SessionPermissionsHandlePendingPermissionRequestParams(sessionId, requestId, - result)); + new SessionPermissionsHandlePendingPermissionRequestParams(sessionId, requestId, result, + result.getDecisionContext())); } catch (Exception e) { LOG.log(Level.WARNING, "Error sending permission result for requestId=" + requestId, e); } }).exceptionally(ex -> { + LOG.log(Level.SEVERE, "Permission handler failed for requestId=" + requestId, ex); try { PermissionRequestResult denied = new PermissionRequestResult(); denied.setKind(PermissionRequestResultKind.DENIED_COULD_NOT_REQUEST_FROM_USER); getRpc().permissions.handlePendingPermissionRequest( - new SessionPermissionsHandlePendingPermissionRequestParams(sessionId, requestId, - denied)); + new SessionPermissionsHandlePendingPermissionRequestParams(sessionId, requestId, denied, + null)); } catch (Exception e) { LOG.log(Level.WARNING, "Error sending permission denied for requestId=" + requestId, e); } @@ -1044,7 +1049,8 @@ private void executePermissionAndRespondAsync(String requestId, PermissionReques PermissionRequestResult denied = new PermissionRequestResult(); denied.setKind(PermissionRequestResultKind.DENIED_COULD_NOT_REQUEST_FROM_USER); getRpc().permissions.handlePendingPermissionRequest( - new SessionPermissionsHandlePendingPermissionRequestParams(sessionId, requestId, denied)); + new SessionPermissionsHandlePendingPermissionRequestParams(sessionId, requestId, denied, + null)); } catch (Exception sendEx) { LOG.log(Level.WARNING, "Error sending permission denied for requestId=" + requestId, sendEx); } @@ -1377,6 +1383,10 @@ void registerPermissionHandler(PermissionHandler handler) { permissionHandler.set(handler); } + void setManagedSettingsEnabled(boolean managedSettingsEnabled) { + this.managedSettingsEnabled = managedSettingsEnabled; + } + void registerMcpAuthHandler(McpAuthHandler handler) { mcpAuthHandler.set(handler); } @@ -1402,6 +1412,7 @@ CompletableFuture handlePermissionRequest(JsonNode perm PermissionRequest request = MAPPER.treeToValue(permissionRequestData, PermissionRequest.class); var invocation = new PermissionInvocation(); invocation.setSessionId(sessionId); + invocation.setManagedSettingsEnabled(managedSettingsEnabled); return handler.handle(request, invocation).exceptionally(ex -> { LOG.log(Level.SEVERE, "Permission handler threw an exception", ex); PermissionRequestResult result = new PermissionRequestResult(); @@ -1860,6 +1871,17 @@ CompletableFuture handleHooksInvoke(String hookType, JsonNode input) { return promptResult.thenApply(output -> (Object) output); } break; + case "userPromptTransformed" : + if (hooks.getOnUserPromptTransformed() != null) { + UserPromptTransformedHookInput transformedInput = MAPPER.treeToValue(input, + UserPromptTransformedHookInput.class); + var transformedResult = hooks.getOnUserPromptTransformed().handle(transformedInput, invocation); + if (transformedResult == null) { + return CompletableFuture.completedFuture(null); + } + return transformedResult.thenApply(output -> (Object) output); + } + break; case "sessionStart" : if (hooks.getOnSessionStart() != null) { SessionStartHookInput startInput = MAPPER.treeToValue(input, SessionStartHookInput.class); @@ -1880,6 +1902,16 @@ CompletableFuture handleHooksInvoke(String hookType, JsonNode input) { return endResult.thenApply(output -> (Object) output); } break; + case "agentStop" : + if (hooks.getOnAgentStop() != null) { + AgentStopHookInput stopInput = MAPPER.treeToValue(input, AgentStopHookInput.class); + var stopResult = hooks.getOnAgentStop().handle(stopInput, invocation); + if (stopResult == null) { + return CompletableFuture.completedFuture(null); + } + return stopResult.thenApply(output -> (Object) output); + } + break; default : LOG.fine("Unhandled hook type: " + hookType); } @@ -1953,7 +1985,8 @@ public CompletableFuture abort() { * the model ID to switch to (e.g., {@code "gpt-5.4"}) * @param reasoningEffort * reasoning effort level (e.g., {@code "low"}, {@code "medium"}, - * {@code "high"}, {@code "xhigh"}); {@code null} to use default + * {@code "high"}, {@code "xhigh"}, {@code "max"}); {@code null} to + * use default * @return a future that completes when the model switch is acknowledged * @throws IllegalStateException * if this session has been terminated @@ -1962,7 +1995,8 @@ public CompletableFuture abort() { public CompletableFuture setModel(String model, String reasoningEffort) { ensureNotTerminated(); return getRpc().model - .switchTo(new SessionModelSwitchToParams(sessionId, model, reasoningEffort, null, null, null, null)) + .switchTo( + new SessionModelSwitchToParams(sessionId, model, reasoningEffort, null, null, null, null, null)) .thenApply(r -> null); } @@ -1983,7 +2017,8 @@ public CompletableFuture setModel(String model, String reasoningEffort) { * the model ID to switch to (e.g., {@code "gpt-5.4"}) * @param reasoningEffort * reasoning effort level (e.g., {@code "low"}, {@code "medium"}, - * {@code "high"}, {@code "xhigh"}); {@code null} to use default + * {@code "high"}, {@code "xhigh"}, {@code "max"}); {@code null} to + * use default * @param modelCapabilities * per-property overrides for model capabilities; {@code null} to use * runtime defaults @@ -2043,7 +2078,7 @@ public CompletableFuture setModel(String model, String reasoningEffort, St ? null : com.github.copilot.generated.rpc.ReasoningSummary.fromValue(reasoningSummary); return getRpc().model.switchTo(new SessionModelSwitchToParams(sessionId, model, reasoningEffort, - generatedReasoningSummary, null, generatedCapabilities, null)).thenApply(r -> null); + generatedReasoningSummary, null, generatedCapabilities, null, null)).thenApply(r -> null); } /** diff --git a/java/src/main/java/com/github/copilot/CopilotWebSocketCloseStatus.java b/java/sdk/src/main/java/com/github/copilot/CopilotWebSocketCloseStatus.java similarity index 100% rename from java/src/main/java/com/github/copilot/CopilotWebSocketCloseStatus.java rename to java/sdk/src/main/java/com/github/copilot/CopilotWebSocketCloseStatus.java diff --git a/java/src/main/java/com/github/copilot/CopilotWebSocketForwarder.java b/java/sdk/src/main/java/com/github/copilot/CopilotWebSocketForwarder.java similarity index 100% rename from java/src/main/java/com/github/copilot/CopilotWebSocketForwarder.java rename to java/sdk/src/main/java/com/github/copilot/CopilotWebSocketForwarder.java diff --git a/java/src/main/java/com/github/copilot/CopilotWebSocketHandler.java b/java/sdk/src/main/java/com/github/copilot/CopilotWebSocketHandler.java similarity index 100% rename from java/src/main/java/com/github/copilot/CopilotWebSocketHandler.java rename to java/sdk/src/main/java/com/github/copilot/CopilotWebSocketHandler.java diff --git a/java/src/main/java/com/github/copilot/CopilotWebSocketMessage.java b/java/sdk/src/main/java/com/github/copilot/CopilotWebSocketMessage.java similarity index 100% rename from java/src/main/java/com/github/copilot/CopilotWebSocketMessage.java rename to java/sdk/src/main/java/com/github/copilot/CopilotWebSocketMessage.java diff --git a/java/src/main/java/com/github/copilot/EventErrorHandler.java b/java/sdk/src/main/java/com/github/copilot/EventErrorHandler.java similarity index 100% rename from java/src/main/java/com/github/copilot/EventErrorHandler.java rename to java/sdk/src/main/java/com/github/copilot/EventErrorHandler.java diff --git a/java/src/main/java/com/github/copilot/EventErrorPolicy.java b/java/sdk/src/main/java/com/github/copilot/EventErrorPolicy.java similarity index 100% rename from java/src/main/java/com/github/copilot/EventErrorPolicy.java rename to java/sdk/src/main/java/com/github/copilot/EventErrorPolicy.java diff --git a/java/src/main/java/com/github/copilot/ExtractedTransforms.java b/java/sdk/src/main/java/com/github/copilot/ExtractedTransforms.java similarity index 100% rename from java/src/main/java/com/github/copilot/ExtractedTransforms.java rename to java/sdk/src/main/java/com/github/copilot/ExtractedTransforms.java diff --git a/java/src/main/java/com/github/copilot/GitHubTelemetryAdapter.java b/java/sdk/src/main/java/com/github/copilot/GitHubTelemetryAdapter.java similarity index 100% rename from java/src/main/java/com/github/copilot/GitHubTelemetryAdapter.java rename to java/sdk/src/main/java/com/github/copilot/GitHubTelemetryAdapter.java diff --git a/java/src/main/java/com/github/copilot/InternalExecutorProvider.java b/java/sdk/src/main/java/com/github/copilot/InternalExecutorProvider.java similarity index 100% rename from java/src/main/java/com/github/copilot/InternalExecutorProvider.java rename to java/sdk/src/main/java/com/github/copilot/InternalExecutorProvider.java diff --git a/java/src/main/java/com/github/copilot/JsonRpcClient.java b/java/sdk/src/main/java/com/github/copilot/JsonRpcClient.java similarity index 89% rename from java/src/main/java/com/github/copilot/JsonRpcClient.java rename to java/sdk/src/main/java/com/github/copilot/JsonRpcClient.java index 5303c4f50..550bd4ca4 100644 --- a/java/src/main/java/com/github/copilot/JsonRpcClient.java +++ b/java/sdk/src/main/java/com/github/copilot/JsonRpcClient.java @@ -18,6 +18,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicLong; import java.util.function.BiConsumer; +import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; @@ -46,6 +47,7 @@ class JsonRpcClient implements AutoCloseable { private final OutputStream outputStream; private final Socket socket; private final Process process; + private final boolean ownsStreams; private final AtomicLong requestIdCounter = new AtomicLong(0); private final Map> pendingRequests = new ConcurrentHashMap<>(); private final Map> notificationHandlers = new ConcurrentHashMap<>(); @@ -53,15 +55,29 @@ class JsonRpcClient implements AutoCloseable { private volatile boolean running = true; private JsonRpcClient(InputStream inputStream, OutputStream outputStream, Socket socket, Process process) { + this(inputStream, outputStream, socket, process, false); + } + + private JsonRpcClient(InputStream inputStream, OutputStream outputStream, Socket socket, Process process, + boolean ownsStreams) { + this(inputStream, outputStream, socket, process, ownsStreams, null); + } + + private JsonRpcClient(InputStream inputStream, OutputStream outputStream, Socket socket, Process process, + boolean ownsStreams, Consumer initializer) { this.inputStream = inputStream; this.outputStream = outputStream; this.socket = socket; this.process = process; + this.ownsStreams = ownsStreams; this.readerExecutor = Executors.newSingleThreadExecutor(r -> { Thread t = new Thread(r, "jsonrpc-reader"); t.setDaemon(true); return t; }); + if (initializer != null) { + initializer.accept(this); + } startReader(); } @@ -93,6 +109,19 @@ public static JsonRpcClient fromSocket(Socket socket) throws IOException { return new JsonRpcClient(socket.getInputStream(), socket.getOutputStream(), socket, null); } + static JsonRpcClient fromSocket(Socket socket, Consumer initializer) throws IOException { + return new JsonRpcClient(socket.getInputStream(), socket.getOutputStream(), socket, null, false, initializer); + } + + /** + * Creates a JSON-RPC client over arbitrary input/output streams. The client + * takes ownership of the streams and closes them when {@link #close()} is + * called. + */ + public static JsonRpcClient fromStreams(InputStream inputStream, OutputStream outputStream) { + return new JsonRpcClient(inputStream, outputStream, null, null, true); + } + /** * Registers a handler for JSON-RPC method calls (requests/notifications from * server). @@ -344,6 +373,19 @@ public void close() { if (process != null) { process.destroy(); } + + if (ownsStreams) { + try { + inputStream.close(); + } catch (IOException e) { + LOG.log(Level.FINE, "Error closing input stream", e); + } + try { + outputStream.close(); + } catch (IOException e) { + LOG.log(Level.FINE, "Error closing output stream", e); + } + } } public boolean isConnected() { diff --git a/java/src/main/java/com/github/copilot/JsonRpcException.java b/java/sdk/src/main/java/com/github/copilot/JsonRpcException.java similarity index 100% rename from java/src/main/java/com/github/copilot/JsonRpcException.java rename to java/sdk/src/main/java/com/github/copilot/JsonRpcException.java diff --git a/java/src/main/java/com/github/copilot/LifecycleEventManager.java b/java/sdk/src/main/java/com/github/copilot/LifecycleEventManager.java similarity index 100% rename from java/src/main/java/com/github/copilot/LifecycleEventManager.java rename to java/sdk/src/main/java/com/github/copilot/LifecycleEventManager.java diff --git a/java/src/main/java/com/github/copilot/LlmInferenceAdapter.java b/java/sdk/src/main/java/com/github/copilot/LlmInferenceAdapter.java similarity index 100% rename from java/src/main/java/com/github/copilot/LlmInferenceAdapter.java rename to java/sdk/src/main/java/com/github/copilot/LlmInferenceAdapter.java diff --git a/java/src/main/java/com/github/copilot/LlmInferenceExchange.java b/java/sdk/src/main/java/com/github/copilot/LlmInferenceExchange.java similarity index 100% rename from java/src/main/java/com/github/copilot/LlmInferenceExchange.java rename to java/sdk/src/main/java/com/github/copilot/LlmInferenceExchange.java diff --git a/java/src/main/java/com/github/copilot/LlmWebSocketResponseBridge.java b/java/sdk/src/main/java/com/github/copilot/LlmWebSocketResponseBridge.java similarity index 100% rename from java/src/main/java/com/github/copilot/LlmWebSocketResponseBridge.java rename to java/sdk/src/main/java/com/github/copilot/LlmWebSocketResponseBridge.java diff --git a/java/src/main/java/com/github/copilot/LoggingHelpers.java b/java/sdk/src/main/java/com/github/copilot/LoggingHelpers.java similarity index 100% rename from java/src/main/java/com/github/copilot/LoggingHelpers.java rename to java/sdk/src/main/java/com/github/copilot/LoggingHelpers.java diff --git a/java/src/main/java/com/github/copilot/RpcHandlerDispatcher.java b/java/sdk/src/main/java/com/github/copilot/RpcHandlerDispatcher.java similarity index 100% rename from java/src/main/java/com/github/copilot/RpcHandlerDispatcher.java rename to java/sdk/src/main/java/com/github/copilot/RpcHandlerDispatcher.java diff --git a/java/src/main/java/com/github/copilot/SdkProtocolVersion.java b/java/sdk/src/main/java/com/github/copilot/SdkProtocolVersion.java similarity index 100% rename from java/src/main/java/com/github/copilot/SdkProtocolVersion.java rename to java/sdk/src/main/java/com/github/copilot/SdkProtocolVersion.java diff --git a/java/src/main/java/com/github/copilot/SessionRequestBuilder.java b/java/sdk/src/main/java/com/github/copilot/SessionRequestBuilder.java similarity index 87% rename from java/src/main/java/com/github/copilot/SessionRequestBuilder.java rename to java/sdk/src/main/java/com/github/copilot/SessionRequestBuilder.java index 57d9a46a2..4254c04ec 100644 --- a/java/src/main/java/com/github/copilot/SessionRequestBuilder.java +++ b/java/sdk/src/main/java/com/github/copilot/SessionRequestBuilder.java @@ -7,9 +7,11 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.function.Function; +import com.github.copilot.rpc.CopilotClientMode; import com.github.copilot.rpc.CreateSessionRequest; import com.github.copilot.rpc.ProviderConfig; import com.github.copilot.rpc.NamedProviderConfig; @@ -97,6 +99,10 @@ static ExtractedTransforms extractTransformCallbacks(SystemMessageConfig systemM * @return the built request object */ static CreateSessionRequest buildCreateRequest(SessionConfig config, String sessionId) { + return buildCreateRequest(config, sessionId, CopilotClientMode.COPILOT_CLI); + } + + static CreateSessionRequest buildCreateRequest(SessionConfig config, String sessionId, CopilotClientMode mode) { var request = new CreateSessionRequest(); // Always request permission callbacks to enable deny-by-default behavior request.setRequestPermission(true); @@ -104,6 +110,7 @@ static CreateSessionRequest buildCreateRequest(SessionConfig config, String sess request.setEnvValueMode("direct"); request.setSessionId(sessionId); if (config == null) { + request.setCustomAgentsLocalOnly(resolveCustomAgentsLocalOnly(null, mode)); return request; } @@ -123,7 +130,10 @@ static CreateSessionRequest buildCreateRequest(SessionConfig config, String sess request.setModels(config.getModels()); config.getEnableSessionTelemetry().ifPresent(request::setEnableSessionTelemetry); config.getEnableCitations().ifPresent(request::setEnableCitations); + config.getEnableFileChangeTracking().ifPresent(request::setEnableFileChangeTracking); request.setSessionLimits(config.getSessionLimits()); + experimentalModeForMode(mode, config.getEnableExperimentalMode().orElse(null)) + .ifPresent(request::setIsExperimentalMode); if (config.getOnUserInputRequest() != null) { request.setRequestUserInput(true); } @@ -131,6 +141,7 @@ static CreateSessionRequest buildCreateRequest(SessionConfig config, String sess request.setHooks(true); } request.setWorkingDirectory(config.getWorkingDirectory()); + request.setAdditionalDirectories(config.getAdditionalDirectories()); if (config.isStreaming()) { request.setStreaming(true); } @@ -138,6 +149,8 @@ static CreateSessionRequest buildCreateRequest(SessionConfig config, String sess request.setMcpServers(config.getMcpServers()); request.setMcpOAuthTokenStorage(config.getMcpOAuthTokenStorage()); request.setCustomAgents(config.getCustomAgents()); + request.setCustomAgentsLocalOnly( + resolveCustomAgentsLocalOnly(config.getCustomAgentsLocalOnly().orElse(null), mode)); request.setDefaultAgent(config.getDefaultAgent()); request.setAgent(config.getAgent()); request.setInfiniteSessions(config.getInfiniteSessions()); @@ -148,6 +161,7 @@ static CreateSessionRequest buildCreateRequest(SessionConfig config, String sess request.setToolSearch(config.getToolSearch()); request.setMemory(config.getMemory()); request.setDisabledSkills(config.getDisabledSkills()); + request.setDisabledMcpServers(config.getDisabledMcpServers()); request.setConfigDirectory(config.getConfigDirectory()); config.getEnableConfigDiscovery().ifPresent(request::setEnableConfigDiscovery); config.getSkipEmbeddingRetrieval().ifPresent(request::setSkipEmbeddingRetrieval); @@ -176,6 +190,7 @@ static CreateSessionRequest buildCreateRequest(SessionConfig config, String sess if (config.isEnableMcpApps()) { request.setRequestMcpApps(true); } + request.setGitHubMcpToolConfig(config.getGitHubMcpToolConfig()); if (config.getOnExitPlanMode() != null) { request.setRequestExitPlanMode(true); } @@ -187,6 +202,7 @@ static CreateSessionRequest buildCreateRequest(SessionConfig config, String sess request.setCloud(config.getCloud()); request.setExpAssignments(config.getExpAssignments()); config.getEnableManagedSettings().ifPresent(request::setEnableManagedSettings); + request.setManagedSettings(config.getManagedSettings()); return request; } @@ -217,6 +233,11 @@ static CreateSessionRequest buildCreateRequest(SessionConfig config) { * @return the built request object */ static ResumeSessionRequest buildResumeRequest(String sessionId, ResumeSessionConfig config) { + return buildResumeRequest(sessionId, config, CopilotClientMode.COPILOT_CLI); + } + + static ResumeSessionRequest buildResumeRequest(String sessionId, ResumeSessionConfig config, + CopilotClientMode mode) { var request = new ResumeSessionRequest(); request.setSessionId(sessionId); // Always request permission callbacks to enable deny-by-default behavior @@ -225,6 +246,7 @@ static ResumeSessionRequest buildResumeRequest(String sessionId, ResumeSessionCo request.setEnvValueMode("direct"); if (config == null) { + request.setCustomAgentsLocalOnly(resolveCustomAgentsLocalOnly(null, mode)); return request; } @@ -244,7 +266,10 @@ static ResumeSessionRequest buildResumeRequest(String sessionId, ResumeSessionCo request.setModels(config.getModels()); config.getEnableSessionTelemetry().ifPresent(request::setEnableSessionTelemetry); config.getEnableCitations().ifPresent(request::setEnableCitations); + config.getEnableFileChangeTracking().ifPresent(request::setEnableFileChangeTracking); request.setSessionLimits(config.getSessionLimits()); + experimentalModeForMode(mode, config.getEnableExperimentalMode().orElse(null)) + .ifPresent(request::setIsExperimentalMode); if (config.getOnUserInputRequest() != null) { request.setRequestUserInput(true); } @@ -252,6 +277,7 @@ static ResumeSessionRequest buildResumeRequest(String sessionId, ResumeSessionCo request.setHooks(true); } request.setWorkingDirectory(config.getWorkingDirectory()); + request.setAdditionalDirectories(config.getAdditionalDirectories()); request.setConfigDirectory(config.getConfigDirectory()); config.getEnableConfigDiscovery().ifPresent(request::setEnableConfigDiscovery); config.getSkipEmbeddingRetrieval().ifPresent(request::setSkipEmbeddingRetrieval); @@ -276,6 +302,8 @@ static ResumeSessionRequest buildResumeRequest(String sessionId, ResumeSessionCo request.setMcpServers(config.getMcpServers()); request.setMcpOAuthTokenStorage(config.getMcpOAuthTokenStorage()); request.setCustomAgents(config.getCustomAgents()); + request.setCustomAgentsLocalOnly( + resolveCustomAgentsLocalOnly(config.getCustomAgentsLocalOnly().orElse(null), mode)); request.setDefaultAgent(config.getDefaultAgent()); request.setAgent(config.getAgent()); request.setSkillDirectories(config.getSkillDirectories()); @@ -285,6 +313,7 @@ static ResumeSessionRequest buildResumeRequest(String sessionId, ResumeSessionCo request.setToolSearch(config.getToolSearch()); request.setMemory(config.getMemory()); request.setDisabledSkills(config.getDisabledSkills()); + request.setDisabledMcpServers(config.getDisabledMcpServers()); request.setInfiniteSessions(config.getInfiniteSessions()); request.setModelCapabilities(config.getModelCapabilities()); @@ -300,6 +329,7 @@ static ResumeSessionRequest buildResumeRequest(String sessionId, ResumeSessionCo if (config.isEnableMcpApps()) { request.setRequestMcpApps(true); } + request.setGitHubMcpToolConfig(config.getGitHubMcpToolConfig()); if (config.getOnExitPlanMode() != null) { request.setRequestExitPlanMode(true); } @@ -310,10 +340,25 @@ static ResumeSessionRequest buildResumeRequest(String sessionId, ResumeSessionCo request.setRemoteSession(config.getRemoteSession()); request.setExpAssignments(config.getExpAssignments()); config.getEnableManagedSettings().ifPresent(request::setEnableManagedSettings); + request.setManagedSettings(config.getManagedSettings()); return request; } + private static Boolean resolveCustomAgentsLocalOnly(Boolean customAgentsLocalOnly, CopilotClientMode mode) { + if (customAgentsLocalOnly != null) { + return customAgentsLocalOnly; + } + return mode == CopilotClientMode.EMPTY ? true : null; + } + + private static Optional experimentalModeForMode(CopilotClientMode mode, Boolean supplied) { + if (mode == CopilotClientMode.EMPTY) { + return Optional.of(supplied != null ? supplied : false); + } + return Optional.ofNullable(supplied); + } + /** * Configures a session with handlers from the given config. * @@ -333,6 +378,8 @@ static void configureSession(CopilotSession session, SessionConfig config) { if (config.getOnPermissionRequest() != null) { session.registerPermissionHandler(config.getOnPermissionRequest()); } + session.setManagedSettingsEnabled( + config.getEnableManagedSettings().orElse(false) || config.getManagedSettings() != null); if (config.getOnMcpAuthRequest() != null) { session.registerMcpAuthHandler(config.getOnMcpAuthRequest()); } @@ -383,6 +430,8 @@ static void configureSession(CopilotSession session, ResumeSessionConfig config) if (config.getOnPermissionRequest() != null) { session.registerPermissionHandler(config.getOnPermissionRequest()); } + session.setManagedSettingsEnabled( + config.getEnableManagedSettings().orElse(false) || config.getManagedSettings() != null); if (config.getOnMcpAuthRequest() != null) { session.registerMcpAuthHandler(config.getOnMcpAuthRequest()); } diff --git a/java/src/main/java/com/github/copilot/SystemMessageMode.java b/java/sdk/src/main/java/com/github/copilot/SystemMessageMode.java similarity index 100% rename from java/src/main/java/com/github/copilot/SystemMessageMode.java rename to java/sdk/src/main/java/com/github/copilot/SystemMessageMode.java diff --git a/java/sdk/src/main/java/com/github/copilot/ffi/FfiOutputStream.java b/java/sdk/src/main/java/com/github/copilot/ffi/FfiOutputStream.java new file mode 100644 index 000000000..4198f08f0 --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/ffi/FfiOutputStream.java @@ -0,0 +1,63 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.ffi; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; + +final class FfiOutputStream extends OutputStream { + + private final NativeBinding nativeBinding; + private final AtomicInteger connectionId; + private final AtomicBoolean closing; + private final ReentrantLock operationLock; + + FfiOutputStream(NativeBinding nativeBinding, AtomicInteger connectionId, AtomicBoolean closing, + ReentrantLock operationLock) { + this.nativeBinding = Objects.requireNonNull(nativeBinding, "nativeBinding must not be null"); + this.connectionId = Objects.requireNonNull(connectionId, "connectionId must not be null"); + this.closing = Objects.requireNonNull(closing, "closing must not be null"); + this.operationLock = Objects.requireNonNull(operationLock, "operationLock must not be null"); + } + + @Override + public void write(int b) throws IOException { + write(new byte[]{(byte) b}, 0, 1); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + Objects.requireNonNull(b, "buffer must not be null"); + if (off < 0 || len < 0 || off + len > b.length) { + throw new IndexOutOfBoundsException("Invalid off/len for buffer of length " + b.length); + } + if (len == 0) { + return; + } + + operationLock.lock(); + try { + if (closing.get()) { + throw new IOException("The in-process runtime connection is closed."); + } + int id = connectionId.get(); + if (id == 0) { + throw new IOException("The in-process runtime connection is closed."); + } + + byte[] payload = (off == 0 && len == b.length) ? b : Arrays.copyOfRange(b, off, off + len); + if (!nativeBinding.connectionWrite(id, payload, payload.length)) { + throw new IOException("Failed to write a frame to the in-process runtime connection."); + } + } finally { + operationLock.unlock(); + } + } +} diff --git a/java/sdk/src/main/java/com/github/copilot/ffi/FfiRuntimeHost.java b/java/sdk/src/main/java/com/github/copilot/ffi/FfiRuntimeHost.java new file mode 100644 index 000000000..5e7d2d461 --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/ffi/FfiRuntimeHost.java @@ -0,0 +1,347 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.ffi; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.copilot.rpc.CopilotClientMode; +import com.github.copilot.rpc.CopilotClientOptions; + +import com.sun.jna.Pointer; + +/** + * Manages the in-process FFI runtime lifecycle. + */ +public final class FfiRuntimeHost implements AutoCloseable { + + private static final Logger LOG = Logger.getLogger(FfiRuntimeHost.class.getName()); + private static final ObjectMapper MAPPER = new ObjectMapper(); + + private final NativeBinding nativeBinding; + private final QueueInputStream receiveStream; + private final AtomicBoolean closing = new AtomicBoolean(false); + private final AtomicBoolean disposed = new AtomicBoolean(false); + private final AtomicInteger serverId = new AtomicInteger(0); + private final AtomicInteger connectionId = new AtomicInteger(0); + private final AtomicInteger activeCallbacks = new AtomicInteger(0); + private final Object callbackDrainMonitor = new Object(); + private final ReentrantLock operationLock = new ReentrantLock(); + private final FfiOutputStream sendStream; + private final String libraryPath; + + private volatile OutboundCallback callbackRef; + + /** + * Creates an FFI runtime host using the resolved bundled native library. + * + * @throws IOException + * if the runtime library cannot be resolved + */ + public FfiRuntimeHost() throws IOException { + this(resolveLibraryPath(), null, new QueueInputStream()); + } + + FfiRuntimeHost(NativeBinding nativeBinding, String libraryPath) { + this(nativeBinding, libraryPath, new QueueInputStream()); + } + + FfiRuntimeHost(NativeBinding nativeBinding, String libraryPath, QueueInputStream receiveStream) { + this.nativeBinding = Objects.requireNonNull(nativeBinding, "nativeBinding must not be null"); + this.receiveStream = Objects.requireNonNull(receiveStream, "receiveStream must not be null"); + this.sendStream = new FfiOutputStream(this.nativeBinding, this.connectionId, this.closing, this.operationLock); + this.libraryPath = libraryPath; + } + + private FfiRuntimeHost(Path libraryPath, NativeBinding nativeBinding, QueueInputStream receiveStream) { + this(nativeBinding == null ? new JnaNativeBinding(libraryPath) : nativeBinding, libraryPath.toString(), + receiveStream); + } + + private static Path resolveLibraryPath() throws IOException { + return NativeRuntimeLoader.resolve(); + } + + /** + * Starts the in-process runtime and opens a connection. + * + * @param entrypointPath + * runtime entrypoint path passed in {@code argv_json} + * @param options + * client options used to construct {@code argv_json} and + * {@code env_json} + */ + public void start(String entrypointPath, CopilotClientOptions options) { + Objects.requireNonNull(entrypointPath, "entrypointPath must not be null"); + Objects.requireNonNull(options, "options must not be null"); + if (disposed.get()) { + throw new IllegalStateException("FfiRuntimeHost is already closed."); + } + if (serverId.get() != 0 || connectionId.get() != 0) { + throw new IllegalStateException("FfiRuntimeHost has already been started."); + } + + byte[] argvJson = buildArgvJson(entrypointPath, options); + byte[] envJson = buildEnvJson(options); + int hostHandle = runHostStartOnBlockingThread(argvJson, envJson); + if (hostHandle == 0) { + String lib = libraryPath != null ? libraryPath : ""; + throw new IllegalStateException( + "copilot_runtime_host_start failed (library '" + lib + "', entrypoint '" + entrypointPath + "')."); + } + + // Hold operationLock while publishing handles to serialize with close(). + // Recheck disposed in case close() ran while hostStart was blocking. + operationLock.lock(); + try { + if (disposed.get()) { + try { + nativeBinding.hostShutdown(hostHandle); + } catch (Throwable ignored) { + // Best effort + } + throw new IllegalStateException("FfiRuntimeHost was closed during startup."); + } + serverId.set(hostHandle); + + OutboundCallback callback = createOutboundCallback(); + callbackRef = callback; + int connHandle = nativeBinding.connectionOpen(hostHandle, callback, Pointer.NULL, null, 0, null, 0, null, + 0); + if (connHandle == 0) { + try { + nativeBinding.hostShutdown(hostHandle); + } catch (Throwable ignored) { + // Best effort + } + serverId.set(0); + callbackRef = null; + throw new IllegalStateException("copilot_runtime_connection_open failed."); + } + connectionId.set(connHandle); + LOG.fine(() -> "Started FFI runtime host. Library=" + libraryPath + ", serverId=" + hostHandle + + ", connectionId=" + connHandle); + } finally { + operationLock.unlock(); + } + } + + public InputStream getReceiveStream() { + return receiveStream; + } + + public OutputStream getSendStream() { + return sendStream; + } + + @Override + public void close() { + if (!disposed.compareAndSet(false, true)) { + return; + } + + closing.set(true); + + operationLock.lock(); + try { + int connHandle = connectionId.getAndSet(0); + if (connHandle != 0) { + try { + nativeBinding.connectionClose(connHandle); + } catch (Throwable t) { + LOG.log(Level.FINE, "Failed to close FFI connection", t); + } + } + } finally { + operationLock.unlock(); + } + + drainActiveCallbacks(); + + int hostHandle = serverId.getAndSet(0); + if (hostHandle != 0) { + try { + nativeBinding.hostShutdown(hostHandle); + } catch (Throwable t) { + LOG.log(Level.FINE, "Failed to shut down FFI host", t); + } + } + + try { + receiveStream.close(); + } catch (Throwable ignored) { + // never throw from close + } + + callbackRef = null; + } + + private void drainActiveCallbacks() { + while (activeCallbacks.get() > 0) { + synchronized (callbackDrainMonitor) { + if (activeCallbacks.get() == 0) { + return; + } + try { + callbackDrainMonitor.wait(10L); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + } + } + } + + private OutboundCallback createOutboundCallback() { + return (userData, data, len) -> { + if (closing.get()) { + return; + } + activeCallbacks.incrementAndGet(); + try { + int length = len.intValue(); + if (closing.get() || data == null || length <= 0) { + return; + } + byte[] bytes = data.getByteArray(0, length); + if (!closing.get()) { + receiveStream.enqueue(bytes); + } + } catch (Throwable t) { + LOG.log(Level.WARNING, "Exception in FFI outbound callback", t); + } finally { + if (activeCallbacks.decrementAndGet() == 0) { + synchronized (callbackDrainMonitor) { + callbackDrainMonitor.notifyAll(); + } + } + } + }; + } + + private int runHostStartOnBlockingThread(byte[] argvJson, byte[] envJson) { + ReaderThreadFactory readerThreadFactory = new ReaderThreadFactory(); + ExecutorService executor = Executors + .newSingleThreadExecutor(runnable -> readerThreadFactory.create(runnable, "copilot-ffi-host-start")); + try { + Future future = executor.submit(() -> nativeBinding.hostStart(argvJson, argvJson.length, envJson, + envJson == null ? 0 : envJson.length)); + return future.get(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IllegalStateException("Interrupted while starting in-process runtime host.", e); + } catch (ExecutionException e) { + Throwable cause = e.getCause(); + if (cause instanceof RuntimeException runtimeException) { + throw runtimeException; + } + throw new IllegalStateException("Failed to start in-process runtime host.", cause); + } finally { + executor.shutdownNow(); + try { + executor.awaitTermination(5, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + + private static byte[] buildArgvJson(String entrypointPath, CopilotClientOptions options) { + List argv = new ArrayList<>(); + if (entrypointPath.toLowerCase().endsWith(".js")) { + argv.add("node"); + } + argv.add(entrypointPath); + argv.add("--embedded-host"); + argv.add("--no-auto-update"); + + String logLevel = options.getLogLevel(); + if (logLevel != null && !logLevel.isBlank()) { + argv.add("--log-level"); + argv.add(logLevel); + } + + String gitHubToken = options.getGitHubToken(); + if (gitHubToken != null && !gitHubToken.isEmpty()) { + argv.add("--auth-token-env"); + argv.add("COPILOT_SDK_AUTH_TOKEN"); + } + + boolean useLoggedInUser = options.getUseLoggedInUser().orElse(gitHubToken == null || gitHubToken.isEmpty()); + if (!useLoggedInUser) { + argv.add("--no-auto-login"); + } + + if (options.getSessionIdleTimeoutSeconds().isPresent() + && options.getSessionIdleTimeoutSeconds().getAsInt() > 0) { + argv.add("--session-idle-timeout"); + argv.add(String.valueOf(options.getSessionIdleTimeoutSeconds().getAsInt())); + } + + if (options.isRemote()) { + argv.add("--remote"); + } + + String[] cliArgs = options.getCliArgs(); + if (cliArgs != null && cliArgs.length > 0) { + for (String arg : cliArgs) { + if (arg != null && !arg.isBlank()) { + argv.add(arg); + } + } + } + + return jsonBytes(argv); + } + + private static byte[] buildEnvJson(CopilotClientOptions options) { + Map env = new LinkedHashMap<>(); + + String token = options.getGitHubToken(); + if (token != null && !token.isEmpty()) { + env.put("COPILOT_SDK_AUTH_TOKEN", token); + } + String copilotHome = options.getCopilotHome(); + if (copilotHome != null && !copilotHome.isEmpty()) { + env.put("COPILOT_HOME", copilotHome); + } + if (options.getMode() == CopilotClientMode.EMPTY) { + env.put("COPILOT_DISABLE_KEYTAR", "1"); + } + + if (env.isEmpty()) { + return null; + } + return jsonBytes(env); + } + + private static byte[] jsonBytes(Object value) { + try { + return MAPPER.writeValueAsString(value).getBytes(StandardCharsets.UTF_8); + } catch (JsonProcessingException e) { + throw new IllegalStateException("Failed to serialize FFI JSON parameter.", e); + } + } +} diff --git a/java/sdk/src/main/java/com/github/copilot/ffi/JnaNativeBinding.java b/java/sdk/src/main/java/com/github/copilot/ffi/JnaNativeBinding.java new file mode 100644 index 000000000..ba3c3c40a --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/ffi/JnaNativeBinding.java @@ -0,0 +1,295 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.ffi; + +import com.sun.jna.Library; +import com.sun.jna.Native; +import com.sun.jna.Pointer; + +import java.nio.file.Path; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Logger; + +/** + * JNA-backed implementation of {@link NativeBinding}. + * + *

+ * Loads the {@code runtime.node} native library by absolute path and delegates + * each {@link NativeBinding} method to the corresponding + * {@code copilot_runtime_*} C ABI export. + * + *

Library-never-unloads pattern

+ *

+ * The loaded JNA library handle is held in a {@code static} field and is never + * released. Native worker threads spawned by the runtime outlive any individual + * {@code FfiRuntimeHost} instance; unloading the library while those threads + * are active would cause a crash. This mirrors the Rust runtime's own + * {@code OnceLock>>} pattern. + * + *

Duplicate-load guard

+ *

+ * Loading a library from a different absolute path in the same JVM + * process is rejected with {@link IllegalStateException}. Loading from the + * same path more than once is silently accepted. + * + *

Active-callback tracking

+ *

+ * The {@link #activeCallbacks} counter is incremented when the native runtime + * enters the outbound callback and decremented when the callback returns. + * Callers (e.g. {@code FfiRuntimeHost}) must drain this counter to zero before + * calling {@link #connectionClose} or {@link #hostShutdown}. + * + *

Callback lifetime

+ *

+ * The native runtime can invoke an outbound callback after connection close and + * host shutdown return. Each JNA callback wrapper is therefore retained for the + * lifetime of the JVM. After host shutdown, its Java delegate is detached so a + * late native invocation safely becomes a no-op without retaining the complete + * host object graph. + * + *

GraalVM Native Image

+ *

+ * JNA callback upcalls are not supported under GraalVM Native Image. InProcess + * transport is not available in native-image executables; use subprocess + * transport instead. + */ +final class JnaNativeBinding implements NativeBinding { + + private static final Logger LOG = Logger.getLogger(JnaNativeBinding.class.getName()); + + /** + * JNA inner interface mapping the five {@code copilot_runtime_*} C ABI exports. + */ + interface CopilotRuntimeLibrary extends Library { + /** Corresponds to {@code copilot_runtime_host_start}. */ + int copilot_runtime_host_start(byte[] argvJson, SizeT argvJsonLen, byte[] envJson, SizeT envJsonLen); + + /** + * Corresponds to {@code copilot_runtime_host_shutdown}. + * + *

+ * Returns {@code byte} (not Java {@code boolean}) because the Rust ABI exports + * a one-byte {@code bool}. JNA maps Java {@code boolean} as a 32-bit C + * {@code int}, which would read three extra bytes. + */ + byte copilot_runtime_host_shutdown(int serverId); + + /** Corresponds to {@code copilot_runtime_connection_open}. */ + int copilot_runtime_connection_open(int serverId, OutboundCallback callback, Pointer userData, byte[] extSource, + SizeT extSourceLen, byte[] extName, SizeT extNameLen, byte[] connToken, SizeT connTokenLen); + + /** + * Corresponds to {@code copilot_runtime_connection_write}. + * + * @see #copilot_runtime_host_shutdown for why this returns {@code byte} + */ + byte copilot_runtime_connection_write(int connectionId, byte[] data, SizeT dataLen); + + /** + * Corresponds to {@code copilot_runtime_connection_close}. + * + * @see #copilot_runtime_host_shutdown for why this returns {@code byte} + */ + byte copilot_runtime_connection_close(int connectionId); + } + + // ------------------------------------------------------------------------- + // Process-wide singleton — never unloaded + // ------------------------------------------------------------------------- + + private static final Object LOAD_LOCK = new Object(); + + /** Absolute path of the library that was first loaded into this JVM process. */ + private static volatile Path loadedPath; + + /** The loaded JNA library interface. Never released after first set. */ + private static volatile CopilotRuntimeLibrary loadedLib; + + /** + * Process-lifetime roots for JNA callback trampolines. Native code can invoke a + * callback after connection and host teardown return, so entries are never + * removed in production. + */ + private static final Set RETAINED_CALLBACKS = ConcurrentHashMap.newKeySet(); + + // ------------------------------------------------------------------------- + // Instance state + // ------------------------------------------------------------------------- + + /** + * The library interface used by this instance for all delegated calls. + * + *

+ * For the production path ({@link #JnaNativeBinding(Path)}), this is always the + * same object as {@link #loadedLib} (the static singleton). For the test path + * ({@link #JnaNativeBinding(CopilotRuntimeLibrary)}), this may be a stub or + * mock without modifying the static singleton. + */ + private final CopilotRuntimeLibrary lib; + + /** + * Count of callbacks currently executing on native threads. Must reach zero + * before {@link #connectionClose} or {@link #hostShutdown} is called. + */ + final AtomicInteger activeCallbacks = new AtomicInteger(0); + + /** + * Callback registrations keyed by connection handle. + *

+ * Registrations remain here through connection close because native callbacks + * can still arrive. Successful host shutdown detaches their Java delegates; the + * wrappers themselves remain rooted by {@link #RETAINED_CALLBACKS}. + */ + private final Map callbackRegistrations = new ConcurrentHashMap<>(); + + private static final class CallbackRegistration { + private final int serverId; + private final AtomicReference delegate; + private final AtomicInteger activeCallbacks; + private final OutboundCallback wrapper; + + private CallbackRegistration(int serverId, OutboundCallback delegate, AtomicInteger activeCallbacks) { + this.serverId = serverId; + this.delegate = new AtomicReference<>(delegate); + this.activeCallbacks = activeCallbacks; + this.wrapper = this::invoke; + } + + private void invoke(Pointer userData, Pointer data, SizeT len) { + activeCallbacks.incrementAndGet(); + try { + OutboundCallback callback = delegate.get(); + if (callback != null) { + callback.invoke(userData, data, len); + } + } finally { + activeCallbacks.decrementAndGet(); + } + } + + private void detach() { + delegate.set(null); + } + } + + // ------------------------------------------------------------------------- + // Constructors + // ------------------------------------------------------------------------- + + /** + * Loads (or re-uses) the native library at the given absolute path. + * + * @param libraryPath + * absolute path to the {@code runtime.node} native library + * @throws IllegalStateException + * if a different library path has already been loaded in + * this JVM process + */ + JnaNativeBinding(Path libraryPath) { + Path absPath = libraryPath.toAbsolutePath().normalize(); + synchronized (LOAD_LOCK) { + if (loadedLib == null) { + LOG.fine(() -> "Loading native library from: " + absPath); + try { + loadedLib = Native.load(absPath.toString(), CopilotRuntimeLibrary.class); + } catch (UnsatisfiedLinkError e) { + throw new IllegalStateException("Failed to load native library from '" + absPath + "'", e); + } + loadedPath = absPath; + LOG.fine(() -> "Native library loaded: " + absPath); + } else if (!absPath.equals(loadedPath)) { + throw new IllegalStateException("An in-process FFI runtime library is already loaded from '" + + loadedPath + "'; loading a different library from '" + absPath + + "' in the same process is not supported."); + } + } + this.lib = loadedLib; + } + + /** + * Testing constructor — accepts a pre-built {@link CopilotRuntimeLibrary} + * directly, bypassing disk I/O and the static singleton guard. + * + *

+ * This constructor is package-private and intended solely for unit tests. + * + * @param library + * a {@link CopilotRuntimeLibrary} stub or mock for testing + */ + JnaNativeBinding(CopilotRuntimeLibrary library) { + // Testing seam — skip the static singleton guard. + this.lib = library; + } + + // ------------------------------------------------------------------------- + // NativeBinding delegation + // ------------------------------------------------------------------------- + + @Override + public int hostStart(byte[] argvJson, int argvJsonLen, byte[] envJson, int envJsonLen) { + return lib.copilot_runtime_host_start(argvJson, new SizeT(argvJsonLen), envJson, new SizeT(envJsonLen)); + } + + @Override + public boolean hostShutdown(int serverId) { + boolean shutdown = lib.copilot_runtime_host_shutdown(serverId) != 0; + if (shutdown) { + callbackRegistrations.forEach((connectionId, registration) -> { + if (registration.serverId == serverId && callbackRegistrations.remove(connectionId, registration)) { + registration.detach(); + } + }); + } + return shutdown; + } + + @Override + public int connectionOpen(int serverId, OutboundCallback callback, Pointer userData, byte[] extSource, + int extSourceLen, byte[] extName, int extNameLen, byte[] connToken, int connTokenLen) { + CallbackRegistration registration = new CallbackRegistration(serverId, callback, activeCallbacks); + int connectionId = lib.copilot_runtime_connection_open(serverId, registration.wrapper, userData, extSource, + new SizeT(extSourceLen), extName, new SizeT(extNameLen), connToken, new SizeT(connTokenLen)); + if (connectionId != 0) { + RETAINED_CALLBACKS.add(registration.wrapper); + callbackRegistrations.put(connectionId, registration); + } + return connectionId; + } + + @Override + public boolean connectionWrite(int connectionId, byte[] data, int dataLen) { + return lib.copilot_runtime_connection_write(connectionId, data, new SizeT(dataLen)) != 0; + } + + @Override + public boolean connectionClose(int connectionId) { + return lib.copilot_runtime_connection_close(connectionId) != 0; + } + + // ------------------------------------------------------------------------- + // Testing support + // ------------------------------------------------------------------------- + + /** + * Resets the process-wide static state for unit tests. + * + *

+ * Must only be called from test code. Resets + * {@link #loadedPath} and {@link #loadedLib} so that a subsequent + * {@link #JnaNativeBinding(Path)} call can load a different library. In + * production, the library is never unloaded. + */ + static void resetForTesting() { + synchronized (LOAD_LOCK) { + loadedPath = null; + loadedLib = null; + RETAINED_CALLBACKS.clear(); + } + } +} diff --git a/java/sdk/src/main/java/com/github/copilot/ffi/NativeBinding.java b/java/sdk/src/main/java/com/github/copilot/ffi/NativeBinding.java new file mode 100644 index 000000000..3aa8ca9e4 --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/ffi/NativeBinding.java @@ -0,0 +1,131 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.ffi; + +import com.sun.jna.Pointer; + +/** + * Internal abstraction over the Copilot runtime C ABI. + * + *

+ * Defines the five {@code extern "C"} entry points exposed by the native + * {@code runtime.node} library. The JNA-backed implementation + * ({@link JnaNativeBinding}) delegates to these through JNA. A future FFM + * implementation may be substituted via the multi-release JAR mechanism without + * changing callers. + * + *

+ * All classes in {@code com.github.copilot.ffi} are internal; consumers must + * not reference them directly. + * + *

C ABI entry points

+ *
    + *
  • {@code copilot_runtime_host_start} — start the runtime host
  • + *
  • {@code copilot_runtime_host_shutdown} — shut down the runtime host
  • + *
  • {@code copilot_runtime_connection_open} — open a bidirectional + * connection
  • + *
  • {@code copilot_runtime_connection_write} — write a JSON-RPC frame to the + * runtime
  • + *
  • {@code copilot_runtime_connection_close} — close a connection
  • + *
+ * + *

Wire format

+ *

+ * All frames use LSP {@code Content-Length} header framing, identical to the + * stdio transport. No special encoding or decoding is needed at the FFI + * boundary. + */ +interface NativeBinding { + + /** + * Starts the runtime host. + * + *

+ * Blocks for up to ~30 s while the worker boots and connects back. Must not be + * called on an async/reactive executor thread. + * + * @param argvJson + * UTF-8 JSON array of strings: the entrypoint and required flags + * @param argvJsonLen + * byte length of {@code argvJson} + * @param envJson + * UTF-8 JSON object of environment overrides, or {@code null} when + * empty + * @param envJsonLen + * byte length of {@code envJson}, or {@code 0} when {@code envJson} + * is null + * @return server handle ({@code 0} on failure) + */ + int hostStart(byte[] argvJson, int argvJsonLen, byte[] envJson, int envJsonLen); + + /** + * Shuts down the runtime host. + * + * @param serverId + * non-zero server handle returned by {@link #hostStart} + * @return {@code true} on success + */ + boolean hostShutdown(int serverId); + + /** + * Opens a bidirectional connection and registers the outbound data callback. + * + *

+ * The {@code extSource}, {@code extName}, and {@code connToken} parameters are + * reserved extension points. All current SDK implementations pass + * {@code null}/0 for all three. + * + * @param serverId + * non-zero server handle returned by {@link #hostStart} + * @param callback + * JNA callback invoked by the runtime on native threads when + * outbound data is available; must be held as a strong reference by + * the caller + * @param userData + * opaque cookie passed back to {@code callback} unchanged; pass + * {@link Pointer#NULL} + * @param extSource + * reserved; pass {@code null} + * @param extSourceLen + * byte length of {@code extSource}; pass {@code 0} + * @param extName + * reserved; pass {@code null} + * @param extNameLen + * byte length of {@code extName}; pass {@code 0} + * @param connToken + * reserved; pass {@code null} + * @param connTokenLen + * byte length of {@code connToken}; pass {@code 0} + * @return connection handle ({@code 0} on failure) + */ + int connectionOpen(int serverId, OutboundCallback callback, Pointer userData, byte[] extSource, int extSourceLen, + byte[] extName, int extNameLen, byte[] connToken, int connTokenLen); + + /** + * Writes a JSON-RPC frame to the runtime. + * + *

+ * The native side copies the buffer synchronously before returning; the byte + * array does not need to survive past this call. + * + * @param connectionId + * non-zero connection handle returned by {@link #connectionOpen} + * @param data + * frame bytes + * @param dataLen + * byte length of {@code data} + * @return {@code true} on success + */ + boolean connectionWrite(int connectionId, byte[] data, int dataLen); + + /** + * Closes a connection. + * + * @param connectionId + * non-zero connection handle returned by {@link #connectionOpen} + * @return {@code true} on success + */ + boolean connectionClose(int connectionId); +} diff --git a/java/sdk/src/main/java/com/github/copilot/ffi/NativeRuntimeLoader.java b/java/sdk/src/main/java/com/github/copilot/ffi/NativeRuntimeLoader.java new file mode 100644 index 000000000..d2d06eb06 --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/ffi/NativeRuntimeLoader.java @@ -0,0 +1,504 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.ffi; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.channels.FileChannel; +import java.nio.file.AtomicMoveNotSupportedException; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; +import java.util.Properties; + +/** + * Locates the {@code runtime.node} native binary, extracts it to a versioned + * cache directory, and returns the filesystem path for JNA to load. + * + *

+ * Resolution order: + *

    + *
  1. {@code COPILOT_CLI_PATH} — checks for + * {@code runtime.node} alongside the configured CLI or in its npm + * {@code prebuilds/} directory before any classpath or platform + * work.
  2. + *
  3. Classpath resource + * {@code native//runtime.node} — extracted atomically to + * {@code ~/.copilot/runtime-cache////runtime.node}.
  4. + *
  5. PATH compatibility fallback — finds {@code copilot} on + * {@code PATH} and accepts only a flat sibling {@code runtime.node}. This + * fallback does not support normal npm or Homebrew installation layouts.
  6. + *
+ */ +public final class NativeRuntimeLoader { + + static final String RUNTIME_FILENAME = "runtime.node"; + static final String CLI_FILENAME = "copilot"; + static final String CLI_FILENAME_WINDOWS = "copilot.exe"; + static final String PLATFORM_PROPERTIES_FILENAME = "platform.properties"; + /** Environment variable that overrides where the runtime is loaded from. */ + public static final String COPILOT_CLI_PATH_ENV = "COPILOT_CLI_PATH"; + static final String VERSION_RESOURCE = "copilot-runtime.properties"; + + /** + * Abstraction for the atomic publish step, enabling deterministic failure + * injection in tests while preserving {@link StandardCopyOption#ATOMIC_MOVE} in + * production. + */ + @FunctionalInterface + interface AtomicPublisher { + /** + * Atomically publishes {@code temp} to {@code cached}. + * + * @param temp + * fully-written temporary file in the same directory as + * {@code cached} + * @param cached + * intended final location + * @throws IOException + * if the move fails + */ + void publish(Path temp, Path cached) throws IOException; + } + + /** + * Production publisher: {@link Files#move} with + * {@link StandardCopyOption#ATOMIC_MOVE}. + */ + static final AtomicPublisher DEFAULT_PUBLISHER = (temp, cached) -> { + try { + Files.move(temp, cached, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING); + } catch (AtomicMoveNotSupportedException ex) { + throw new IllegalStateException("Filesystem does not support atomic moves; cannot safely publish " + + RUNTIME_FILENAME + " to " + cached, ex); + } catch (FileAlreadyExistsException ex) { + // Another process won the race — accept the winner if it is a valid file. + try { + if (isValidCachedFile(cached)) { + return; + } + } catch (IOException ignored) { + // fall through to the error below + } + throw new IllegalStateException( + "Concurrent extraction race: target already exists but is not a valid file: " + cached, ex); + } + }; + + private NativeRuntimeLoader() { + } + + /** + * Resolves the filesystem path to the {@code runtime.node} binary. + * + *

+ * Follows the three-step resolution order documented on this class. The + * returned path is guaranteed to refer to a regular, non-empty file at the time + * of return. + * + * @return absolute path to the {@code runtime.node} binary + * @throws IOException + * if the binary cannot be located or extracted + * @throws IllegalStateException + * if required resources are missing or extraction fails + */ + public static Path resolve() throws IOException { + String cliPathEnv = System.getenv(COPILOT_CLI_PATH_ENV); + Path cliOverride = resolveFromCliPath(cliPathEnv); + if (cliOverride != null) { + return cliOverride; + } + + ClassLoader loader = NativeRuntimeLoader.class.getClassLoader(); + String classifier = PlatformDetector.detectClassifier(); + String version = readVersion(loader); + Path cacheBase = defaultCacheBase(); + return resolve(null, findRuntimeOnPath(), cacheBase, loader, classifier, version); + } + + /** + * Resolves the copilot CLI executable from the same location as the bundled + * {@code runtime.node}. The CLI is used as {@code argv[0]} in + * {@code copilot_runtime_host_start} — the Rust runtime spawns it as a child + * process. + * + *

+ * This method calls {@link #resolve()} to locate {@code runtime.node}, then + * looks for the {@code copilot} executable in the same directory. Both + * artifacts are extracted from the classifier JAR together. + * + * @return absolute path to the {@code copilot} CLI executable + * @throws IOException + * if the CLI executable cannot be located + */ + public static Path resolveEntrypoint() throws IOException { + String configuredCli = System.getenv(COPILOT_CLI_PATH_ENV); + return resolveEntrypoint(configuredCli, resolve()); + } + + static Path resolveEntrypoint(String configuredCli, Path runtimePath) throws IOException { + if (configuredCli != null && !configuredCli.isBlank()) { + Path configuredPath = Path.of(configuredCli).toAbsolutePath().normalize(); + if (resolveFromCliPath(configuredCli) != null && Files.isRegularFile(configuredPath) + && Files.size(configuredPath) > 0) { + return configuredPath; + } + } + + Path parent = runtimePath.getParent(); + String cliName = isWindows() ? CLI_FILENAME_WINDOWS : CLI_FILENAME; + Path cliPath = parent.resolve(cliName); + if (Files.isRegularFile(cliPath) && Files.size(cliPath) > 0) { + return cliPath; + } + throw new IOException("Copilot CLI executable not found at " + cliPath + + " — the classifier JAR must contain both runtime.node and the copilot binary"); + } + + /** + * Reads the SDK version from the filtered {@code copilot-runtime.properties} + * resource. + * + * @return the version string + * @throws IOException + * if the resource cannot be read + * @throws IllegalStateException + * if the resource is missing or the version property is blank + */ + static String readVersion(ClassLoader loader) throws IOException { + URL resource = loader.getResource(VERSION_RESOURCE); + if (resource == null) { + throw new IllegalStateException("Missing version resource: " + VERSION_RESOURCE + + " — ensure Maven resource filtering has run (mvn process-resources)"); + } + Properties props = new Properties(); + try (InputStream in = resource.openStream()) { + props.load(in); + } + String version = props.getProperty("version"); + if (version == null || version.isBlank()) { + throw new IllegalStateException("Blank or missing 'version' property in " + VERSION_RESOURCE + + " — check Maven resource filtering configuration"); + } + return version; + } + + private static String readNativePackageVersion(ClassLoader loader, String classifier) throws IOException { + String resourcePath = "native/" + classifier + "/" + PLATFORM_PROPERTIES_FILENAME; + URL resource = loader.getResource(resourcePath); + if (resource == null) { + throw new FileNotFoundException("Native runtime metadata not found on classpath: " + resourcePath + + " — add the matching classifier JAR to the classpath"); + } + + Properties props = new Properties(); + try (InputStream in = resource.openStream()) { + props.load(in); + } + String version = props.getProperty("version"); + if (version == null || version.isBlank()) { + throw new IllegalStateException("Blank or missing 'version' property in " + resourcePath); + } + return version; + } + + /** + * Resolves the runtime binary path using the given parameters. Package-private + * to allow injection of test doubles in unit tests. + */ + static Path resolve(String cliPathEnv, Path cacheBase, ClassLoader loader, String classifier, String version) + throws IOException { + return resolve(cliPathEnv, cacheBase, loader, classifier, version, null, DEFAULT_PUBLISHER); + } + + static Path resolve(String cliPathEnv, String bundledCliPath, Path cacheBase, ClassLoader loader, String classifier, + String version) throws IOException { + Path bundledCliDir = bundledCliPath == null ? null : Path.of(bundledCliPath).toAbsolutePath().getParent(); + return resolve(cliPathEnv, cacheBase, loader, classifier, version, bundledCliDir, DEFAULT_PUBLISHER); + } + + static Path resolve(String cliPathEnv, Path cacheBase, ClassLoader loader, String classifier, String version, + Path bundledCliDir) throws IOException { + return resolve(cliPathEnv, cacheBase, loader, classifier, version, bundledCliDir, DEFAULT_PUBLISHER); + } + + static Path resolve(String cliPathEnv, Path cacheBase, ClassLoader loader, String classifier, String version, + Path bundledCliDir, AtomicPublisher publisher) throws IOException { + Path cliOverride = resolveFromCliPath(cliPathEnv); + if (cliOverride != null) { + return cliOverride; + } + + return resolveFromClasspathOrBundledCli(cacheBase, loader, classifier, version, bundledCliDir, publisher); + } + + /** + * Checks for {@code runtime.node} alongside the configured CLI. + * + *

+ * Checks, in order, the flat bundled layout ({@code runtime.node} directly next + * to the CLI) and the npm package layout + * ({@code prebuilds//runtime.node} next to the CLI), matching the + * two layouts the {@code @github/copilot-} packages may ship. + */ + static Path resolveFromCliPath(String cliPathStr) throws IOException { + if (cliPathStr == null || cliPathStr.isBlank()) { + return null; + } + Path cliPath = Path.of(cliPathStr).toAbsolutePath().normalize(); + Path parent = cliPath.getParent(); + + Path flat = parent.resolve(RUNTIME_FILENAME); + if (Files.isRegularFile(flat) && Files.size(flat) > 0) { + return flat; + } + + Path prebuilt = parent.resolve("prebuilds").resolve(PlatformDetector.detectClassifier()) + .resolve(RUNTIME_FILENAME); + if (Files.isRegularFile(prebuilt) && Files.size(prebuilt) > 0) { + return prebuilt; + } + + return null; + } + + /** + * Extracts the classpath resource {@code native//runtime.node} to + * the versioned cache directory, using an atomic publish sequence to prevent + * readers from observing a partially-written file. Uses + * {@link #DEFAULT_PUBLISHER}. + * + * @param cacheBase + * root cache directory (e.g. {@code ~/.copilot/runtime-cache}) + * @param loader + * class loader used to open the classpath resource + * @param classifier + * platform classifier (e.g. {@code linux-x64}) + * @param version + * SDK version used as part of the cache key + * @return path to the extracted {@code runtime.node} binary + * @throws IOException + * if I/O or the atomic rename fails + * @throws IllegalStateException + * if the classpath resource is missing or empty, or if the + * filesystem does not support atomic moves + */ + static Path extractToCache(Path cacheBase, ClassLoader loader, String classifier, String version) + throws IOException { + return extractToCache(cacheBase, loader, classifier, version, DEFAULT_PUBLISHER); + } + + /** + * Extracts the classpath resource to the versioned cache directory with an + * injectable publisher. Package-private for unit tests. + * + * @param cacheBase + * root cache directory + * @param loader + * class loader used to open the classpath resource + * @param classifier + * platform classifier + * @param version + * SDK version used as part of the cache key + * @param publisher + * atomic publish implementation + * @return path to the extracted {@code runtime.node} binary + * @throws IOException + * if I/O or the atomic rename fails + * @throws IllegalStateException + * if the classpath resource is missing or empty + */ + static Path extractToCache(Path cacheBase, ClassLoader loader, String classifier, String version, + AtomicPublisher publisher) throws IOException { + String resourcePath = "native/" + classifier + "/" + RUNTIME_FILENAME; + String nativeVersion = readNativePackageVersion(loader, classifier); + Path cacheDir = cacheBase.resolve(version).resolve(nativeVersion).resolve(classifier); + Path cached = cacheDir.resolve(RUNTIME_FILENAME); + + // Step 1 — fast path: return an existing valid cache entry. + if (isValidCachedFile(cached)) { + extractCliToCache(cacheDir, loader, classifier, publisher); + return cached; + } + + // Step 2 — locate the classpath resource before creating any files. + URL resource = loader.getResource(resourcePath); + if (resource == null) { + throw new FileNotFoundException("Native runtime not found on classpath: " + resourcePath + + " — add the matching classifier JAR to the classpath"); + } + + // Step 3 — ensure the cache directory exists. + Files.createDirectories(cacheDir); + + // Step 4 — write to a unique sibling temp file, then publish atomically. + Path temp = Files.createTempFile(cacheDir, "runtime-tmp-", ".node"); + try { + copyResourceToTemp(resource, resourcePath, temp); + publisher.publish(temp, cached); + } finally { + tryDelete(temp); + } + + // Step 5 — also extract the copilot CLI executable alongside runtime.node. + extractCliToCache(cacheDir, loader, classifier, publisher); + + return cached; + } + + /** + * Extracts the copilot CLI executable from the classpath to the same cache + * directory as {@code runtime.node}. Idempotent — skips extraction if already + * present and valid. + */ + static void extractCliToCache(Path cacheDir, ClassLoader loader, String classifier, AtomicPublisher publisher) + throws IOException { + String cliName = isWindows() ? CLI_FILENAME_WINDOWS : CLI_FILENAME; + String cliResourcePath = "native/" + classifier + "/" + cliName; + Path cachedCli = cacheDir.resolve(cliName); + + if (isValidCachedCli(cachedCli)) { + return; + } + + URL cliResource = loader.getResource(cliResourcePath); + if (cliResource == null) { + // CLI not on classpath — this is allowed for the COPILOT_CLI_PATH fallback + // path but will fail later in resolveEntrypoint() if InProcess is selected. + return; + } + + Files.createDirectories(cacheDir); + Path temp = Files.createTempFile(cacheDir, "cli-tmp-", ""); + try { + copyResourceToTemp(cliResource, cliResourcePath, temp); + makeExecutable(temp); + publisher.publish(temp, cachedCli); + } finally { + tryDelete(temp); + } + + if (!isValidCachedCli(cachedCli)) { + throw new IOException("Published Copilot CLI is not a non-empty executable file: " + cachedCli); + } + } + + /** + * Tries source 2 (classpath extraction) first and falls back to source 3 + * (bundled-CLI sibling) only when the classpath resource is absent. + */ + private static Path resolveFromClasspathOrBundledCli(Path cacheBase, ClassLoader loader, String classifier, + String version, Path bundledCliDir, AtomicPublisher publisher) throws IOException { + // Source 2: classpath resource. + try { + return extractToCache(cacheBase, loader, classifier, version, publisher); + } catch (FileNotFoundException ex) { + // Source 3: runtime.node alongside the bundled CLI binary. + if (bundledCliDir != null) { + Path candidate = bundledCliDir.resolve(RUNTIME_FILENAME); + try { + if (isValidCachedFile(candidate)) { + return candidate; + } + } catch (IOException ignored) { + // fall through and rethrow the original classpath error + } + } + throw ex; + } + } + + private static boolean isValidCachedFile(Path path) throws IOException { + if (!Files.isRegularFile(path)) { + return false; + } + return Files.size(path) > 0; + } + + private static boolean isValidCachedCli(Path path) throws IOException { + return isValidCachedFile(path) && (isWindows() || Files.isExecutable(path)); + } + + private static void makeExecutable(Path path) throws IOException { + if (isWindows()) { + return; + } + final boolean executableSet; + try { + executableSet = path.toFile().setExecutable(true, false); + } catch (SecurityException ex) { + throw new IOException("Failed to make Copilot CLI executable: " + path, ex); + } + if (!executableSet || !Files.isExecutable(path)) { + throw new IOException("Failed to make Copilot CLI executable: " + path); + } + } + + private static void copyResourceToTemp(URL resource, String resourcePath, Path temp) throws IOException { + try (InputStream in = resource.openStream()) { + long bytesWritten = Files.copy(in, temp, StandardCopyOption.REPLACE_EXISTING); + if (bytesWritten == 0) { + throw new IllegalStateException("Classpath resource is empty: " + resourcePath); + } + } + // Flush OS buffers to durable storage before the atomic rename. + try (FileChannel channel = FileChannel.open(temp, StandardOpenOption.WRITE)) { + channel.force(true); + } + } + + /** + * Finds the Copilot CLI executable on the {@code PATH}. + * + * @return the absolute CLI path, or {@code null} if none was found + */ + public static String findRuntimeOnPath() { + String pathValue = System.getenv("PATH"); + if (pathValue == null || pathValue.isBlank()) { + return null; + } + + String[] executableNames = isWindows() + ? new String[]{"copilot.exe", "copilot.cmd", "copilot.bat", "copilot"} + : new String[]{"copilot"}; + for (String directory : pathValue.split(java.io.File.pathSeparator)) { + if (directory.isBlank()) { + continue; + } + for (String executableName : executableNames) { + Path candidate = Path.of(directory, executableName); + if (Files.isRegularFile(candidate)) { + try { + return candidate.toRealPath().toString(); + } catch (IOException ignored) { + return candidate.toAbsolutePath().normalize().toString(); + } + } + } + } + return null; + } + + private static boolean isWindows() { + return System.getProperty("os.name", "").toLowerCase(java.util.Locale.ROOT).contains("win"); + } + + private static void tryDelete(Path path) { + try { + Files.deleteIfExists(path); + } catch (IOException ignored) { + // Best-effort cleanup; an orphaned temp file in the cache directory is benign. + } + } + + private static Path defaultCacheBase() { + return Path.of(System.getProperty("user.home"), ".copilot", "runtime-cache"); + } + +} diff --git a/java/sdk/src/main/java/com/github/copilot/ffi/OutboundCallback.java b/java/sdk/src/main/java/com/github/copilot/ffi/OutboundCallback.java new file mode 100644 index 000000000..597feea9e --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/ffi/OutboundCallback.java @@ -0,0 +1,46 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.ffi; + +import com.sun.jna.Callback; +import com.sun.jna.Pointer; + +/** + * JNA callback interface for the runtime-to-Java outbound data path. + * + *

+ * The native runtime invokes this callback on a native thread when data is + * ready to be delivered to the Java side. JNA automatically attaches the native + * thread to the JVM before dispatching the callback. + * + *

+ * Buffer lifetime: The {@code data} pointer is only valid for + * the duration of the callback invocation. Implementations must copy the bytes + * out (e.g. {@code data.getByteArray(0, len)}) before returning. + * + *

+ * GC protection: Instances must be held as strong-reference + * fields for as long as native code may invoke the callback. If the instance is + * garbage-collected, the function pointer becomes dangling and the JVM will + * crash. + */ +@FunctionalInterface +interface OutboundCallback extends Callback { + + /** + * Invoked by the native runtime when outbound data is available. + * + * @param userData + * opaque cookie passed through unchanged from + * {@code copilot_runtime_connection_open}; always + * {@code Pointer.NULL} in this SDK + * @param data + * pointer to the outbound byte buffer; valid only for the duration + * of this invocation + * @param len + * byte length of the buffer pointed to by {@code data} + */ + void invoke(Pointer userData, Pointer data, SizeT len); +} diff --git a/java/sdk/src/main/java/com/github/copilot/ffi/PlatformDetector.java b/java/sdk/src/main/java/com/github/copilot/ffi/PlatformDetector.java new file mode 100644 index 000000000..466cf794b --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/ffi/PlatformDetector.java @@ -0,0 +1,303 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.ffi; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +/** + * Detects the current platform and resolves the runtime classifier. + */ +public final class PlatformDetector { + private static final int ELF_HEADER_PROBE_BYTES = 2048; + private static final int ELF_MAGIC_0 = 0x7F; + private static final int ELF_MAGIC_1 = 'E'; + private static final int ELF_MAGIC_2 = 'L'; + private static final int ELF_MAGIC_3 = 'F'; + private static final int ELF_CLASS_32 = 1; + private static final int ELF_CLASS_64 = 2; + private static final int ELF_DATA_LITTLE_ENDIAN = 1; + private static final int ELF_DATA_BIG_ENDIAN = 2; + private static final int ELF32_PROGRAM_HEADER_SIZE = 32; + private static final int ELF64_PROGRAM_HEADER_SIZE = 56; + private static final int PT_INTERP = 3; + + private static final Set SUPPORTED_CLASSIFIERS = Set.of("linux-x64", "linux-arm64", "linuxmusl-x64", + "linuxmusl-arm64", "darwin-x64", "darwin-arm64", "win32-x64", "win32-arm64"); + + private static final Map CLASSIFIER_BY_KEY = Map.ofEntries( + Map.entry(new ClassifierKey("linux", "x64", LinuxLibc.GLIBC), "linux-x64"), + Map.entry(new ClassifierKey("linux", "arm64", LinuxLibc.GLIBC), "linux-arm64"), + Map.entry(new ClassifierKey("linux", "x64", LinuxLibc.MUSL), "linuxmusl-x64"), + Map.entry(new ClassifierKey("linux", "arm64", LinuxLibc.MUSL), "linuxmusl-arm64"), + Map.entry(new ClassifierKey("linux", "x64", LinuxLibc.UNKNOWN), "linux-x64"), + Map.entry(new ClassifierKey("linux", "arm64", LinuxLibc.UNKNOWN), "linux-arm64"), + Map.entry(new ClassifierKey("darwin", "x64", LinuxLibc.UNKNOWN), "darwin-x64"), + Map.entry(new ClassifierKey("darwin", "arm64", LinuxLibc.UNKNOWN), "darwin-arm64"), + Map.entry(new ClassifierKey("win32", "x64", LinuxLibc.UNKNOWN), "win32-x64"), + Map.entry(new ClassifierKey("win32", "arm64", LinuxLibc.UNKNOWN), "win32-arm64")); + + private PlatformDetector() { + } + + /** + * Linux C runtime classification. + */ + public enum LinuxLibc { + /** GNU libc runtime. */ + GLIBC, + + /** musl libc runtime. */ + MUSL, + + /** Unknown or undetectable runtime. */ + UNKNOWN + } + + /** + * Detects the normalized operating system identifier. + * + * @return {@code darwin}, {@code linux}, or {@code win32} + */ + public static String detectOs() { + return detectOs(System.getProperty("os.name", "")); + } + + /** + * Detects the normalized architecture identifier. + * + * @return {@code x64} or {@code arm64} + */ + public static String detectArch() { + return detectArch(System.getProperty("os.arch", "")); + } + + /** + * Detects the Linux libc variant using {@code /proc/self/exe} PT_INTERP. + * + * @return Linux libc classification; {@code UNKNOWN} on non-Linux or parse + * failures + */ + public static LinuxLibc detectLinuxLibc() { + if (!"linux".equals(detectOs())) { + return LinuxLibc.UNKNOWN; + } + return detectLinuxLibc(Path.of("/proc/self/exe")); + } + + /** + * Detects the runtime classifier for the current platform. + * + * @return platform classifier string + */ + public static String detectClassifier() { + return detectClassifier(detectOs(), detectArch(), detectLinuxLibc()); + } + + static String detectOs(String osName) { + String normalized = osName.toLowerCase(Locale.ROOT); + if (normalized.contains("mac") || normalized.contains("darwin")) { + return "darwin"; + } + if (normalized.contains("win")) { + return "win32"; + } + if (normalized.contains("linux")) { + return "linux"; + } + throw new IllegalStateException("Unsupported os.name: " + osName); + } + + static String detectArch(String osArch) { + String normalized = osArch.toLowerCase(Locale.ROOT).replace('-', '_'); + if (normalized.equals("amd64") || normalized.equals("x86_64") || normalized.equals("x64")) { + return "x64"; + } + if (normalized.equals("aarch64") || normalized.equals("arm64")) { + return "arm64"; + } + throw new IllegalStateException("Unsupported os.arch: " + osArch); + } + + static LinuxLibc detectLinuxLibc(Path executablePath) { + try { + return detectLinuxLibc(readPrefix(executablePath, ELF_HEADER_PROBE_BYTES)); + } catch (IOException ex) { + return LinuxLibc.UNKNOWN; + } + } + + static LinuxLibc detectLinuxLibc(byte[] elfPrefix) throws IOException { + String interpreter = readElfPtInterp(elfPrefix); + if (interpreter.contains("/ld-musl-")) { + return LinuxLibc.MUSL; + } + if (interpreter.contains("/ld-linux-")) { + return LinuxLibc.GLIBC; + } + return LinuxLibc.UNKNOWN; + } + + static String detectClassifier(String os, String arch, LinuxLibc linuxLibc) { + LinuxLibc classifierLibc = "linux".equals(os) ? linuxLibc : LinuxLibc.UNKNOWN; + String classifier = CLASSIFIER_BY_KEY.get(new ClassifierKey(os, arch, classifierLibc)); + if (classifier == null || !SUPPORTED_CLASSIFIERS.contains(classifier)) { + throw new IllegalStateException( + "Unsupported platform tuple: os=" + os + ", arch=" + arch + ", libc=" + classifierLibc); + } + return classifier; + } + + static Set supportedClassifiers() { + return SUPPORTED_CLASSIFIERS; + } + + private static String readElfPtInterp(byte[] probe) throws IOException { + int size = probe.length; + if (size < 64) { + throw new IOException("ELF probe too small: " + size + " bytes"); + } + if ((probe[0] & 0xFF) != ELF_MAGIC_0 || (probe[1] & 0xFF) != ELF_MAGIC_1 || (probe[2] & 0xFF) != ELF_MAGIC_2 + || (probe[3] & 0xFF) != ELF_MAGIC_3) { + throw new IOException("Not an ELF executable"); + } + + int elfClass = probe[4] & 0xFF; + int elfData = probe[5] & 0xFF; + if (elfData != ELF_DATA_LITTLE_ENDIAN && elfData != ELF_DATA_BIG_ENDIAN) { + throw new IOException("Unsupported ELF data encoding: " + elfData); + } + boolean littleEndian = elfData == ELF_DATA_LITTLE_ENDIAN; + + long phoff; + int phentsize; + int phnum; + int minimumPhentsize; + if (elfClass == ELF_CLASS_64) { + phoff = readUInt64(probe, 32, littleEndian); + phentsize = readUInt16(probe, 54, littleEndian); + phnum = readUInt16(probe, 56, littleEndian); + minimumPhentsize = ELF64_PROGRAM_HEADER_SIZE; + } else if (elfClass == ELF_CLASS_32) { + phoff = readUInt32(probe, 28, littleEndian); + phentsize = readUInt16(probe, 42, littleEndian); + phnum = readUInt16(probe, 44, littleEndian); + minimumPhentsize = ELF32_PROGRAM_HEADER_SIZE; + } else { + throw new IOException("Unsupported ELF class: " + elfClass); + } + + if (phoff < 0 || phoff >= size) { + throw new IOException("Program header table offset outside probe window: " + phoff); + } + if (phentsize < minimumPhentsize || phnum <= 0) { + throw new IOException("Invalid ELF program header metadata: phentsize=" + phentsize + ", phnum=" + phnum); + } + + for (int i = 0; i < phnum; i++) { + long baseLong = phoff + ((long) i * phentsize); + if (baseLong < 0 || baseLong > Integer.MAX_VALUE) { + break; + } + int base = (int) baseLong; + if (base + phentsize > size) { + break; + } + + long pType = readUInt32(probe, base, littleEndian); + if (pType != PT_INTERP) { + continue; + } + + long pOffset; + long pFileSize; + if (elfClass == ELF_CLASS_64) { + pOffset = readUInt64(probe, base + 8, littleEndian); + pFileSize = readUInt64(probe, base + 32, littleEndian); + } else { + pOffset = readUInt32(probe, base + 4, littleEndian); + pFileSize = readUInt32(probe, base + 16, littleEndian); + } + + if (pOffset < 0 || pFileSize <= 0 || pOffset > Integer.MAX_VALUE || pFileSize > Integer.MAX_VALUE) { + throw new IOException("Invalid PT_INTERP bounds"); + } + + int start = (int) pOffset; + int end = start + (int) pFileSize; + if (end > size) { + throw new IOException("PT_INTERP extends past probe window; increase probe size"); + } + + int nulIndex = start; + while (nulIndex < end && probe[nulIndex] != 0) { + nulIndex++; + } + if (nulIndex == start) { + throw new IOException("Empty PT_INTERP segment"); + } + return new String(probe, start, nulIndex - start, StandardCharsets.UTF_8); + } + + throw new IOException("ELF PT_INTERP segment not found"); + } + + private static byte[] readPrefix(Path path, int maxBytes) throws IOException { + byte[] buffer = new byte[maxBytes]; + int total = 0; + try (InputStream in = Files.newInputStream(path)) { + while (total < maxBytes) { + int read = in.read(buffer, total, maxBytes - total); + if (read < 0) { + break; + } + total += read; + } + } + byte[] resized = new byte[total]; + System.arraycopy(buffer, 0, resized, 0, total); + return resized; + } + + private static int readUInt16(byte[] data, int offset, boolean littleEndian) { + int b0 = data[offset] & 0xFF; + int b1 = data[offset + 1] & 0xFF; + return littleEndian ? (b0 | (b1 << 8)) : ((b0 << 8) | b1); + } + + private static long readUInt32(byte[] data, int offset, boolean littleEndian) { + long b0 = data[offset] & 0xFFL; + long b1 = data[offset + 1] & 0xFFL; + long b2 = data[offset + 2] & 0xFFL; + long b3 = data[offset + 3] & 0xFFL; + if (littleEndian) { + return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24); + } + return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3; + } + + private static long readUInt64(byte[] data, int offset, boolean littleEndian) { + long result = 0L; + if (littleEndian) { + for (int i = 7; i >= 0; i--) { + result = (result << 8) | (data[offset + i] & 0xFFL); + } + return result; + } + for (int i = 0; i < 8; i++) { + result = (result << 8) | (data[offset + i] & 0xFFL); + } + return result; + } + + private record ClassifierKey(String os, String arch, LinuxLibc libc) { + } +} diff --git a/java/sdk/src/main/java/com/github/copilot/ffi/QueueInputStream.java b/java/sdk/src/main/java/com/github/copilot/ffi/QueueInputStream.java new file mode 100644 index 000000000..977182d5f --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/ffi/QueueInputStream.java @@ -0,0 +1,119 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.ffi; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Objects; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * {@link InputStream} backed by a {@link BlockingQueue} of byte-array chunks. + * + *

+ * Used by the in-process FFI transport to bridge native callback frames into + * the JSON-RPC reader. + */ +public class QueueInputStream extends InputStream { + + private static final byte[] EOF_SENTINEL = new byte[0]; + + private final BlockingQueue queue; + private final AtomicBoolean closed = new AtomicBoolean(false); + + private byte[] currentChunk; + private int currentOffset; + private boolean eof; + + /** + * Creates a queue-backed input stream with an unbounded queue. + */ + public QueueInputStream() { + this(new LinkedBlockingQueue<>()); + } + + /** + * Testing constructor that injects a queue implementation. + * + * @param queue + * backing queue + */ + QueueInputStream(BlockingQueue queue) { + this.queue = Objects.requireNonNull(queue, "queue must not be null"); + } + + void enqueue(byte[] bytes) { + if (bytes == null || bytes.length == 0 || closed.get()) { + return; + } + queue.offer(bytes); + } + + @Override + public int read() throws IOException { + byte[] one = new byte[1]; + int read = read(one, 0, 1); + if (read == -1) { + return -1; + } + return one[0] & 0xFF; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + Objects.requireNonNull(b, "buffer must not be null"); + if (off < 0 || len < 0 || off + len > b.length) { + throw new IndexOutOfBoundsException("Invalid off/len for buffer of length " + b.length); + } + if (len == 0) { + return 0; + } + if (eof) { + return -1; + } + + while (currentChunk == null || currentOffset >= currentChunk.length) { + byte[] next; + try { + next = queue.take(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IOException("Interrupted while waiting for callback data", e); + } + if (next == EOF_SENTINEL) { + eof = true; + return -1; + } + if (next.length == 0) { + continue; + } + currentChunk = next; + currentOffset = 0; + } + + int available = currentChunk.length - currentOffset; + int toCopy = Math.min(available, len); + System.arraycopy(currentChunk, currentOffset, b, off, toCopy); + currentOffset += toCopy; + return toCopy; + } + + @Override + public int available() { + if (currentChunk == null || currentOffset >= currentChunk.length) { + return 0; + } + return currentChunk.length - currentOffset; + } + + @Override + public void close() { + if (closed.compareAndSet(false, true)) { + queue.offer(EOF_SENTINEL); + } + } +} diff --git a/java/sdk/src/main/java/com/github/copilot/ffi/ReaderThreadFactory.java b/java/sdk/src/main/java/com/github/copilot/ffi/ReaderThreadFactory.java new file mode 100644 index 000000000..b0824fa9a --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/ffi/ReaderThreadFactory.java @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.ffi; + +/** + * Creates reader threads for FFI queue consumption. + * + *

+ * Baseline (JDK 17) implementation creates a daemon platform thread. The JDK 25 + * multi-release overlay switches this to a virtual thread with the same + * package-private API. + */ +final class ReaderThreadFactory { + + Thread create(Runnable task, String name) { + Thread thread = new Thread(task, name); + thread.setDaemon(true); + return thread; + } +} diff --git a/java/sdk/src/main/java/com/github/copilot/ffi/SizeT.java b/java/sdk/src/main/java/com/github/copilot/ffi/SizeT.java new file mode 100644 index 000000000..5bec1c327 --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/ffi/SizeT.java @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.ffi; + +import com.sun.jna.IntegerType; +import com.sun.jna.Native; + +/** + * JNA type mapping for the C {@code size_t} type. + * + *

+ * {@code size_t} is pointer-sized: 8 bytes on 64-bit platforms, 4 bytes on + * 32-bit. Using Java {@code int} (always 4 bytes) would silently truncate on + * 64-bit, and using {@link com.sun.jna.NativeLong} would be wrong on Windows + * x64 where C {@code long} is 4 bytes but {@code size_t} is 8 bytes. + * + *

+ * This class uses {@link Native#SIZE_T_SIZE} so JNA marshals the correct width + * on every platform. + */ +public final class SizeT extends IntegerType { + + /** Zero-valued instance; required by JNA for return-type instantiation. */ + public SizeT() { + this(0); + } + + /** + * Creates a {@code size_t} with the given value. + * + * @param value + * the numeric value (unsigned, but stored as signed long) + */ + public SizeT(long value) { + super(Native.SIZE_T_SIZE, value, true); + } +} diff --git a/java/src/main/java/com/github/copilot/package-info.java b/java/sdk/src/main/java/com/github/copilot/package-info.java similarity index 100% rename from java/src/main/java/com/github/copilot/package-info.java rename to java/sdk/src/main/java/com/github/copilot/package-info.java diff --git a/java/src/main/java/com/github/copilot/rpc/AgentInfo.java b/java/sdk/src/main/java/com/github/copilot/rpc/AgentInfo.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/AgentInfo.java rename to java/sdk/src/main/java/com/github/copilot/rpc/AgentInfo.java diff --git a/java/src/main/java/com/github/copilot/rpc/AgentMode.java b/java/sdk/src/main/java/com/github/copilot/rpc/AgentMode.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/AgentMode.java rename to java/sdk/src/main/java/com/github/copilot/rpc/AgentMode.java diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/AgentStopHandler.java b/java/sdk/src/main/java/com/github/copilot/rpc/AgentStopHandler.java new file mode 100644 index 000000000..7f1577605 --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/rpc/AgentStopHandler.java @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.rpc; + +import java.util.concurrent.CompletableFuture; + +/** + * Handler for agent-stop hooks. + * + * @since 1.0.9 + */ +@FunctionalInterface +public interface AgentStopHandler { + + /** + * Handles an agent-stop hook invocation. + * + * @param input + * the hook input + * @param invocation + * context information about the invocation + * @return a future that resolves with the hook output, or {@code null} to let + * the agent stop + */ + CompletableFuture handle(AgentStopHookInput input, HookInvocation invocation); +} diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/AgentStopHookInput.java b/java/sdk/src/main/java/com/github/copilot/rpc/AgentStopHookInput.java new file mode 100644 index 000000000..fceea8b72 --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/rpc/AgentStopHookInput.java @@ -0,0 +1,161 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Input for an agent-stop hook. + * + * @since 1.0.9 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class AgentStopHookInput { + + @JsonProperty("sessionId") + private String sessionId; + + @JsonProperty("timestamp") + private long timestamp; + + @JsonProperty("cwd") + private String cwd; + + @JsonProperty("stopReason") + private String stopReason; + + @JsonProperty("transcriptPath") + private String transcriptPath; + + @JsonProperty("stop_hook_active") + private Boolean stopHookActive; + + /** + * Gets the runtime session ID of the session that triggered the hook. + * + * @return the session ID + */ + public String getSessionId() { + return sessionId; + } + + /** + * Sets the runtime session ID of the session that triggered the hook. + * + * @param sessionId + * the session ID + * @return this instance for method chaining + */ + public AgentStopHookInput setSessionId(String sessionId) { + this.sessionId = sessionId; + return this; + } + + /** + * Gets the timestamp of the hook invocation. + * + * @return the timestamp in milliseconds + */ + public long getTimestamp() { + return timestamp; + } + + /** + * Sets the timestamp of the hook invocation. + * + * @param timestamp + * the timestamp in milliseconds + * @return this instance for method chaining + */ + public AgentStopHookInput setTimestamp(long timestamp) { + this.timestamp = timestamp; + return this; + } + + /** + * Gets the current working directory. + * + * @return the working directory path + */ + public String getCwd() { + return cwd; + } + + /** + * Sets the current working directory. + * + * @param cwd + * the working directory path + * @return this instance for method chaining + */ + public AgentStopHookInput setCwd(String cwd) { + this.cwd = cwd; + return this; + } + + /** + * Gets the reason the agent stopped. + * + * @return the stop reason + */ + public String getStopReason() { + return stopReason; + } + + /** + * Sets the reason the agent stopped. + * + * @param stopReason + * the stop reason + * @return this instance for method chaining + */ + public AgentStopHookInput setStopReason(String stopReason) { + this.stopReason = stopReason; + return this; + } + + /** + * Gets the path to the on-disk session transcript. + * + * @return the transcript path + */ + public String getTranscriptPath() { + return transcriptPath; + } + + /** + * Sets the path to the on-disk session transcript. + * + * @param transcriptPath + * the transcript path + * @return this instance for method chaining + */ + public AgentStopHookInput setTranscriptPath(String transcriptPath) { + this.transcriptPath = transcriptPath; + return this; + } + + /** + * Gets whether this stop follows a previous block decision. + * + * @return {@code true} when the stop hook is already active + */ + public Boolean getStopHookActive() { + return stopHookActive; + } + + /** + * Sets whether this stop follows a previous block decision. + * + * @param stopHookActive + * whether the stop hook is already active + * @return this instance for method chaining + */ + public AgentStopHookInput setStopHookActive(Boolean stopHookActive) { + this.stopHookActive = stopHookActive; + return this; + } +} diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/AgentStopHookOutput.java b/java/sdk/src/main/java/com/github/copilot/rpc/AgentStopHookOutput.java new file mode 100644 index 000000000..293bb3138 --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/rpc/AgentStopHookOutput.java @@ -0,0 +1,65 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.rpc; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Output for an agent-stop hook. + * + * @since 1.0.9 + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public class AgentStopHookOutput { + + @JsonProperty("decision") + private String decision; + + @JsonProperty("reason") + private String reason; + + /** + * Gets the stop decision. + * + * @return {@code "block"} to keep the agent running, or {@code null} + */ + public String getDecision() { + return decision; + } + + /** + * Sets the stop decision. + * + * @param decision + * {@code "block"} to keep the agent running + * @return this instance for method chaining + */ + public AgentStopHookOutput setDecision(String decision) { + this.decision = decision; + return this; + } + + /** + * Gets the follow-up instruction supplied when the stop is blocked. + * + * @return the follow-up instruction + */ + public String getReason() { + return reason; + } + + /** + * Sets the follow-up instruction supplied when the stop is blocked. + * + * @param reason + * the follow-up instruction + * @return this instance for method chaining + */ + public AgentStopHookOutput setReason(String reason) { + this.reason = reason; + return this; + } +} diff --git a/java/src/main/java/com/github/copilot/rpc/Attachment.java b/java/sdk/src/main/java/com/github/copilot/rpc/Attachment.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/Attachment.java rename to java/sdk/src/main/java/com/github/copilot/rpc/Attachment.java diff --git a/java/src/main/java/com/github/copilot/rpc/AutoModeSwitchHandler.java b/java/sdk/src/main/java/com/github/copilot/rpc/AutoModeSwitchHandler.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/AutoModeSwitchHandler.java rename to java/sdk/src/main/java/com/github/copilot/rpc/AutoModeSwitchHandler.java diff --git a/java/src/main/java/com/github/copilot/rpc/AutoModeSwitchInvocation.java b/java/sdk/src/main/java/com/github/copilot/rpc/AutoModeSwitchInvocation.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/AutoModeSwitchInvocation.java rename to java/sdk/src/main/java/com/github/copilot/rpc/AutoModeSwitchInvocation.java diff --git a/java/src/main/java/com/github/copilot/rpc/AutoModeSwitchRequest.java b/java/sdk/src/main/java/com/github/copilot/rpc/AutoModeSwitchRequest.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/AutoModeSwitchRequest.java rename to java/sdk/src/main/java/com/github/copilot/rpc/AutoModeSwitchRequest.java diff --git a/java/src/main/java/com/github/copilot/rpc/AutoModeSwitchResponse.java b/java/sdk/src/main/java/com/github/copilot/rpc/AutoModeSwitchResponse.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/AutoModeSwitchResponse.java rename to java/sdk/src/main/java/com/github/copilot/rpc/AutoModeSwitchResponse.java diff --git a/java/src/main/java/com/github/copilot/rpc/AzureOptions.java b/java/sdk/src/main/java/com/github/copilot/rpc/AzureOptions.java similarity index 85% rename from java/src/main/java/com/github/copilot/rpc/AzureOptions.java rename to java/sdk/src/main/java/com/github/copilot/rpc/AzureOptions.java index 7adbc5656..cd25e845e 100644 --- a/java/src/main/java/com/github/copilot/rpc/AzureOptions.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/AzureOptions.java @@ -12,6 +12,7 @@ *

* When using a BYOK (Bring Your Own Key) setup with Azure OpenAI, this class * allows you to specify Azure-specific settings such as the API version to use. + * When no API version is set, the runtime uses the GA versionless v1 route. * *

Example Usage

* @@ -32,7 +33,8 @@ public class AzureOptions { /** * Gets the Azure OpenAI API version. * - * @return the API version string + * @return the API version string, or {@code null} to use the GA versionless v1 + * route */ public String getApiVersion() { return apiVersion; @@ -41,7 +43,8 @@ public String getApiVersion() { /** * Sets the Azure OpenAI API version to use. *

- * Examples: {@code "2024-02-01"}, {@code "2023-12-01-preview"} + * Examples: {@code "2024-02-01"}, {@code "2023-12-01-preview"} When this option + * is not set, the runtime uses the GA versionless v1 route. * * @param apiVersion * the API version string diff --git a/java/src/main/java/com/github/copilot/rpc/BearerTokenProvider.java b/java/sdk/src/main/java/com/github/copilot/rpc/BearerTokenProvider.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/BearerTokenProvider.java rename to java/sdk/src/main/java/com/github/copilot/rpc/BearerTokenProvider.java diff --git a/java/src/main/java/com/github/copilot/rpc/BlobAttachment.java b/java/sdk/src/main/java/com/github/copilot/rpc/BlobAttachment.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/BlobAttachment.java rename to java/sdk/src/main/java/com/github/copilot/rpc/BlobAttachment.java diff --git a/java/src/main/java/com/github/copilot/rpc/BuiltInTools.java b/java/sdk/src/main/java/com/github/copilot/rpc/BuiltInTools.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/BuiltInTools.java rename to java/sdk/src/main/java/com/github/copilot/rpc/BuiltInTools.java diff --git a/java/src/main/java/com/github/copilot/rpc/CapiSessionOptions.java b/java/sdk/src/main/java/com/github/copilot/rpc/CapiSessionOptions.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/CapiSessionOptions.java rename to java/sdk/src/main/java/com/github/copilot/rpc/CapiSessionOptions.java diff --git a/java/src/main/java/com/github/copilot/rpc/CloudSessionOptions.java b/java/sdk/src/main/java/com/github/copilot/rpc/CloudSessionOptions.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/CloudSessionOptions.java rename to java/sdk/src/main/java/com/github/copilot/rpc/CloudSessionOptions.java diff --git a/java/src/main/java/com/github/copilot/rpc/CloudSessionRepository.java b/java/sdk/src/main/java/com/github/copilot/rpc/CloudSessionRepository.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/CloudSessionRepository.java rename to java/sdk/src/main/java/com/github/copilot/rpc/CloudSessionRepository.java diff --git a/java/src/main/java/com/github/copilot/rpc/CommandContext.java b/java/sdk/src/main/java/com/github/copilot/rpc/CommandContext.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/CommandContext.java rename to java/sdk/src/main/java/com/github/copilot/rpc/CommandContext.java diff --git a/java/src/main/java/com/github/copilot/rpc/CommandDefinition.java b/java/sdk/src/main/java/com/github/copilot/rpc/CommandDefinition.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/CommandDefinition.java rename to java/sdk/src/main/java/com/github/copilot/rpc/CommandDefinition.java diff --git a/java/src/main/java/com/github/copilot/rpc/CommandHandler.java b/java/sdk/src/main/java/com/github/copilot/rpc/CommandHandler.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/CommandHandler.java rename to java/sdk/src/main/java/com/github/copilot/rpc/CommandHandler.java diff --git a/java/src/main/java/com/github/copilot/rpc/CommandWireDefinition.java b/java/sdk/src/main/java/com/github/copilot/rpc/CommandWireDefinition.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/CommandWireDefinition.java rename to java/sdk/src/main/java/com/github/copilot/rpc/CommandWireDefinition.java diff --git a/java/src/main/java/com/github/copilot/rpc/CopilotClientMode.java b/java/sdk/src/main/java/com/github/copilot/rpc/CopilotClientMode.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/CopilotClientMode.java rename to java/sdk/src/main/java/com/github/copilot/rpc/CopilotClientMode.java diff --git a/java/src/main/java/com/github/copilot/rpc/CopilotClientOptions.java b/java/sdk/src/main/java/com/github/copilot/rpc/CopilotClientOptions.java similarity index 89% rename from java/src/main/java/com/github/copilot/rpc/CopilotClientOptions.java rename to java/sdk/src/main/java/com/github/copilot/rpc/CopilotClientOptions.java index 0d4494d73..d3515509b 100644 --- a/java/src/main/java/com/github/copilot/rpc/CopilotClientOptions.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/CopilotClientOptions.java @@ -4,11 +4,15 @@ package com.github.copilot.rpc; +import java.nio.file.Path; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; +import java.util.OptionalInt; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.function.Function; @@ -19,8 +23,6 @@ import com.github.copilot.CopilotExperimental; import com.github.copilot.CopilotRequestHandler; import com.github.copilot.generated.rpc.GitHubTelemetryNotification; -import java.util.Optional; -import java.util.OptionalInt; /** * Configuration options for creating a @@ -48,9 +50,11 @@ public class CopilotClientOptions { @Deprecated private boolean autoRestart; private boolean autoStart = true; + private List builtinPluginDirectories; private String[] cliArgs; private String cliPath; private String cliUrl; + private RuntimeConnection connection; private String copilotHome; private String cwd; private Map environment; @@ -119,6 +123,40 @@ public CopilotClientOptions setAutoStart(boolean autoStart) { return this; } + /** + * Gets the trusted plugin directories bundled by the host. + * + * @return a copy of the configured absolute paths, or {@code null} + */ + public List getBuiltinPluginDirectories() { + return builtinPluginDirectories != null ? new ArrayList<>(builtinPluginDirectories) : null; + } + + /** + * Sets trusted plugin directories bundled by the host. Every path must be + * absolute. When non-empty, the complete set is registered during startup + * before sessions can be created. + * + * @param paths + * absolute plugin directory paths, or {@code null}/empty to disable + * @return this options instance for method chaining + */ + public CopilotClientOptions setBuiltinPluginDirectories(List paths) { + if (paths == null || paths.isEmpty()) { + this.builtinPluginDirectories = null; + return this; + } + for (Path path : paths) { + Objects.requireNonNull(path, "builtin plugin directory path must not be null"); + if (!path.isAbsolute()) { + throw new IllegalArgumentException( + "BuiltinPluginDirectories must contain only absolute paths: " + path); + } + } + this.builtinPluginDirectories = new ArrayList<>(paths); + return this; + } + /** * Gets the extra CLI arguments. *

@@ -204,6 +242,41 @@ public CopilotClientOptions setCliUrl(String cliUrl) { return this; } + /** + * Gets the connection that selects how the client reaches the Copilot runtime. + * + * @return the connection, or {@code null} to infer the transport from + * {@link #isUseStdio()}, {@link #getCliUrl()} and {@link #getCliPath()} + */ + @JsonIgnore + @CopilotExperimental + public RuntimeConnection getConnection() { + return connection; + } + + /** + * Sets the connection that selects how the client reaches the Copilot runtime. + *

+ * When set, the connection takes precedence over the transport-selecting + * options {@link #setUseStdio(boolean)}, {@link #setCliUrl(String)}, + * {@link #setCliPath(String)}, {@link #setPort(int)} and + * {@link #setTcpConnectionToken(String)}; combining a connection with + * conflicting values for any of those options makes the client constructor + * throw {@link IllegalArgumentException}. Values that match what the connection + * implies are accepted, so the same options instance can be reused across + * multiple client constructions. + * + * @param connection + * the connection, or {@code null} to infer the transport from the + * individual transport options + * @return this options instance for method chaining + */ + @CopilotExperimental + public CopilotClientOptions setConnection(RuntimeConnection connection) { + this.connection = connection; + return this; + } + /** * Gets the base directory for Copilot data (session state, config, etc.). * @@ -248,13 +321,11 @@ public String getCwd() { * Sets the working directory for the CLI process. * * @param cwd - * the working directory path (must not be {@code null} or empty) + * the working directory path, or {@code null} to clear * @return this options instance for method chaining - * @throws IllegalArgumentException - * if {@code cwd} is {@code null} or empty */ public CopilotClientOptions setCwd(String cwd) { - this.cwd = Objects.requireNonNull(cwd, "cwd must not be null"); + this.cwd = cwd; return this; } @@ -751,9 +822,13 @@ public CopilotClientOptions clone() { CopilotClientOptions copy = new CopilotClientOptions(); copy.autoRestart = this.autoRestart; copy.autoStart = this.autoStart; + copy.builtinPluginDirectories = this.builtinPluginDirectories != null + ? new ArrayList<>(this.builtinPluginDirectories) + : null; copy.cliArgs = this.cliArgs != null ? this.cliArgs.clone() : null; copy.cliPath = this.cliPath; copy.cliUrl = this.cliUrl; + copy.connection = this.connection; copy.copilotHome = this.copilotHome; copy.cwd = this.cwd; copy.environment = this.environment != null ? new java.util.HashMap<>(this.environment) : null; diff --git a/java/src/main/java/com/github/copilot/rpc/CopilotExpAssignmentResponse.java b/java/sdk/src/main/java/com/github/copilot/rpc/CopilotExpAssignmentResponse.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/CopilotExpAssignmentResponse.java rename to java/sdk/src/main/java/com/github/copilot/rpc/CopilotExpAssignmentResponse.java diff --git a/java/src/main/java/com/github/copilot/rpc/CreateSessionRequest.java b/java/sdk/src/main/java/com/github/copilot/rpc/CreateSessionRequest.java similarity index 89% rename from java/src/main/java/com/github/copilot/rpc/CreateSessionRequest.java rename to java/sdk/src/main/java/com/github/copilot/rpc/CreateSessionRequest.java index 9a9f4f152..2eab977db 100644 --- a/java/src/main/java/com/github/copilot/rpc/CreateSessionRequest.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/CreateSessionRequest.java @@ -80,6 +80,9 @@ public final class CreateSessionRequest { @JsonProperty("enableCitations") private Boolean enableCitations; + @JsonProperty("enableFileChangeTracking") + private Boolean enableFileChangeTracking; + @JsonProperty("sessionLimits") private SessionLimitsConfig sessionLimits; @@ -95,6 +98,9 @@ public final class CreateSessionRequest { @JsonProperty("workingDirectory") private String workingDirectory; + @JsonProperty("additionalDirectories") + private List additionalDirectories; + @JsonProperty("streaming") private Boolean streaming; @@ -116,6 +122,9 @@ public final class CreateSessionRequest { @JsonProperty("customAgents") private List customAgents; + @JsonProperty("customAgentsLocalOnly") + private Boolean customAgentsLocalOnly; + @JsonProperty("defaultAgent") private DefaultAgentConfig defaultAgent; @@ -146,6 +155,9 @@ public final class CreateSessionRequest { @JsonProperty("disabledSkills") private List disabledSkills; + @JsonProperty("disabledMcpServers") + private List disabledMcpServers; + @JsonProperty("configDir") private String configDirectory; @@ -193,6 +205,13 @@ public final class CreateSessionRequest { @JsonProperty("requestMcpApps") private Boolean requestMcpApps; + @JsonProperty("githubMcpToolConfig") + private GitHubMcpToolConfig githubMcpToolConfig; + + @JsonProperty("isExperimentalMode") + @JsonInclude(JsonInclude.Include.NON_NULL) + private Boolean isExperimentalMode; + @JsonProperty("requestExitPlanMode") private Boolean requestExitPlanMode; @@ -218,6 +237,10 @@ public final class CreateSessionRequest { @JsonInclude(JsonInclude.Include.NON_NULL) private Boolean enableManagedSettings; + @JsonProperty("managedSettings") + @JsonInclude(JsonInclude.Include.NON_NULL) + private ManagedSettings managedSettings; + /** Gets the model name. @return the model */ public String getModel() { return model; @@ -414,6 +437,21 @@ public void setEnableCitations(boolean enableCitations) { this.enableCitations = enableCitations; } + /** Gets the file change tracking flag. @return the flag */ + public Boolean getEnableFileChangeTracking() { + return enableFileChangeTracking; + } + + /** + * Sets the file change tracking flag. + * + * @param enableFileChangeTracking + * the flag + */ + public void setEnableFileChangeTracking(boolean enableFileChangeTracking) { + this.enableFileChangeTracking = enableFileChangeTracking; + } + /** Gets the session limits. @return the session limits */ public SessionLimitsConfig getSessionLimits() { return sessionLimits; @@ -492,6 +530,21 @@ public void setWorkingDirectory(String workingDirectory) { this.workingDirectory = workingDirectory; } + /** Gets additional directories. @return the additional directories */ + public List getAdditionalDirectories() { + return additionalDirectories; + } + + /** + * Sets additional directories. + * + * @param additionalDirectories + * the additional directories + */ + public void setAdditionalDirectories(List additionalDirectories) { + this.additionalDirectories = additionalDirectories; + } + /** Gets streaming flag. @return the flag */ public Boolean getStreaming() { return streaming; @@ -552,6 +605,19 @@ public void setCustomAgents(List customAgents) { this.customAgents = customAgents; } + /** Gets whether custom agents are local only. @return the flag */ + public Boolean getCustomAgentsLocalOnly() { + return customAgentsLocalOnly; + } + + /** + * Sets whether custom agents are local only. @param customAgentsLocalOnly the + * flag + */ + public void setCustomAgentsLocalOnly(Boolean customAgentsLocalOnly) { + this.customAgentsLocalOnly = customAgentsLocalOnly; + } + /** Gets the default agent config. @return the default agent config */ public DefaultAgentConfig getDefaultAgent() { return defaultAgent; @@ -656,6 +722,18 @@ public void setDisabledSkills(List disabledSkills) { this.disabledSkills = disabledSkills; } + /** Gets disabled MCP server names. @return the server names */ + public List getDisabledMcpServers() { + return disabledMcpServers == null ? null : Collections.unmodifiableList(disabledMcpServers); + } + + /** + * Sets disabled MCP server names. @param disabledMcpServers the server names + */ + public void setDisabledMcpServers(List disabledMcpServers) { + this.disabledMcpServers = disabledMcpServers; + } + /** Gets config directory. @return the config directory path */ public String getConfigDirectory() { return configDirectory; @@ -902,6 +980,40 @@ public void clearRequestMcpApps() { this.requestMcpApps = null; } + /** Gets the GitHub MCP tool configuration. @return the configuration */ + public GitHubMcpToolConfig getGitHubMcpToolConfig() { + return githubMcpToolConfig; + } + + /** Sets the GitHub MCP tool configuration. @param config the value */ + public void setGitHubMcpToolConfig(GitHubMcpToolConfig config) { + this.githubMcpToolConfig = config; + } + + /** + * Gets the isExperimentalMode flag. + * + * @return the flag + */ + public Boolean getIsExperimentalMode() { + return isExperimentalMode; + } + + /** + * Sets the isExperimentalMode flag. + * + * @param isExperimentalMode + * the flag + */ + public void setIsExperimentalMode(boolean isExperimentalMode) { + this.isExperimentalMode = isExperimentalMode; + } + + /** Clears the isExperimentalMode setting, reverting to the default behavior. */ + public void clearIsExperimentalMode() { + this.isExperimentalMode = null; + } + /** Gets the requestExitPlanMode flag. @return the flag */ public Boolean getRequestExitPlanMode() { return requestExitPlanMode; @@ -1005,4 +1117,17 @@ public void setEnableManagedSettings(boolean enableManagedSettings) { public void clearEnableManagedSettings() { this.enableManagedSettings = null; } + + /** @return host-injected managed settings, or {@code null} when unset */ + public ManagedSettings getManagedSettings() { + return managedSettings; + } + + /** + * @param managedSettings + * host-injected managed settings + */ + public void setManagedSettings(ManagedSettings managedSettings) { + this.managedSettings = managedSettings; + } } diff --git a/java/src/main/java/com/github/copilot/rpc/CreateSessionResponse.java b/java/sdk/src/main/java/com/github/copilot/rpc/CreateSessionResponse.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/CreateSessionResponse.java rename to java/sdk/src/main/java/com/github/copilot/rpc/CreateSessionResponse.java diff --git a/java/src/main/java/com/github/copilot/rpc/CustomAgentConfig.java b/java/sdk/src/main/java/com/github/copilot/rpc/CustomAgentConfig.java similarity index 98% rename from java/src/main/java/com/github/copilot/rpc/CustomAgentConfig.java rename to java/sdk/src/main/java/com/github/copilot/rpc/CustomAgentConfig.java index 3604a1ef5..62de19b6a 100644 --- a/java/src/main/java/com/github/copilot/rpc/CustomAgentConfig.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/CustomAgentConfig.java @@ -298,8 +298,8 @@ public String getReasoningEffort() { /** * Sets the reasoning effort level for this agent's model. *

- * When omitted, no per-agent override is sent and the backend chooses its - * default. The parent session effort is not inherited. + * When omitted, the runtime resolves model configuration, then inherits the + * parent effort only if this agent uses the same model. * * @param reasoningEffort * the reasoning effort level diff --git a/java/src/main/java/com/github/copilot/rpc/DefaultAgentConfig.java b/java/sdk/src/main/java/com/github/copilot/rpc/DefaultAgentConfig.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/DefaultAgentConfig.java rename to java/sdk/src/main/java/com/github/copilot/rpc/DefaultAgentConfig.java diff --git a/java/src/main/java/com/github/copilot/rpc/DeleteSessionResponse.java b/java/sdk/src/main/java/com/github/copilot/rpc/DeleteSessionResponse.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/DeleteSessionResponse.java rename to java/sdk/src/main/java/com/github/copilot/rpc/DeleteSessionResponse.java diff --git a/java/src/main/java/com/github/copilot/rpc/ElicitationContext.java b/java/sdk/src/main/java/com/github/copilot/rpc/ElicitationContext.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/ElicitationContext.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ElicitationContext.java diff --git a/java/src/main/java/com/github/copilot/rpc/ElicitationHandler.java b/java/sdk/src/main/java/com/github/copilot/rpc/ElicitationHandler.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/ElicitationHandler.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ElicitationHandler.java diff --git a/java/src/main/java/com/github/copilot/rpc/ElicitationParams.java b/java/sdk/src/main/java/com/github/copilot/rpc/ElicitationParams.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/ElicitationParams.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ElicitationParams.java diff --git a/java/src/main/java/com/github/copilot/rpc/ElicitationResult.java b/java/sdk/src/main/java/com/github/copilot/rpc/ElicitationResult.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/ElicitationResult.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ElicitationResult.java diff --git a/java/src/main/java/com/github/copilot/rpc/ElicitationResultAction.java b/java/sdk/src/main/java/com/github/copilot/rpc/ElicitationResultAction.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/ElicitationResultAction.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ElicitationResultAction.java diff --git a/java/src/main/java/com/github/copilot/rpc/ElicitationSchema.java b/java/sdk/src/main/java/com/github/copilot/rpc/ElicitationSchema.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/ElicitationSchema.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ElicitationSchema.java diff --git a/java/src/main/java/com/github/copilot/rpc/ExitPlanModeHandler.java b/java/sdk/src/main/java/com/github/copilot/rpc/ExitPlanModeHandler.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/ExitPlanModeHandler.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ExitPlanModeHandler.java diff --git a/java/src/main/java/com/github/copilot/rpc/ExitPlanModeInvocation.java b/java/sdk/src/main/java/com/github/copilot/rpc/ExitPlanModeInvocation.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/ExitPlanModeInvocation.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ExitPlanModeInvocation.java diff --git a/java/src/main/java/com/github/copilot/rpc/ExitPlanModeRequest.java b/java/sdk/src/main/java/com/github/copilot/rpc/ExitPlanModeRequest.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/ExitPlanModeRequest.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ExitPlanModeRequest.java diff --git a/java/src/main/java/com/github/copilot/rpc/ExitPlanModeResult.java b/java/sdk/src/main/java/com/github/copilot/rpc/ExitPlanModeResult.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/ExitPlanModeResult.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ExitPlanModeResult.java diff --git a/java/src/main/java/com/github/copilot/rpc/ExpConfigEntry.java b/java/sdk/src/main/java/com/github/copilot/rpc/ExpConfigEntry.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/ExpConfigEntry.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ExpConfigEntry.java diff --git a/java/src/main/java/com/github/copilot/rpc/GetAuthStatusResponse.java b/java/sdk/src/main/java/com/github/copilot/rpc/GetAuthStatusResponse.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/GetAuthStatusResponse.java rename to java/sdk/src/main/java/com/github/copilot/rpc/GetAuthStatusResponse.java diff --git a/java/src/main/java/com/github/copilot/rpc/GetForegroundSessionResponse.java b/java/sdk/src/main/java/com/github/copilot/rpc/GetForegroundSessionResponse.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/GetForegroundSessionResponse.java rename to java/sdk/src/main/java/com/github/copilot/rpc/GetForegroundSessionResponse.java diff --git a/java/src/main/java/com/github/copilot/rpc/GetLastSessionIdResponse.java b/java/sdk/src/main/java/com/github/copilot/rpc/GetLastSessionIdResponse.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/GetLastSessionIdResponse.java rename to java/sdk/src/main/java/com/github/copilot/rpc/GetLastSessionIdResponse.java diff --git a/java/src/main/java/com/github/copilot/rpc/GetMessagesResponse.java b/java/sdk/src/main/java/com/github/copilot/rpc/GetMessagesResponse.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/GetMessagesResponse.java rename to java/sdk/src/main/java/com/github/copilot/rpc/GetMessagesResponse.java diff --git a/java/src/main/java/com/github/copilot/rpc/GetModelsResponse.java b/java/sdk/src/main/java/com/github/copilot/rpc/GetModelsResponse.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/GetModelsResponse.java rename to java/sdk/src/main/java/com/github/copilot/rpc/GetModelsResponse.java diff --git a/java/src/main/java/com/github/copilot/rpc/GetSessionMetadataResponse.java b/java/sdk/src/main/java/com/github/copilot/rpc/GetSessionMetadataResponse.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/GetSessionMetadataResponse.java rename to java/sdk/src/main/java/com/github/copilot/rpc/GetSessionMetadataResponse.java diff --git a/java/src/main/java/com/github/copilot/rpc/GetStatusResponse.java b/java/sdk/src/main/java/com/github/copilot/rpc/GetStatusResponse.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/GetStatusResponse.java rename to java/sdk/src/main/java/com/github/copilot/rpc/GetStatusResponse.java diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/GitHubMcpToolConfig.java b/java/sdk/src/main/java/com/github/copilot/rpc/GitHubMcpToolConfig.java new file mode 100644 index 000000000..75a8e3016 --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/rpc/GitHubMcpToolConfig.java @@ -0,0 +1,82 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.rpc; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Configuration for the built-in GitHub MCP server. + * + *

+ * {@code disableFormDeferral} only applies to the built-in GitHub MCP server + * and only has an effect when MCP Apps and form-backed GitHub tools are + * enabled. + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public class GitHubMcpToolConfig { + + @JsonProperty("enableAllTools") + private Boolean enableAllTools; + + @JsonProperty("additionalToolsets") + private List additionalToolsets; + + @JsonProperty("additionalTools") + private List additionalTools; + + @JsonProperty("enableInsidersMode") + private Boolean enableInsidersMode; + + @JsonProperty("disableFormDeferral") + private Boolean disableFormDeferral; + + public Boolean getEnableAllTools() { + return enableAllTools; + } + + public GitHubMcpToolConfig setEnableAllTools(Boolean enableAllTools) { + this.enableAllTools = enableAllTools; + return this; + } + + public List getAdditionalToolsets() { + return additionalToolsets; + } + + public GitHubMcpToolConfig setAdditionalToolsets(List additionalToolsets) { + this.additionalToolsets = additionalToolsets; + return this; + } + + public List getAdditionalTools() { + return additionalTools; + } + + public GitHubMcpToolConfig setAdditionalTools(List additionalTools) { + this.additionalTools = additionalTools; + return this; + } + + public Boolean getEnableInsidersMode() { + return enableInsidersMode; + } + + public GitHubMcpToolConfig setEnableInsidersMode(Boolean enableInsidersMode) { + this.enableInsidersMode = enableInsidersMode; + return this; + } + + public Boolean getDisableFormDeferral() { + return disableFormDeferral; + } + + public GitHubMcpToolConfig setDisableFormDeferral(Boolean disableFormDeferral) { + this.disableFormDeferral = disableFormDeferral; + return this; + } +} diff --git a/java/src/main/java/com/github/copilot/rpc/HookInvocation.java b/java/sdk/src/main/java/com/github/copilot/rpc/HookInvocation.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/HookInvocation.java rename to java/sdk/src/main/java/com/github/copilot/rpc/HookInvocation.java diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/InProcessRuntimeConnection.java b/java/sdk/src/main/java/com/github/copilot/rpc/InProcessRuntimeConnection.java new file mode 100644 index 000000000..274f8b89d --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/rpc/InProcessRuntimeConnection.java @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.rpc; + +import com.github.copilot.CopilotExperimental; + +/** + * Hosts the runtime in-process by loading its native library and communicating + * over the C ABI — no child process is spawned by the SDK for JSON-RPC + * transport. Construct with {@link RuntimeConnection#forInProcess()}. + *

+ * The in-process runtime is self-contained: it carries everything it needs and + * requires no external installation. Because it runs inside the host process, + * per-client process settings ({@code environment}, {@code telemetry}, + * {@code cwd}, and {@code cliArgs}) are rejected; configure those on the host + * process instead, or use a child-process connection. + * + * @since 1.0.0 + */ +@CopilotExperimental +public final class InProcessRuntimeConnection extends RuntimeConnection { + + InProcessRuntimeConnection() { + } +} diff --git a/java/src/main/java/com/github/copilot/rpc/InfiniteSessionConfig.java b/java/sdk/src/main/java/com/github/copilot/rpc/InfiniteSessionConfig.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/InfiniteSessionConfig.java rename to java/sdk/src/main/java/com/github/copilot/rpc/InfiniteSessionConfig.java diff --git a/java/src/main/java/com/github/copilot/rpc/InputOptions.java b/java/sdk/src/main/java/com/github/copilot/rpc/InputOptions.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/InputOptions.java rename to java/sdk/src/main/java/com/github/copilot/rpc/InputOptions.java diff --git a/java/src/main/java/com/github/copilot/rpc/JsonRpcError.java b/java/sdk/src/main/java/com/github/copilot/rpc/JsonRpcError.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/JsonRpcError.java rename to java/sdk/src/main/java/com/github/copilot/rpc/JsonRpcError.java diff --git a/java/src/main/java/com/github/copilot/rpc/JsonRpcRequest.java b/java/sdk/src/main/java/com/github/copilot/rpc/JsonRpcRequest.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/JsonRpcRequest.java rename to java/sdk/src/main/java/com/github/copilot/rpc/JsonRpcRequest.java diff --git a/java/src/main/java/com/github/copilot/rpc/JsonRpcResponse.java b/java/sdk/src/main/java/com/github/copilot/rpc/JsonRpcResponse.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/JsonRpcResponse.java rename to java/sdk/src/main/java/com/github/copilot/rpc/JsonRpcResponse.java diff --git a/java/src/main/java/com/github/copilot/rpc/LargeToolOutputConfig.java b/java/sdk/src/main/java/com/github/copilot/rpc/LargeToolOutputConfig.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/LargeToolOutputConfig.java rename to java/sdk/src/main/java/com/github/copilot/rpc/LargeToolOutputConfig.java diff --git a/java/src/main/java/com/github/copilot/rpc/ListSessionsResponse.java b/java/sdk/src/main/java/com/github/copilot/rpc/ListSessionsResponse.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/ListSessionsResponse.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ListSessionsResponse.java diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/ManagedSettings.java b/java/sdk/src/main/java/com/github/copilot/rpc/ManagedSettings.java new file mode 100644 index 000000000..39e8fcf55 --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/rpc/ManagedSettings.java @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ +package com.github.copilot.rpc; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Managed settings an SDK host may inject at session create or resume. + * + *

+ * The initial public contract is permissions-only. + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public final class ManagedSettings { + @JsonProperty("permissions") + private ManagedSettingsPermissions permissions; + + /** @return the managed permission policy, or {@code null} when unset */ + public ManagedSettingsPermissions getPermissions() { + return permissions; + } + + /** + * @param permissions + * managed permission policy + * @return this settings object + */ + public ManagedSettings setPermissions(ManagedSettingsPermissions permissions) { + this.permissions = permissions; + return this; + } +} diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/ManagedSettingsPermissions.java b/java/sdk/src/main/java/com/github/copilot/rpc/ManagedSettingsPermissions.java new file mode 100644 index 000000000..0923cea54 --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/rpc/ManagedSettingsPermissions.java @@ -0,0 +1,90 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ +package com.github.copilot.rpc; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.generated.rpc.DisableBypassPermissionsMode; +import java.util.ArrayList; +import java.util.List; + +/** + * Enterprise permission policy injected by an SDK host at session startup. + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public final class ManagedSettingsPermissions { + @JsonProperty("disableBypassPermissionsMode") + private DisableBypassPermissionsMode disableBypassPermissionsMode; + + @JsonProperty("deny") + private List deny; + + @JsonProperty("ask") + private List ask; + + @JsonProperty("allow") + private List allow; + + /** @return the bypass-permissions policy, or {@code null} when unset */ + public DisableBypassPermissionsMode getDisableBypassPermissionsMode() { + return disableBypassPermissionsMode; + } + + /** + * Disables bypass/allow-all permission modes. + * + * @param value + * bypass-permissions policy + * @return this policy + */ + public ManagedSettingsPermissions setDisableBypassPermissionsMode(DisableBypassPermissionsMode value) { + this.disableBypassPermissionsMode = value; + return this; + } + + /** @return rules that deny matching operations, or {@code null} when unset */ + public List getDeny() { + return deny; + } + + /** + * @param rules + * deny rules + * @return this policy + */ + public ManagedSettingsPermissions setDeny(List rules) { + this.deny = rules == null ? null : new ArrayList<>(rules); + return this; + } + + /** @return rules that require approval, or {@code null} when unset */ + public List getAsk() { + return ask; + } + + /** + * @param rules + * ask rules + * @return this policy + */ + public ManagedSettingsPermissions setAsk(List rules) { + this.ask = rules == null ? null : new ArrayList<>(rules); + return this; + } + + /** @return rules that allow matching operations, or {@code null} when unset */ + public List getAllow() { + return allow; + } + + /** + * @param rules + * allow rules + * @return this policy + */ + public ManagedSettingsPermissions setAllow(List rules) { + this.allow = rules == null ? null : new ArrayList<>(rules); + return this; + } +} diff --git a/java/src/main/java/com/github/copilot/rpc/McpAuthHandler.java b/java/sdk/src/main/java/com/github/copilot/rpc/McpAuthHandler.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/McpAuthHandler.java rename to java/sdk/src/main/java/com/github/copilot/rpc/McpAuthHandler.java diff --git a/java/src/main/java/com/github/copilot/rpc/McpAuthInvocation.java b/java/sdk/src/main/java/com/github/copilot/rpc/McpAuthInvocation.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/McpAuthInvocation.java rename to java/sdk/src/main/java/com/github/copilot/rpc/McpAuthInvocation.java diff --git a/java/src/main/java/com/github/copilot/rpc/McpAuthRequest.java b/java/sdk/src/main/java/com/github/copilot/rpc/McpAuthRequest.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/McpAuthRequest.java rename to java/sdk/src/main/java/com/github/copilot/rpc/McpAuthRequest.java diff --git a/java/src/main/java/com/github/copilot/rpc/McpAuthResult.java b/java/sdk/src/main/java/com/github/copilot/rpc/McpAuthResult.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/McpAuthResult.java rename to java/sdk/src/main/java/com/github/copilot/rpc/McpAuthResult.java diff --git a/java/src/main/java/com/github/copilot/rpc/McpAuthToken.java b/java/sdk/src/main/java/com/github/copilot/rpc/McpAuthToken.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/McpAuthToken.java rename to java/sdk/src/main/java/com/github/copilot/rpc/McpAuthToken.java diff --git a/java/src/main/java/com/github/copilot/rpc/McpHttpServerConfig.java b/java/sdk/src/main/java/com/github/copilot/rpc/McpHttpServerConfig.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/McpHttpServerConfig.java rename to java/sdk/src/main/java/com/github/copilot/rpc/McpHttpServerConfig.java diff --git a/java/src/main/java/com/github/copilot/rpc/McpServerConfig.java b/java/sdk/src/main/java/com/github/copilot/rpc/McpServerConfig.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/McpServerConfig.java rename to java/sdk/src/main/java/com/github/copilot/rpc/McpServerConfig.java diff --git a/java/src/main/java/com/github/copilot/rpc/McpStdioServerConfig.java b/java/sdk/src/main/java/com/github/copilot/rpc/McpStdioServerConfig.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/McpStdioServerConfig.java rename to java/sdk/src/main/java/com/github/copilot/rpc/McpStdioServerConfig.java diff --git a/java/src/main/java/com/github/copilot/rpc/MemoryConfiguration.java b/java/sdk/src/main/java/com/github/copilot/rpc/MemoryConfiguration.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/MemoryConfiguration.java rename to java/sdk/src/main/java/com/github/copilot/rpc/MemoryConfiguration.java diff --git a/java/src/main/java/com/github/copilot/rpc/MessageAttachment.java b/java/sdk/src/main/java/com/github/copilot/rpc/MessageAttachment.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/MessageAttachment.java rename to java/sdk/src/main/java/com/github/copilot/rpc/MessageAttachment.java diff --git a/java/src/main/java/com/github/copilot/rpc/MessageOptions.java b/java/sdk/src/main/java/com/github/copilot/rpc/MessageOptions.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/MessageOptions.java rename to java/sdk/src/main/java/com/github/copilot/rpc/MessageOptions.java diff --git a/java/src/main/java/com/github/copilot/rpc/ModelBilling.java b/java/sdk/src/main/java/com/github/copilot/rpc/ModelBilling.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/ModelBilling.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ModelBilling.java diff --git a/java/src/main/java/com/github/copilot/rpc/ModelCapabilities.java b/java/sdk/src/main/java/com/github/copilot/rpc/ModelCapabilities.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/ModelCapabilities.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ModelCapabilities.java diff --git a/java/src/main/java/com/github/copilot/rpc/ModelCapabilitiesOverride.java b/java/sdk/src/main/java/com/github/copilot/rpc/ModelCapabilitiesOverride.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/ModelCapabilitiesOverride.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ModelCapabilitiesOverride.java diff --git a/java/src/main/java/com/github/copilot/rpc/ModelInfo.java b/java/sdk/src/main/java/com/github/copilot/rpc/ModelInfo.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/ModelInfo.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ModelInfo.java diff --git a/java/src/main/java/com/github/copilot/rpc/ModelLimits.java b/java/sdk/src/main/java/com/github/copilot/rpc/ModelLimits.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/ModelLimits.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ModelLimits.java diff --git a/java/src/main/java/com/github/copilot/rpc/ModelPolicy.java b/java/sdk/src/main/java/com/github/copilot/rpc/ModelPolicy.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/ModelPolicy.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ModelPolicy.java diff --git a/java/src/main/java/com/github/copilot/rpc/ModelSupports.java b/java/sdk/src/main/java/com/github/copilot/rpc/ModelSupports.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/ModelSupports.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ModelSupports.java diff --git a/java/src/main/java/com/github/copilot/rpc/ModelVisionLimits.java b/java/sdk/src/main/java/com/github/copilot/rpc/ModelVisionLimits.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/ModelVisionLimits.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ModelVisionLimits.java diff --git a/java/src/main/java/com/github/copilot/rpc/NamedProviderConfig.java b/java/sdk/src/main/java/com/github/copilot/rpc/NamedProviderConfig.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/NamedProviderConfig.java rename to java/sdk/src/main/java/com/github/copilot/rpc/NamedProviderConfig.java diff --git a/java/src/main/java/com/github/copilot/rpc/ParamCoercion.java b/java/sdk/src/main/java/com/github/copilot/rpc/ParamCoercion.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/ParamCoercion.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ParamCoercion.java diff --git a/java/src/main/java/com/github/copilot/rpc/ParamSchema.java b/java/sdk/src/main/java/com/github/copilot/rpc/ParamSchema.java similarity index 90% rename from java/src/main/java/com/github/copilot/rpc/ParamSchema.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ParamSchema.java index ee025eb2c..bdb4f38ae 100644 --- a/java/src/main/java/com/github/copilot/rpc/ParamSchema.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/ParamSchema.java @@ -14,6 +14,7 @@ import java.util.Set; import java.util.stream.Collectors; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.copilot.tool.Param; @@ -84,7 +85,21 @@ static Map buildSchema(String toolName, ObjectMapper mapper, Par Map properties = new LinkedHashMap<>(); for (Param param : params) { - Map typeSchema = forType(param.type()); + Map typeSchema; + if (!param.schema().isEmpty()) { + try { + @SuppressWarnings("unchecked") + Map parsed = mapper.readerFor(Map.class) + .with(DeserializationFeature.FAIL_ON_TRAILING_TOKENS) + .with(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS).readValue(param.schema()); + typeSchema = parsed; + } catch (Exception e) { + throw new IllegalArgumentException("Invalid schema JSON for parameter '" + param.name() + + "' in tool '" + toolName + "': " + e.getMessage(), e); + } + } else { + typeSchema = forType(param.type()); + } Map enriched = new LinkedHashMap<>(typeSchema); enriched.put("description", param.description()); if (param.hasDefaultValue()) { diff --git a/java/src/main/java/com/github/copilot/rpc/PermissionHandler.java b/java/sdk/src/main/java/com/github/copilot/rpc/PermissionHandler.java similarity index 65% rename from java/src/main/java/com/github/copilot/rpc/PermissionHandler.java rename to java/sdk/src/main/java/com/github/copilot/rpc/PermissionHandler.java index bd8e70b75..58639beda 100644 --- a/java/src/main/java/com/github/copilot/rpc/PermissionHandler.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/PermissionHandler.java @@ -17,6 +17,11 @@ * *

{@code
  * PermissionHandler handler = (request, invocation) -> {
+ * 	if (Boolean.TRUE.equals(request.getManagedApprovalRequired())) {
+ * 		// Obtain an explicit human decision before approving this request.
+ * 		return requestHumanApproval(request);
+ * 	}
+ *
  * 	// Check the permission kind
  * 	if ("dangerous-action".equals(request.getKind())) {
  * 		// Deny dangerous actions
@@ -29,6 +34,11 @@
  * 			.completedFuture(new PermissionRequestResult().setKind(PermissionRequestResultKind.APPROVED));
  * };
  * }
+ *

+ * Event-based permission dispatch can use + * {@link PermissionRequestResult#noResult()} to let another connected client + * answer a pending request. Legacy protocol-v2 callbacks require a decision and + * cannot abstain. * *

* A pre-built handler that approves all requests is available as @@ -43,12 +53,21 @@ public interface PermissionHandler { /** - * A pre-built handler that approves all permission requests. + * A pre-built handler that approves permission requests when managed settings + * are disabled. * * @since 1.0.11 */ - PermissionHandler APPROVE_ALL = (request, invocation) -> CompletableFuture - .completedFuture(new PermissionRequestResult().setKind(PermissionRequestResultKind.APPROVED)); + PermissionHandler APPROVE_ALL = (request, invocation) -> { + if (invocation.isManagedSettingsEnabled()) { + return CompletableFuture.failedFuture( + new IllegalStateException("APPROVE_ALL cannot be used when managed settings are enabled")); + } + if (Boolean.TRUE.equals(request.getManagedApprovalRequired())) { + return CompletableFuture.completedFuture(PermissionRequestResult.noResult()); + } + return CompletableFuture.completedFuture(PermissionRequestResult.approveOnce()); + }; /** * Handles a permission request from the assistant. diff --git a/java/src/main/java/com/github/copilot/rpc/PermissionInvocation.java b/java/sdk/src/main/java/com/github/copilot/rpc/PermissionInvocation.java similarity index 60% rename from java/src/main/java/com/github/copilot/rpc/PermissionInvocation.java rename to java/sdk/src/main/java/com/github/copilot/rpc/PermissionInvocation.java index bda5bdde0..10988cc1b 100644 --- a/java/src/main/java/com/github/copilot/rpc/PermissionInvocation.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/PermissionInvocation.java @@ -16,6 +16,7 @@ public final class PermissionInvocation { private String sessionId; + private boolean managedSettingsEnabled; /** * Gets the session ID where the permission was requested. @@ -37,4 +38,25 @@ public PermissionInvocation setSessionId(String sessionId) { this.sessionId = sessionId; return this; } + + /** + * Gets whether managed settings are enabled for this session. + * + * @return whether managed settings are enabled + */ + public boolean isManagedSettingsEnabled() { + return managedSettingsEnabled; + } + + /** + * Sets whether managed settings are enabled for this session. + * + * @param managedSettingsEnabled + * whether managed settings are enabled + * @return this invocation for method chaining + */ + public PermissionInvocation setManagedSettingsEnabled(boolean managedSettingsEnabled) { + this.managedSettingsEnabled = managedSettingsEnabled; + return this; + } } diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/PermissionRequest.java b/java/sdk/src/main/java/com/github/copilot/rpc/PermissionRequest.java new file mode 100644 index 000000000..fc49332b8 --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/rpc/PermissionRequest.java @@ -0,0 +1,167 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.rpc; + +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +/** + * Represents a permission request from the AI assistant. + *

+ * When the assistant needs permission to perform certain actions, this object + * contains the details of the request, including the kind of permission and any + * associated tool call. + * + * @see PermissionHandler + * @since 1.0.0 + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class PermissionRequest { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + @JsonProperty("kind") + private String kind; + + @JsonProperty("toolCallId") + private String toolCallId; + + @JsonProperty("managedApprovalRequired") + @JsonDeserialize(using = ManagedApprovalRequiredDeserializer.class) + private Boolean managedApprovalRequired; + + private Map extensionData; + + @JsonAnySetter + private void setExtensionDataEntry(String key, Object value) { + if (extensionData == null) { + extensionData = new LinkedHashMap<>(); + } + extensionData.put(key, value); + } + + private static final class ManagedApprovalRequiredDeserializer extends JsonDeserializer { + + @Override + public Boolean deserialize(JsonParser parser, DeserializationContext context) throws IOException { + JsonToken token = parser.currentToken(); + if (token == JsonToken.VALUE_TRUE) { + return true; + } + if (token == JsonToken.VALUE_FALSE) { + return false; + } + parser.skipChildren(); + return true; + } + } + + /** + * Converts the value exposed by a {@code permission.requested} event into a + * typed permission request. + * + * @param value + * the event's {@code permissionRequest} value + * @return the typed permission request + * @throws IllegalArgumentException + * if the value cannot be converted + */ + public static PermissionRequest fromJsonValue(Object value) { + if (value instanceof PermissionRequest request) { + return request; + } + return MAPPER.convertValue(value, PermissionRequest.class); + } + + /** + * Gets the kind of permission being requested. + * + * @return the permission kind + */ + public String getKind() { + return kind; + } + + /** + * Sets the permission kind. + * + * @param kind + * the permission kind + */ + public void setKind(String kind) { + this.kind = kind; + } + + /** + * Gets the associated tool call ID, if applicable. + * + * @return the tool call ID, or {@code null} if not a tool-related request + */ + public String getToolCallId() { + return toolCallId; + } + + /** + * Sets the tool call ID. + * + * @param toolCallId + * the tool call ID + */ + public void setToolCallId(String toolCallId) { + this.toolCallId = toolCallId; + } + + /** + * Gets whether managed policy requires an explicit human decision. + * + * @return {@code true} when automatic approval must be bypassed, otherwise + * {@code false} or {@code null} + */ + public Boolean getManagedApprovalRequired() { + return managedApprovalRequired; + } + + /** + * Sets whether managed policy requires an explicit human decision. + * + * @param managedApprovalRequired + * whether managed approval is required + */ + public void setManagedApprovalRequired(Boolean managedApprovalRequired) { + this.managedApprovalRequired = managedApprovalRequired; + } + + /** + * Gets additional extension data for the request. + * + * @return the extension data map + */ + public Map getExtensionData() { + return extensionData; + } + + /** + * Sets additional extension data for the request. + * + * @param extensionData + * the extension data map + */ + public void setExtensionData(Map extensionData) { + this.extensionData = extensionData; + } +} diff --git a/java/src/main/java/com/github/copilot/rpc/PermissionRequestResult.java b/java/sdk/src/main/java/com/github/copilot/rpc/PermissionRequestResult.java similarity index 74% rename from java/src/main/java/com/github/copilot/rpc/PermissionRequestResult.java rename to java/sdk/src/main/java/com/github/copilot/rpc/PermissionRequestResult.java index 2e5c60100..6546291cf 100644 --- a/java/src/main/java/com/github/copilot/rpc/PermissionRequestResult.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/PermissionRequestResult.java @@ -6,8 +6,10 @@ import java.util.List; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.copilot.generated.rpc.PermissionDecisionContext; /** * Result of a permission request decision. @@ -42,6 +44,15 @@ public final class PermissionRequestResult { @JsonProperty("feedback") private String feedback; + /** + * Optional provenance describing how and where this decision was made. Never + * serialized inside the result — the SDK forwards it as a sibling of + * {@code result} so the runtime can attribute {@code auto_approval_decision} + * telemetry. + */ + @JsonIgnore + private PermissionDecisionContext decisionContext; + /** * Creates a result that approves this single request. * @@ -168,4 +179,35 @@ public PermissionRequestResult setFeedback(String feedback) { this.feedback = feedback; return this; } + + /** + * Gets the optional provenance describing how and where this decision was made. + *

+ * This value is never serialized inside the result JSON; the SDK forwards it as + * a sibling of {@code result} when responding to the runtime. + * + * @return the decision context, or {@code null} if none was attached + * @since 1.3.0 + */ + public PermissionDecisionContext getDecisionContext() { + return decisionContext; + } + + /** + * Sets provenance describing how and where this decision was made, so the + * runtime can attribute {@code auto_approval_decision} telemetry. + *

+ * Calling this method more than once replaces any previously set context. The + * context is never serialized inside the result; the SDK forwards it as a + * sibling of {@code result}. + * + * @param decisionContext + * the decision context, or {@code null} to attach none + * @return this result for method chaining + * @since 1.3.0 + */ + public PermissionRequestResult setDecisionContext(PermissionDecisionContext decisionContext) { + this.decisionContext = decisionContext; + return this; + } } diff --git a/java/src/main/java/com/github/copilot/rpc/PermissionRequestResultKind.java b/java/sdk/src/main/java/com/github/copilot/rpc/PermissionRequestResultKind.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/PermissionRequestResultKind.java rename to java/sdk/src/main/java/com/github/copilot/rpc/PermissionRequestResultKind.java diff --git a/java/src/main/java/com/github/copilot/rpc/PingResponse.java b/java/sdk/src/main/java/com/github/copilot/rpc/PingResponse.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/PingResponse.java rename to java/sdk/src/main/java/com/github/copilot/rpc/PingResponse.java diff --git a/java/src/main/java/com/github/copilot/rpc/PostToolUseFailureHandler.java b/java/sdk/src/main/java/com/github/copilot/rpc/PostToolUseFailureHandler.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/PostToolUseFailureHandler.java rename to java/sdk/src/main/java/com/github/copilot/rpc/PostToolUseFailureHandler.java diff --git a/java/src/main/java/com/github/copilot/rpc/PostToolUseFailureHookInput.java b/java/sdk/src/main/java/com/github/copilot/rpc/PostToolUseFailureHookInput.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/PostToolUseFailureHookInput.java rename to java/sdk/src/main/java/com/github/copilot/rpc/PostToolUseFailureHookInput.java diff --git a/java/src/main/java/com/github/copilot/rpc/PostToolUseFailureHookOutput.java b/java/sdk/src/main/java/com/github/copilot/rpc/PostToolUseFailureHookOutput.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/PostToolUseFailureHookOutput.java rename to java/sdk/src/main/java/com/github/copilot/rpc/PostToolUseFailureHookOutput.java diff --git a/java/src/main/java/com/github/copilot/rpc/PostToolUseHandler.java b/java/sdk/src/main/java/com/github/copilot/rpc/PostToolUseHandler.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/PostToolUseHandler.java rename to java/sdk/src/main/java/com/github/copilot/rpc/PostToolUseHandler.java diff --git a/java/src/main/java/com/github/copilot/rpc/PostToolUseHookInput.java b/java/sdk/src/main/java/com/github/copilot/rpc/PostToolUseHookInput.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/PostToolUseHookInput.java rename to java/sdk/src/main/java/com/github/copilot/rpc/PostToolUseHookInput.java diff --git a/java/src/main/java/com/github/copilot/rpc/PostToolUseHookOutput.java b/java/sdk/src/main/java/com/github/copilot/rpc/PostToolUseHookOutput.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/PostToolUseHookOutput.java rename to java/sdk/src/main/java/com/github/copilot/rpc/PostToolUseHookOutput.java diff --git a/java/src/main/java/com/github/copilot/rpc/PreMcpToolCallHandler.java b/java/sdk/src/main/java/com/github/copilot/rpc/PreMcpToolCallHandler.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/PreMcpToolCallHandler.java rename to java/sdk/src/main/java/com/github/copilot/rpc/PreMcpToolCallHandler.java diff --git a/java/src/main/java/com/github/copilot/rpc/PreMcpToolCallHookInput.java b/java/sdk/src/main/java/com/github/copilot/rpc/PreMcpToolCallHookInput.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/PreMcpToolCallHookInput.java rename to java/sdk/src/main/java/com/github/copilot/rpc/PreMcpToolCallHookInput.java diff --git a/java/src/main/java/com/github/copilot/rpc/PreMcpToolCallHookOutput.java b/java/sdk/src/main/java/com/github/copilot/rpc/PreMcpToolCallHookOutput.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/PreMcpToolCallHookOutput.java rename to java/sdk/src/main/java/com/github/copilot/rpc/PreMcpToolCallHookOutput.java diff --git a/java/src/main/java/com/github/copilot/rpc/PreToolUseHandler.java b/java/sdk/src/main/java/com/github/copilot/rpc/PreToolUseHandler.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/PreToolUseHandler.java rename to java/sdk/src/main/java/com/github/copilot/rpc/PreToolUseHandler.java diff --git a/java/src/main/java/com/github/copilot/rpc/PreToolUseHookInput.java b/java/sdk/src/main/java/com/github/copilot/rpc/PreToolUseHookInput.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/PreToolUseHookInput.java rename to java/sdk/src/main/java/com/github/copilot/rpc/PreToolUseHookInput.java diff --git a/java/src/main/java/com/github/copilot/rpc/PreToolUseHookOutput.java b/java/sdk/src/main/java/com/github/copilot/rpc/PreToolUseHookOutput.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/PreToolUseHookOutput.java rename to java/sdk/src/main/java/com/github/copilot/rpc/PreToolUseHookOutput.java diff --git a/java/src/main/java/com/github/copilot/rpc/ProviderConfig.java b/java/sdk/src/main/java/com/github/copilot/rpc/ProviderConfig.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/ProviderConfig.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ProviderConfig.java diff --git a/java/src/main/java/com/github/copilot/rpc/ProviderModelConfig.java b/java/sdk/src/main/java/com/github/copilot/rpc/ProviderModelConfig.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/ProviderModelConfig.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ProviderModelConfig.java diff --git a/java/src/main/java/com/github/copilot/rpc/ProviderTokenArgs.java b/java/sdk/src/main/java/com/github/copilot/rpc/ProviderTokenArgs.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/ProviderTokenArgs.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ProviderTokenArgs.java diff --git a/java/src/main/java/com/github/copilot/rpc/ResumeSessionConfig.java b/java/sdk/src/main/java/com/github/copilot/rpc/ResumeSessionConfig.java similarity index 89% rename from java/src/main/java/com/github/copilot/rpc/ResumeSessionConfig.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ResumeSessionConfig.java index a3dd336cf..a18803637 100644 --- a/java/src/main/java/com/github/copilot/rpc/ResumeSessionConfig.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/ResumeSessionConfig.java @@ -53,7 +53,9 @@ public class ResumeSessionConfig { private List models; private Boolean enableSessionTelemetry; private Boolean enableCitations; + private Boolean enableFileChangeTracking; private SessionLimitsConfig sessionLimits; + private Boolean enableExperimentalMode; private Boolean skipCustomInstructions; private Boolean customAgentsLocalOnly; private Boolean coauthorEnabled; @@ -67,6 +69,7 @@ public class ResumeSessionConfig { private UserInputHandler onUserInputRequest; private SessionHooks hooks; private String workingDirectory; + private List additionalDirectories; private String configDirectory; private Boolean enableConfigDiscovery; private Boolean skipEmbeddingRetrieval; @@ -92,6 +95,7 @@ public class ResumeSessionConfig { private ToolSearchConfig toolSearch; private MemoryConfiguration memory; private List disabledSkills; + private List disabledMcpServers; private InfiniteSessionConfig infiniteSessions; private Consumer onEvent; private List commands; @@ -99,10 +103,12 @@ public class ResumeSessionConfig { private ExitPlanModeHandler onExitPlanMode; private AutoModeSwitchHandler onAutoModeSwitch; private boolean enableMcpApps; + private GitHubMcpToolConfig githubMcpToolConfig; private String gitHubToken; private String remoteSession; private CopilotExpAssignmentResponse expAssignments; private Boolean enableManagedSettings; + private ManagedSettings managedSettings; /** * Gets the AI model to use. @@ -448,6 +454,41 @@ public ResumeSessionConfig clearEnableCitations() { return this; } + /** + * Gets whether file change tracking is enabled for rewind and cumulative + * session diff. + * + * @return an {@link java.util.Optional} containing the setting, or + * {@link java.util.Optional#empty()} for the default + */ + @JsonIgnore + public Optional getEnableFileChangeTracking() { + return Optional.ofNullable(enableFileChangeTracking); + } + + /** + * Enables or disables file change tracking when the resumed session has a valid + * baseline. Earlier untracked changes cannot be reconstructed. + * + * @param enableFileChangeTracking + * whether to enable file change tracking + * @return this config instance for method chaining + */ + public ResumeSessionConfig setEnableFileChangeTracking(boolean enableFileChangeTracking) { + this.enableFileChangeTracking = enableFileChangeTracking; + return this; + } + + /** + * Clears the file change tracking setting, reverting to the default behavior. + * + * @return this instance for method chaining + */ + public ResumeSessionConfig clearEnableFileChangeTracking() { + this.enableFileChangeTracking = null; + return this; + } + /** * Gets the limits for this session's current accounting window. * @@ -471,6 +512,52 @@ public ResumeSessionConfig setSessionLimits(SessionLimitsConfig sessionLimits) { return this; } + /** + * Clears the sessionLimits setting, reverting to the default behavior. + * + * @return this instance for method chaining + */ + @CopilotExperimental + public ResumeSessionConfig clearSessionLimits() { + this.sessionLimits = null; + return this; + } + + /** + * Controls whether the session enables experimental features. + * + * @return {@code true} when experimental features are enabled, {@code false} + * when they are disabled, or empty to use the mode-specific default + */ + @JsonIgnore + public Optional getEnableExperimentalMode() { + return Optional.ofNullable(enableExperimentalMode); + } + + /** + * Controls whether the session enables experimental features. + * + * @param enableExperimentalMode + * {@code true} to enable experimental features; {@code false} to + * disable them + * @return this config for method chaining + */ + public ResumeSessionConfig setEnableExperimentalMode(boolean enableExperimentalMode) { + this.enableExperimentalMode = enableExperimentalMode; + return this; + } + + /** + * Clears the enableExperimentalMode setting. In {@link CopilotClientMode#EMPTY + * EMPTY} mode this defaults to {@code false}; otherwise the runtime decides. + * + * @return this instance for method chaining + */ + public ResumeSessionConfig clearEnableExperimentalMode() { + this.enableExperimentalMode = null; + return this; + } + /** * Gets whether custom instruction file loading is suppressed. * @@ -526,11 +613,11 @@ public Optional getCustomAgentsLocalOnly() { * Sets whether custom-agent discovery is restricted to the session's local * working directory. *

- * This option is sent to the server via a {@code session.options.update} - * JSON-RPC call immediately after session resume. In - * {@link CopilotClientMode#EMPTY EMPTY} mode the default is {@code true} (local - * only); in {@link CopilotClientMode#COPILOT_CLI COPILOT_CLI} mode the value is - * forwarded only when explicitly set. + * This option is sent with the initial resume request and maintained via + * {@code session.options.update}. In {@link CopilotClientMode#EMPTY EMPTY} mode + * the default is {@code true} (local only); in + * {@link CopilotClientMode#COPILOT_CLI COPILOT_CLI} mode the value is forwarded + * only when explicitly set. * * @param customAgentsLocalOnly * whether to restrict to local agents @@ -636,7 +723,8 @@ public ResumeSessionConfig clearManageScheduleEnabled() { /** * Gets the reasoning effort level. * - * @return the reasoning effort level ("low", "medium", "high", or "xhigh") + * @return the reasoning effort level ("low", "medium", "high", "xhigh", or + * "max") */ public String getReasoningEffort() { return reasoningEffort; @@ -645,7 +733,7 @@ public String getReasoningEffort() { /** * Sets the reasoning effort level for models that support it. *

- * Valid values: "low", "medium", "high", "xhigh". + * Valid values: "low", "medium", "high", "xhigh", "max". * * @param reasoningEffort * the reasoning effort level @@ -811,6 +899,27 @@ public ResumeSessionConfig setWorkingDirectory(String workingDirectory) { return this; } + /** + * Gets the directories the agent may access beyond the working directory. + * + * @return the additional directory paths + */ + public List getAdditionalDirectories() { + return additionalDirectories; + } + + /** + * Sets directories the agent may access beyond the working directory. + * + * @param additionalDirectories + * the additional directory paths + * @return this config for method chaining + */ + public ResumeSessionConfig setAdditionalDirectories(List additionalDirectories) { + this.additionalDirectories = additionalDirectories; + return this; + } + /** * Gets the configuration directory path. * @@ -847,12 +956,8 @@ public Optional getEnableConfigDiscovery() { } /** - * Sets whether to automatically discover MCP server configurations and skill - * directories from the working directory. - *

- * When {@code true}, the CLI scans the working directory for {@code .mcp.json}, - * {@code .vscode/mcp.json} and skill directories, and merges them with - * explicitly provided configurations. + * Enables runtime discovery of supported configuration. Explicitly supplied + * configuration takes precedence over discovered values. * * @param enableConfigDiscovery * {@code true} to enable discovery, {@code false} to disable @@ -1510,6 +1615,29 @@ public ResumeSessionConfig setDisabledSkills(List disabledSkills) { return this; } + /** + * Gets exact MCP server names disabled for this session. + * + * @return the disabled MCP server names, or {@code null} when none are disabled + */ + public List getDisabledMcpServers() { + return disabledMcpServers == null ? null : Collections.unmodifiableList(disabledMcpServers); + } + + /** + * Sets exact MCP server names to disable for this session. Disabled servers are + * not started or authenticated on create or cold resume; a resident resume + * cannot stop servers already running. + * + * @param disabledMcpServers + * the server names to disable + * @return this config for method chaining + */ + public ResumeSessionConfig setDisabledMcpServers(List disabledMcpServers) { + this.disabledMcpServers = disabledMcpServers; + return this; + } + /** * Gets the infinite session configuration. * @@ -1634,6 +1762,27 @@ public ResumeSessionConfig setEnableMcpApps(boolean enableMcpApps) { return this; } + /** + * Gets the configuration for the built-in GitHub MCP server. + * + * @return the GitHub MCP configuration, or {@code null} + */ + public GitHubMcpToolConfig getGitHubMcpToolConfig() { + return githubMcpToolConfig; + } + + /** + * Sets the configuration for the built-in GitHub MCP server. + * + * @param githubMcpToolConfig + * the GitHub MCP configuration + * @return this config instance for method chaining + */ + public ResumeSessionConfig setGitHubMcpToolConfig(GitHubMcpToolConfig githubMcpToolConfig) { + this.githubMcpToolConfig = githubMcpToolConfig; + return this; + } + /** * Gets the exit-plan-mode request handler. * @@ -1798,6 +1947,24 @@ public ResumeSessionConfig setEnableManagedSettings(boolean enableManagedSetting return this; } + /** @return host-injected managed settings, or {@code null} when unset */ + public ManagedSettings getManagedSettings() { + return managedSettings; + } + + /** + * Supplies permissions-only managed settings for this resume. The value + * replaces the prior injected layer and is not persisted. + * + * @param managedSettings + * the host-injected managed settings + * @return this config for method chaining + */ + public ResumeSessionConfig setManagedSettings(ManagedSettings managedSettings) { + this.managedSettings = managedSettings; + return this; + } + /** * Creates a shallow clone of this {@code ResumeSessionConfig} instance. *

@@ -1827,7 +1994,9 @@ public ResumeSessionConfig clone() { copy.models = this.models != null ? new ArrayList<>(this.models) : null; copy.enableSessionTelemetry = this.enableSessionTelemetry; copy.enableCitations = this.enableCitations; + copy.enableFileChangeTracking = this.enableFileChangeTracking; copy.sessionLimits = this.sessionLimits; + copy.enableExperimentalMode = this.enableExperimentalMode; copy.reasoningEffort = this.reasoningEffort; copy.reasoningSummary = this.reasoningSummary; copy.contextTier = this.contextTier; @@ -1836,6 +2005,9 @@ public ResumeSessionConfig clone() { copy.onUserInputRequest = this.onUserInputRequest; copy.hooks = this.hooks; copy.workingDirectory = this.workingDirectory; + copy.additionalDirectories = this.additionalDirectories != null + ? new ArrayList<>(this.additionalDirectories) + : null; copy.configDirectory = this.configDirectory; copy.enableConfigDiscovery = this.enableConfigDiscovery; copy.skipEmbeddingRetrieval = this.skipEmbeddingRetrieval; @@ -1862,6 +2034,7 @@ public ResumeSessionConfig clone() { copy.toolSearch = this.toolSearch; copy.memory = this.memory; copy.disabledSkills = this.disabledSkills != null ? new ArrayList<>(this.disabledSkills) : null; + copy.disabledMcpServers = this.disabledMcpServers != null ? new ArrayList<>(this.disabledMcpServers) : null; copy.infiniteSessions = this.infiniteSessions; copy.onEvent = this.onEvent; copy.commands = this.commands != null ? new ArrayList<>(this.commands) : null; @@ -1870,10 +2043,12 @@ public ResumeSessionConfig clone() { copy.onExitPlanMode = this.onExitPlanMode; copy.onAutoModeSwitch = this.onAutoModeSwitch; copy.enableMcpApps = this.enableMcpApps; + copy.githubMcpToolConfig = this.githubMcpToolConfig; copy.gitHubToken = this.gitHubToken; copy.remoteSession = this.remoteSession; copy.expAssignments = this.expAssignments; copy.enableManagedSettings = this.enableManagedSettings; + copy.managedSettings = this.managedSettings; return copy; } } diff --git a/java/src/main/java/com/github/copilot/rpc/ResumeSessionRequest.java b/java/sdk/src/main/java/com/github/copilot/rpc/ResumeSessionRequest.java similarity index 89% rename from java/src/main/java/com/github/copilot/rpc/ResumeSessionRequest.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ResumeSessionRequest.java index a81ed49dd..e52892477 100644 --- a/java/src/main/java/com/github/copilot/rpc/ResumeSessionRequest.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/ResumeSessionRequest.java @@ -82,6 +82,9 @@ public final class ResumeSessionRequest { @JsonProperty("enableCitations") private Boolean enableCitations; + @JsonProperty("enableFileChangeTracking") + private Boolean enableFileChangeTracking; + @JsonProperty("sessionLimits") private SessionLimitsConfig sessionLimits; @@ -97,6 +100,9 @@ public final class ResumeSessionRequest { @JsonProperty("workingDirectory") private String workingDirectory; + @JsonProperty("additionalDirectories") + private List additionalDirectories; + @JsonProperty("configDir") private String configDirectory; @@ -159,6 +165,9 @@ public final class ResumeSessionRequest { @JsonProperty("customAgents") private List customAgents; + @JsonProperty("customAgentsLocalOnly") + private Boolean customAgentsLocalOnly; + @JsonProperty("defaultAgent") private DefaultAgentConfig defaultAgent; @@ -186,6 +195,9 @@ public final class ResumeSessionRequest { @JsonProperty("disabledSkills") private List disabledSkills; + @JsonProperty("disabledMcpServers") + private List disabledMcpServers; + @JsonProperty("infiniteSessions") private InfiniteSessionConfig infiniteSessions; @@ -198,6 +210,13 @@ public final class ResumeSessionRequest { @JsonProperty("requestMcpApps") private Boolean requestMcpApps; + @JsonProperty("githubMcpToolConfig") + private GitHubMcpToolConfig githubMcpToolConfig; + + @JsonProperty("isExperimentalMode") + @JsonInclude(JsonInclude.Include.NON_NULL) + private Boolean isExperimentalMode; + @JsonProperty("requestExitPlanMode") private Boolean requestExitPlanMode; @@ -220,6 +239,10 @@ public final class ResumeSessionRequest { @JsonInclude(JsonInclude.Include.NON_NULL) private Boolean enableManagedSettings; + @JsonProperty("managedSettings") + @JsonInclude(JsonInclude.Include.NON_NULL) + private ManagedSettings managedSettings; + /** Gets the session ID. @return the session ID */ public String getSessionId() { return sessionId; @@ -419,6 +442,21 @@ public void setEnableCitations(boolean enableCitations) { this.enableCitations = enableCitations; } + /** Gets the file change tracking flag. @return the flag */ + public Boolean getEnableFileChangeTracking() { + return enableFileChangeTracking; + } + + /** + * Sets the file change tracking flag. + * + * @param enableFileChangeTracking + * the flag + */ + public void setEnableFileChangeTracking(boolean enableFileChangeTracking) { + this.enableFileChangeTracking = enableFileChangeTracking; + } + /** Gets the session limits. @return the session limits */ public SessionLimitsConfig getSessionLimits() { return sessionLimits; @@ -497,6 +535,21 @@ public void setWorkingDirectory(String workingDirectory) { this.workingDirectory = workingDirectory; } + /** Gets additional directories. @return the additional directories */ + public List getAdditionalDirectories() { + return additionalDirectories; + } + + /** + * Sets additional directories. + * + * @param additionalDirectories + * the additional directories + */ + public void setAdditionalDirectories(List additionalDirectories) { + this.additionalDirectories = additionalDirectories; + } + /** Gets config directory. @return the config directory */ public String getConfigDirectory() { return configDirectory; @@ -778,6 +831,19 @@ public void setCustomAgents(List customAgents) { this.customAgents = customAgents; } + /** Gets whether custom agents are local only. @return the flag */ + public Boolean getCustomAgentsLocalOnly() { + return customAgentsLocalOnly; + } + + /** + * Sets whether custom agents are local only. @param customAgentsLocalOnly the + * flag + */ + public void setCustomAgentsLocalOnly(Boolean customAgentsLocalOnly) { + this.customAgentsLocalOnly = customAgentsLocalOnly; + } + /** Gets the default agent config. @return the default agent config */ public DefaultAgentConfig getDefaultAgent() { return defaultAgent; @@ -872,6 +938,18 @@ public void setDisabledSkills(List disabledSkills) { this.disabledSkills = disabledSkills; } + /** Gets disabled MCP server names. @return the server names */ + public List getDisabledMcpServers() { + return disabledMcpServers == null ? null : Collections.unmodifiableList(disabledMcpServers); + } + + /** + * Sets disabled MCP server names. @param disabledMcpServers the server names + */ + public void setDisabledMcpServers(List disabledMcpServers) { + this.disabledMcpServers = disabledMcpServers; + } + /** Gets infinite sessions config. @return the infinite sessions config */ public InfiniteSessionConfig getInfiniteSessions() { return infiniteSessions; @@ -927,6 +1005,40 @@ public void clearRequestMcpApps() { this.requestMcpApps = null; } + /** Gets the GitHub MCP tool configuration. @return the configuration */ + public GitHubMcpToolConfig getGitHubMcpToolConfig() { + return githubMcpToolConfig; + } + + /** Sets the GitHub MCP tool configuration. @param config the value */ + public void setGitHubMcpToolConfig(GitHubMcpToolConfig config) { + this.githubMcpToolConfig = config; + } + + /** + * Gets the isExperimentalMode flag. + * + * @return the flag + */ + public Boolean getIsExperimentalMode() { + return isExperimentalMode; + } + + /** + * Sets the isExperimentalMode flag. + * + * @param isExperimentalMode + * the flag + */ + public void setIsExperimentalMode(boolean isExperimentalMode) { + this.isExperimentalMode = isExperimentalMode; + } + + /** Clears the isExperimentalMode setting, reverting to the default behavior. */ + public void clearIsExperimentalMode() { + this.isExperimentalMode = null; + } + /** Gets the requestExitPlanMode flag. @return the flag */ public Boolean getRequestExitPlanMode() { return requestExitPlanMode; @@ -1020,4 +1132,17 @@ public void setEnableManagedSettings(boolean enableManagedSettings) { public void clearEnableManagedSettings() { this.enableManagedSettings = null; } + + /** @return host-injected managed settings, or {@code null} when unset */ + public ManagedSettings getManagedSettings() { + return managedSettings; + } + + /** + * @param managedSettings + * host-injected managed settings + */ + public void setManagedSettings(ManagedSettings managedSettings) { + this.managedSettings = managedSettings; + } } diff --git a/java/src/main/java/com/github/copilot/rpc/ResumeSessionResponse.java b/java/sdk/src/main/java/com/github/copilot/rpc/ResumeSessionResponse.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/ResumeSessionResponse.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ResumeSessionResponse.java diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/RuntimeConnection.java b/java/sdk/src/main/java/com/github/copilot/rpc/RuntimeConnection.java new file mode 100644 index 000000000..a0c8eec5c --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/rpc/RuntimeConnection.java @@ -0,0 +1,94 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.rpc; + +import com.github.copilot.CopilotExperimental; + +/** + * Configures how a {@link com.github.copilot.CopilotClient} connects to the + * Copilot runtime. + *

+ * Instances are created through the factory methods on this class and assigned + * with {@link CopilotClientOptions#setConnection(RuntimeConnection)}: + * + *

{@code
+ * // Spawn a runtime child process and talk over stdin/stdout (the default).
+ * new CopilotClientOptions().setConnection(RuntimeConnection.forStdio());
+ *
+ * // Spawn a runtime child process listening on a TCP socket.
+ * new CopilotClientOptions().setConnection(RuntimeConnection.forTcp().setPath("/usr/local/bin/copilot"));
+ *
+ * // Connect to an already-running runtime.
+ * new CopilotClientOptions().setConnection(RuntimeConnection.forUri("localhost:3000"));
+ * }
+ * + * @since 1.0.0 + */ +@CopilotExperimental +public abstract sealed class RuntimeConnection + permits StdioRuntimeConnection, TcpRuntimeConnection, UriRuntimeConnection, InProcessRuntimeConnection { + + RuntimeConnection() { + } + + /** + * Spawns a runtime child process and communicates over its stdin/stdout. This + * is the default when no connection is configured. + * + * @return a new stdio connection + */ + public static StdioRuntimeConnection forStdio() { + return new StdioRuntimeConnection(); + } + + /** + * Spawns a runtime child process at the given path and communicates over its + * stdin/stdout. + * + * @param path + * path to the runtime executable, or {@code null} to use the runtime + * discovered on the {@code PATH} + * @return a new stdio connection + */ + public static StdioRuntimeConnection forStdio(String path) { + return new StdioRuntimeConnection().setPath(path); + } + + /** + * Spawns a runtime child process that listens on a TCP socket and connects to + * it. + * + * @return a new TCP connection + */ + public static TcpRuntimeConnection forTcp() { + return new TcpRuntimeConnection(); + } + + /** + * Connects to an already-running runtime at the given URL. + * + * @param url + * URL of the runtime to connect to; accepts {@code "port"}, + * {@code "host:port"}, or a full URL + * @return a new URI connection + * @throws IllegalArgumentException + * if {@code url} is {@code null} or empty + */ + public static UriRuntimeConnection forUri(String url) { + return new UriRuntimeConnection(url); + } + + /** + * Hosts the runtime in-process by loading its native library and communicating + * over the C ABI — no child process is spawned by the SDK for JSON-RPC + * transport. + * + * @return a new in-process connection + */ + @CopilotExperimental + public static InProcessRuntimeConnection forInProcess() { + return new InProcessRuntimeConnection(); + } +} diff --git a/java/src/main/java/com/github/copilot/rpc/SectionOverride.java b/java/sdk/src/main/java/com/github/copilot/rpc/SectionOverride.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/SectionOverride.java rename to java/sdk/src/main/java/com/github/copilot/rpc/SectionOverride.java diff --git a/java/src/main/java/com/github/copilot/rpc/SectionOverrideAction.java b/java/sdk/src/main/java/com/github/copilot/rpc/SectionOverrideAction.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/SectionOverrideAction.java rename to java/sdk/src/main/java/com/github/copilot/rpc/SectionOverrideAction.java diff --git a/java/src/main/java/com/github/copilot/rpc/SendMessageRequest.java b/java/sdk/src/main/java/com/github/copilot/rpc/SendMessageRequest.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/SendMessageRequest.java rename to java/sdk/src/main/java/com/github/copilot/rpc/SendMessageRequest.java diff --git a/java/src/main/java/com/github/copilot/rpc/SendMessageResponse.java b/java/sdk/src/main/java/com/github/copilot/rpc/SendMessageResponse.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/SendMessageResponse.java rename to java/sdk/src/main/java/com/github/copilot/rpc/SendMessageResponse.java diff --git a/java/src/main/java/com/github/copilot/rpc/SessionCapabilities.java b/java/sdk/src/main/java/com/github/copilot/rpc/SessionCapabilities.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/SessionCapabilities.java rename to java/sdk/src/main/java/com/github/copilot/rpc/SessionCapabilities.java diff --git a/java/src/main/java/com/github/copilot/rpc/SessionConfig.java b/java/sdk/src/main/java/com/github/copilot/rpc/SessionConfig.java similarity index 90% rename from java/src/main/java/com/github/copilot/rpc/SessionConfig.java rename to java/sdk/src/main/java/com/github/copilot/rpc/SessionConfig.java index 0e02482de..1127e6777 100644 --- a/java/src/main/java/com/github/copilot/rpc/SessionConfig.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/SessionConfig.java @@ -57,7 +57,9 @@ public class SessionConfig { private List models; private Boolean enableSessionTelemetry; private Boolean enableCitations; + private Boolean enableFileChangeTracking; private SessionLimitsConfig sessionLimits; + private Boolean enableExperimentalMode; private Boolean skipCustomInstructions; private Boolean customAgentsLocalOnly; private Boolean coauthorEnabled; @@ -67,6 +69,7 @@ public class SessionConfig { private UserInputHandler onUserInputRequest; private SessionHooks hooks; private String workingDirectory; + private List additionalDirectories; private boolean streaming; private Boolean includeSubAgentStreamingEvents; private Map mcpServers; @@ -82,6 +85,7 @@ public class SessionConfig { private ToolSearchConfig toolSearch; private MemoryConfiguration memory; private List disabledSkills; + private List disabledMcpServers; private String configDirectory; private Boolean enableConfigDiscovery; private Boolean skipEmbeddingRetrieval; @@ -99,11 +103,13 @@ public class SessionConfig { private ExitPlanModeHandler onExitPlanMode; private AutoModeSwitchHandler onAutoModeSwitch; private boolean enableMcpApps; + private GitHubMcpToolConfig githubMcpToolConfig; private String gitHubToken; private String remoteSession; private CloudSessionOptions cloud; private CopilotExpAssignmentResponse expAssignments; private Boolean enableManagedSettings; + private ManagedSettings managedSettings; /** * Gets the custom session ID. @@ -177,7 +183,8 @@ public SessionConfig setModel(String model) { /** * Gets the reasoning effort level. * - * @return the reasoning effort level ("low", "medium", "high", or "xhigh") + * @return the reasoning effort level ("low", "medium", "high", "xhigh", or + * "max") */ public String getReasoningEffort() { return reasoningEffort; @@ -186,8 +193,8 @@ public String getReasoningEffort() { /** * Sets the reasoning effort level for models that support it. *

- * Valid values: "low", "medium", "high", "xhigh". Only applies to models where - * {@code capabilities.supports.reasoningEffort} is true. + * Valid values: "low", "medium", "high", "xhigh", "max". Only applies to models + * where {@code capabilities.supports.reasoningEffort} is true. * * @param reasoningEffort * the reasoning effort level @@ -550,6 +557,40 @@ public SessionConfig clearEnableCitations() { return this; } + /** + * Gets whether file change tracking is enabled for rewind and cumulative + * session diff. + * + * @return an {@link java.util.Optional} containing the setting, or + * {@link java.util.Optional#empty()} for the default + */ + @JsonIgnore + public Optional getEnableFileChangeTracking() { + return Optional.ofNullable(enableFileChangeTracking); + } + + /** + * Enables or disables file change tracking from the first turn. + * + * @param enableFileChangeTracking + * whether to enable file change tracking + * @return this config instance for method chaining + */ + public SessionConfig setEnableFileChangeTracking(boolean enableFileChangeTracking) { + this.enableFileChangeTracking = enableFileChangeTracking; + return this; + } + + /** + * Clears the file change tracking setting, reverting to the default behavior. + * + * @return this instance for method chaining + */ + public SessionConfig clearEnableFileChangeTracking() { + this.enableFileChangeTracking = null; + return this; + } + /** * Gets the limits for this session's current accounting window. * @@ -573,6 +614,52 @@ public SessionConfig setSessionLimits(SessionLimitsConfig sessionLimits) { return this; } + /** + * Clears the sessionLimits setting, reverting to the default behavior. + * + * @return this instance for method chaining + */ + @CopilotExperimental + public SessionConfig clearSessionLimits() { + this.sessionLimits = null; + return this; + } + + /** + * Controls whether the session enables experimental features. + * + * @return {@code true} when experimental features are enabled, {@code false} + * when they are disabled, or empty to use the mode-specific default + */ + @JsonIgnore + public Optional getEnableExperimentalMode() { + return Optional.ofNullable(enableExperimentalMode); + } + + /** + * Controls whether the session enables experimental features. + * + * @param enableExperimentalMode + * {@code true} to enable experimental features; {@code false} to + * disable them + * @return this config instance for method chaining + */ + public SessionConfig setEnableExperimentalMode(boolean enableExperimentalMode) { + this.enableExperimentalMode = enableExperimentalMode; + return this; + } + + /** + * Clears the enableExperimentalMode setting. In {@link CopilotClientMode#EMPTY + * EMPTY} mode this defaults to {@code false}; otherwise the runtime decides. + * + * @return this instance for method chaining + */ + public SessionConfig clearEnableExperimentalMode() { + this.enableExperimentalMode = null; + return this; + } + /** * Gets whether custom instruction file loading is suppressed. * @@ -631,11 +718,11 @@ public Optional getCustomAgentsLocalOnly() { * Sets whether custom-agent discovery is restricted to the session's local * working directory (no organisation-level discovery). *

- * This option is sent to the server via a {@code session.options.update} - * JSON-RPC call immediately after session creation. In - * {@link CopilotClientMode#EMPTY EMPTY} mode the default is {@code true} (local - * only); in {@link CopilotClientMode#COPILOT_CLI COPILOT_CLI} mode the value is - * forwarded only when explicitly set. + * This option is sent with the initial create request and maintained via + * {@code session.options.update}. In {@link CopilotClientMode#EMPTY EMPTY} mode + * the default is {@code true} (local only); in + * {@link CopilotClientMode#COPILOT_CLI COPILOT_CLI} mode the value is forwarded + * only when explicitly set. * * @param customAgentsLocalOnly * whether to restrict to local agents @@ -861,6 +948,27 @@ public SessionConfig setWorkingDirectory(String workingDirectory) { return this; } + /** + * Gets the directories the agent may access beyond the working directory. + * + * @return the additional directory paths + */ + public List getAdditionalDirectories() { + return additionalDirectories; + } + + /** + * Sets directories the agent may access beyond the working directory. + * + * @param additionalDirectories + * the additional directory paths + * @return this config instance for method chaining + */ + public SessionConfig setAdditionalDirectories(List additionalDirectories) { + this.additionalDirectories = additionalDirectories; + return this; + } + /** * Returns whether streaming is enabled. * @@ -1197,6 +1305,29 @@ public SessionConfig setDisabledSkills(List disabledSkills) { return this; } + /** + * Gets exact MCP server names disabled for this session. + * + * @return the disabled MCP server names, or {@code null} when none are disabled + */ + public List getDisabledMcpServers() { + return disabledMcpServers == null ? null : Collections.unmodifiableList(disabledMcpServers); + } + + /** + * Sets exact MCP server names to disable for this session. Disabled servers are + * not started or authenticated on create or cold resume; a resident resume + * cannot stop servers already running. + * + * @param disabledMcpServers + * the server names to disable + * @return this config for method chaining + */ + public SessionConfig setDisabledMcpServers(List disabledMcpServers) { + this.disabledMcpServers = disabledMcpServers; + return this; + } + /** * Gets the custom configuration directory. * @@ -1234,14 +1365,8 @@ public Optional getEnableConfigDiscovery() { } /** - * Sets whether to automatically discover MCP server configurations and skill - * directories from the working directory. - *

- * When {@code true}, the CLI scans the working directory for {@code .mcp.json}, - * {@code .vscode/mcp.json} and skill directories, and merges them with - * explicitly provided {@link #setMcpServers(Map)} and - * {@link #setSkillDirectories(List)}, with explicit values taking precedence on - * name collision. + * Enables runtime discovery of supported configuration. Explicitly supplied + * configuration takes precedence over discovered values. * * @param enableConfigDiscovery * {@code true} to enable discovery, {@code false} to disable @@ -1718,6 +1843,27 @@ public SessionConfig setEnableMcpApps(boolean enableMcpApps) { return this; } + /** + * Gets the configuration for the built-in GitHub MCP server. + * + * @return the GitHub MCP configuration, or {@code null} + */ + public GitHubMcpToolConfig getGitHubMcpToolConfig() { + return githubMcpToolConfig; + } + + /** + * Sets the configuration for the built-in GitHub MCP server. + * + * @param githubMcpToolConfig + * the GitHub MCP configuration + * @return this config instance for method chaining + */ + public SessionConfig setGitHubMcpToolConfig(GitHubMcpToolConfig githubMcpToolConfig) { + this.githubMcpToolConfig = githubMcpToolConfig; + return this; + } + /** * Gets the exit-plan-mode request handler. * @@ -1931,6 +2077,29 @@ public SessionConfig setEnableManagedSettings(boolean enableManagedSettings) { return this; } + /** + * Gets host-injected managed settings for this session. + * + * @return the managed settings, or {@code null} when unset + */ + public ManagedSettings getManagedSettings() { + return managedSettings; + } + + /** + * Supplies permissions-only managed settings at session startup. The runtime + * validates and composes this policy restrictively with self-fetched and device + * policy. Re-supply it on resume because it is not persisted. + * + * @param managedSettings + * the host-injected managed settings + * @return this config instance for method chaining + */ + public SessionConfig setManagedSettings(ManagedSettings managedSettings) { + this.managedSettings = managedSettings; + return this; + } + /** * Creates a shallow clone of this {@code SessionConfig} instance. *

@@ -1964,7 +2133,9 @@ public SessionConfig clone() { copy.models = this.models != null ? new ArrayList<>(this.models) : null; copy.enableSessionTelemetry = this.enableSessionTelemetry; copy.enableCitations = this.enableCitations; + copy.enableFileChangeTracking = this.enableFileChangeTracking; copy.sessionLimits = this.sessionLimits; + copy.enableExperimentalMode = this.enableExperimentalMode; copy.skipCustomInstructions = this.skipCustomInstructions; copy.customAgentsLocalOnly = this.customAgentsLocalOnly; copy.coauthorEnabled = this.coauthorEnabled; @@ -1973,6 +2144,9 @@ public SessionConfig clone() { copy.onUserInputRequest = this.onUserInputRequest; copy.hooks = this.hooks; copy.workingDirectory = this.workingDirectory; + copy.additionalDirectories = this.additionalDirectories != null + ? new ArrayList<>(this.additionalDirectories) + : null; copy.streaming = this.streaming; copy.includeSubAgentStreamingEvents = this.includeSubAgentStreamingEvents; copy.mcpServers = this.mcpServers != null ? new java.util.HashMap<>(this.mcpServers) : null; @@ -1989,6 +2163,7 @@ public SessionConfig clone() { copy.toolSearch = this.toolSearch; copy.memory = this.memory; copy.disabledSkills = this.disabledSkills != null ? new ArrayList<>(this.disabledSkills) : null; + copy.disabledMcpServers = this.disabledMcpServers != null ? new ArrayList<>(this.disabledMcpServers) : null; copy.configDirectory = this.configDirectory; copy.enableConfigDiscovery = this.enableConfigDiscovery; copy.skipEmbeddingRetrieval = this.skipEmbeddingRetrieval; @@ -2007,11 +2182,13 @@ public SessionConfig clone() { copy.onExitPlanMode = this.onExitPlanMode; copy.onAutoModeSwitch = this.onAutoModeSwitch; copy.enableMcpApps = this.enableMcpApps; + copy.githubMcpToolConfig = this.githubMcpToolConfig; copy.gitHubToken = this.gitHubToken; copy.remoteSession = this.remoteSession; copy.cloud = this.cloud; copy.expAssignments = this.expAssignments; copy.enableManagedSettings = this.enableManagedSettings; + copy.managedSettings = this.managedSettings; return copy; } } diff --git a/java/src/main/java/com/github/copilot/rpc/SessionContext.java b/java/sdk/src/main/java/com/github/copilot/rpc/SessionContext.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/SessionContext.java rename to java/sdk/src/main/java/com/github/copilot/rpc/SessionContext.java diff --git a/java/src/main/java/com/github/copilot/rpc/SessionEndHandler.java b/java/sdk/src/main/java/com/github/copilot/rpc/SessionEndHandler.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/SessionEndHandler.java rename to java/sdk/src/main/java/com/github/copilot/rpc/SessionEndHandler.java diff --git a/java/src/main/java/com/github/copilot/rpc/SessionEndHookInput.java b/java/sdk/src/main/java/com/github/copilot/rpc/SessionEndHookInput.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/SessionEndHookInput.java rename to java/sdk/src/main/java/com/github/copilot/rpc/SessionEndHookInput.java diff --git a/java/src/main/java/com/github/copilot/rpc/SessionEndHookOutput.java b/java/sdk/src/main/java/com/github/copilot/rpc/SessionEndHookOutput.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/SessionEndHookOutput.java rename to java/sdk/src/main/java/com/github/copilot/rpc/SessionEndHookOutput.java diff --git a/java/src/main/java/com/github/copilot/rpc/SessionHooks.java b/java/sdk/src/main/java/com/github/copilot/rpc/SessionHooks.java similarity index 80% rename from java/src/main/java/com/github/copilot/rpc/SessionHooks.java rename to java/sdk/src/main/java/com/github/copilot/rpc/SessionHooks.java index f13e08131..e476f888e 100644 --- a/java/src/main/java/com/github/copilot/rpc/SessionHooks.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/SessionHooks.java @@ -42,8 +42,10 @@ public class SessionHooks { private PostToolUseHandler onPostToolUse; private PostToolUseFailureHandler onPostToolUseFailure; private UserPromptSubmittedHandler onUserPromptSubmitted; + private UserPromptTransformedHandler onUserPromptTransformed; private SessionStartHandler onSessionStart; private SessionEndHandler onSessionEnd; + private AgentStopHandler onAgentStop; /** * Gets the pre-tool-use handler. @@ -160,6 +162,29 @@ public SessionHooks setOnUserPromptSubmitted(UserPromptSubmittedHandler onUserPr return this; } + /** + * Gets the user-prompt-transformed handler. + * + * @return the handler, or {@code null} if not set + * @since 1.0.11 + */ + public UserPromptTransformedHandler getOnUserPromptTransformed() { + return onUserPromptTransformed; + } + + /** + * Sets the handler called after the runtime transforms a submitted prompt. + * + * @param onUserPromptTransformed + * the handler + * @return this instance for method chaining + * @since 1.0.11 + */ + public SessionHooks setOnUserPromptTransformed(UserPromptTransformedHandler onUserPromptTransformed) { + this.onUserPromptTransformed = onUserPromptTransformed; + return this; + } + /** * Gets the session-start handler. * @@ -206,6 +231,29 @@ public SessionHooks setOnSessionEnd(SessionEndHandler onSessionEnd) { return this; } + /** + * Gets the agent-stop handler. + * + * @return the handler, or {@code null} if not set + * @since 1.0.9 + */ + public AgentStopHandler getOnAgentStop() { + return onAgentStop; + } + + /** + * Sets the handler called when the top-level agent reaches a natural stop. + * + * @param onAgentStop + * the handler + * @return this instance for method chaining + * @since 1.0.9 + */ + public SessionHooks setOnAgentStop(AgentStopHandler onAgentStop) { + this.onAgentStop = onAgentStop; + return this; + } + /** * Returns whether any hooks are registered. * @@ -213,6 +261,7 @@ public SessionHooks setOnSessionEnd(SessionEndHandler onSessionEnd) { */ public boolean hasHooks() { return onPreToolUse != null || onPreMcpToolCall != null || onPostToolUse != null || onPostToolUseFailure != null - || onUserPromptSubmitted != null || onSessionStart != null || onSessionEnd != null; + || onUserPromptSubmitted != null || onUserPromptTransformed != null || onSessionStart != null + || onSessionEnd != null || onAgentStop != null; } } diff --git a/java/src/main/java/com/github/copilot/rpc/SessionLifecycleEvent.java b/java/sdk/src/main/java/com/github/copilot/rpc/SessionLifecycleEvent.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/SessionLifecycleEvent.java rename to java/sdk/src/main/java/com/github/copilot/rpc/SessionLifecycleEvent.java diff --git a/java/src/main/java/com/github/copilot/rpc/SessionLifecycleEventMetadata.java b/java/sdk/src/main/java/com/github/copilot/rpc/SessionLifecycleEventMetadata.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/SessionLifecycleEventMetadata.java rename to java/sdk/src/main/java/com/github/copilot/rpc/SessionLifecycleEventMetadata.java diff --git a/java/src/main/java/com/github/copilot/rpc/SessionLifecycleEventTypes.java b/java/sdk/src/main/java/com/github/copilot/rpc/SessionLifecycleEventTypes.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/SessionLifecycleEventTypes.java rename to java/sdk/src/main/java/com/github/copilot/rpc/SessionLifecycleEventTypes.java diff --git a/java/src/main/java/com/github/copilot/rpc/SessionLifecycleHandler.java b/java/sdk/src/main/java/com/github/copilot/rpc/SessionLifecycleHandler.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/SessionLifecycleHandler.java rename to java/sdk/src/main/java/com/github/copilot/rpc/SessionLifecycleHandler.java diff --git a/java/src/main/java/com/github/copilot/rpc/SessionListFilter.java b/java/sdk/src/main/java/com/github/copilot/rpc/SessionListFilter.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/SessionListFilter.java rename to java/sdk/src/main/java/com/github/copilot/rpc/SessionListFilter.java diff --git a/java/src/main/java/com/github/copilot/rpc/SessionMetadata.java b/java/sdk/src/main/java/com/github/copilot/rpc/SessionMetadata.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/SessionMetadata.java rename to java/sdk/src/main/java/com/github/copilot/rpc/SessionMetadata.java diff --git a/java/src/main/java/com/github/copilot/rpc/SessionStartHandler.java b/java/sdk/src/main/java/com/github/copilot/rpc/SessionStartHandler.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/SessionStartHandler.java rename to java/sdk/src/main/java/com/github/copilot/rpc/SessionStartHandler.java diff --git a/java/src/main/java/com/github/copilot/rpc/SessionStartHookInput.java b/java/sdk/src/main/java/com/github/copilot/rpc/SessionStartHookInput.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/SessionStartHookInput.java rename to java/sdk/src/main/java/com/github/copilot/rpc/SessionStartHookInput.java diff --git a/java/src/main/java/com/github/copilot/rpc/SessionStartHookOutput.java b/java/sdk/src/main/java/com/github/copilot/rpc/SessionStartHookOutput.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/SessionStartHookOutput.java rename to java/sdk/src/main/java/com/github/copilot/rpc/SessionStartHookOutput.java diff --git a/java/src/main/java/com/github/copilot/rpc/SessionUiApi.java b/java/sdk/src/main/java/com/github/copilot/rpc/SessionUiApi.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/SessionUiApi.java rename to java/sdk/src/main/java/com/github/copilot/rpc/SessionUiApi.java diff --git a/java/src/main/java/com/github/copilot/rpc/SessionUiCapabilities.java b/java/sdk/src/main/java/com/github/copilot/rpc/SessionUiCapabilities.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/SessionUiCapabilities.java rename to java/sdk/src/main/java/com/github/copilot/rpc/SessionUiCapabilities.java diff --git a/java/src/main/java/com/github/copilot/rpc/SetForegroundSessionRequest.java b/java/sdk/src/main/java/com/github/copilot/rpc/SetForegroundSessionRequest.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/SetForegroundSessionRequest.java rename to java/sdk/src/main/java/com/github/copilot/rpc/SetForegroundSessionRequest.java diff --git a/java/src/main/java/com/github/copilot/rpc/SetForegroundSessionResponse.java b/java/sdk/src/main/java/com/github/copilot/rpc/SetForegroundSessionResponse.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/SetForegroundSessionResponse.java rename to java/sdk/src/main/java/com/github/copilot/rpc/SetForegroundSessionResponse.java diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/StdioRuntimeConnection.java b/java/sdk/src/main/java/com/github/copilot/rpc/StdioRuntimeConnection.java new file mode 100644 index 000000000..7d0923e0f --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/rpc/StdioRuntimeConnection.java @@ -0,0 +1,71 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.rpc; + +import java.util.ArrayList; +import java.util.List; + +import com.github.copilot.CopilotExperimental; + +/** + * Spawns a runtime child process and communicates over its stdin/stdout. + * Construct with {@link RuntimeConnection#forStdio()} or + * {@link RuntimeConnection#forStdio(String)}. + * + * @since 1.0.0 + */ +@CopilotExperimental +public final class StdioRuntimeConnection extends RuntimeConnection { + + private String path; + private List args; + + StdioRuntimeConnection() { + } + + /** + * Returns the path to the runtime executable. + * + * @return the path, or {@code null} to use the runtime discovered on the + * {@code PATH} + */ + public String getPath() { + return path; + } + + /** + * Sets the path to the runtime executable. + * + * @param path + * the path, or {@code null} to use the runtime discovered on the + * {@code PATH} + * @return this instance for method chaining + */ + public StdioRuntimeConnection setPath(String path) { + this.path = path; + return this; + } + + /** + * Returns the extra command-line arguments passed to the runtime process. + * + * @return the arguments, or {@code null} if none are configured + */ + public List getArgs() { + return args; + } + + /** + * Sets extra command-line arguments passed to the runtime process. + * + * @param args + * the arguments, or {@code null} for none + * @return this instance for method chaining + */ + public StdioRuntimeConnection setArgs(List args) { + this.args = args == null ? null : new ArrayList<>(args); + return this; + } +} diff --git a/java/src/main/java/com/github/copilot/rpc/SystemMessageConfig.java b/java/sdk/src/main/java/com/github/copilot/rpc/SystemMessageConfig.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/SystemMessageConfig.java rename to java/sdk/src/main/java/com/github/copilot/rpc/SystemMessageConfig.java diff --git a/java/src/main/java/com/github/copilot/rpc/SystemMessageSections.java b/java/sdk/src/main/java/com/github/copilot/rpc/SystemMessageSections.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/SystemMessageSections.java rename to java/sdk/src/main/java/com/github/copilot/rpc/SystemMessageSections.java diff --git a/java/src/main/java/com/github/copilot/rpc/SystemPromptSections.java b/java/sdk/src/main/java/com/github/copilot/rpc/SystemPromptSections.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/SystemPromptSections.java rename to java/sdk/src/main/java/com/github/copilot/rpc/SystemPromptSections.java diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/TcpRuntimeConnection.java b/java/sdk/src/main/java/com/github/copilot/rpc/TcpRuntimeConnection.java new file mode 100644 index 000000000..648321a21 --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/rpc/TcpRuntimeConnection.java @@ -0,0 +1,116 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.rpc; + +import java.util.ArrayList; +import java.util.List; + +import com.github.copilot.CopilotExperimental; + +/** + * Spawns a runtime child process listening on a TCP socket and connects to it. + * Construct with {@link RuntimeConnection#forTcp()}. + * + * @since 1.0.0 + */ +@CopilotExperimental +public final class TcpRuntimeConnection extends RuntimeConnection { + + private String path; + private int port; + private String connectionToken; + private List args; + + TcpRuntimeConnection() { + } + + /** + * Returns the path to the runtime executable. + * + * @return the path, or {@code null} to use the runtime discovered on the + * {@code PATH} + */ + public String getPath() { + return path; + } + + /** + * Sets the path to the runtime executable. + * + * @param path + * the path, or {@code null} to use the runtime discovered on the + * {@code PATH} + * @return this instance for method chaining + */ + public TcpRuntimeConnection setPath(String path) { + this.path = path; + return this; + } + + /** + * Returns the TCP port the spawned runtime listens on. + * + * @return the port, or {@code 0} to auto-allocate a free port + */ + public int getPort() { + return port; + } + + /** + * Sets the TCP port the spawned runtime listens on. + * + * @param port + * the port, or {@code 0} (the default) to auto-allocate a free port + * @return this instance for method chaining + */ + public TcpRuntimeConnection setPort(int port) { + this.port = port; + return this; + } + + /** + * Returns the shared secret the SDK sends to the spawned runtime to + * authenticate the TCP connection. + * + * @return the token, or {@code null} to generate one automatically + */ + public String getConnectionToken() { + return connectionToken; + } + + /** + * Sets the shared secret the SDK sends to the spawned runtime to authenticate + * the TCP connection. + * + * @param connectionToken + * the token, or {@code null} to generate one automatically + * @return this instance for method chaining + */ + public TcpRuntimeConnection setConnectionToken(String connectionToken) { + this.connectionToken = connectionToken; + return this; + } + + /** + * Returns the extra command-line arguments passed to the runtime process. + * + * @return the arguments, or {@code null} if none are configured + */ + public List getArgs() { + return args; + } + + /** + * Sets extra command-line arguments passed to the runtime process. + * + * @param args + * the arguments, or {@code null} for none + * @return this instance for method chaining + */ + public TcpRuntimeConnection setArgs(List args) { + this.args = args == null ? null : new ArrayList<>(args); + return this; + } +} diff --git a/java/src/main/java/com/github/copilot/rpc/TelemetryConfig.java b/java/sdk/src/main/java/com/github/copilot/rpc/TelemetryConfig.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/TelemetryConfig.java rename to java/sdk/src/main/java/com/github/copilot/rpc/TelemetryConfig.java diff --git a/java/src/main/java/com/github/copilot/rpc/ToolBinaryResult.java b/java/sdk/src/main/java/com/github/copilot/rpc/ToolBinaryResult.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/ToolBinaryResult.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ToolBinaryResult.java diff --git a/java/src/main/java/com/github/copilot/rpc/ToolDefer.java b/java/sdk/src/main/java/com/github/copilot/rpc/ToolDefer.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/ToolDefer.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ToolDefer.java diff --git a/java/src/main/java/com/github/copilot/rpc/ToolDefinition.java b/java/sdk/src/main/java/com/github/copilot/rpc/ToolDefinition.java similarity index 93% rename from java/src/main/java/com/github/copilot/rpc/ToolDefinition.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ToolDefinition.java index ccf0ef530..de274b66a 100644 --- a/java/src/main/java/com/github/copilot/rpc/ToolDefinition.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/ToolDefinition.java @@ -78,6 +78,11 @@ * @param metadata * opaque, host-defined metadata; keys are namespaced and not part of * the stable public API; {@code null} when unset + * @param isTerminal + * when {@code true}, a successful call to this tool ends the agent + * turn: the runtime's tool phase halts instead of feeding the result + * back to the model for another round; {@code null} or {@code false} + * leaves the turn running * @see SessionConfig#setTools(java.util.List) * @see ToolHandler * @since 1.0.0 @@ -87,13 +92,13 @@ public record ToolDefinition(@JsonProperty("name") String name, @JsonProperty("d @JsonProperty("parameters") Object parameters, @JsonIgnore ToolHandler handler, @JsonProperty("overridesBuiltInTool") Boolean overridesBuiltInTool, @JsonProperty("skipPermission") Boolean skipPermission, @JsonProperty("defer") ToolDefer defer, - @JsonProperty("metadata") Map metadata) { + @JsonProperty("metadata") Map metadata, @JsonProperty("isTerminal") Boolean isTerminal) { /** - * Creates a tool definition without a {@code metadata} bag. + * Creates a tool definition without a {@code metadata} bag or terminality hint. *

* Convenience overload equivalent to the canonical constructor with - * {@code metadata} set to {@code null}. + * {@code metadata} and {@code isTerminal} set to {@code null}. * * @param name * the unique name of the tool @@ -114,7 +119,37 @@ public record ToolDefinition(@JsonProperty("name") String name, @JsonProperty("d */ public ToolDefinition(String name, String description, Object parameters, ToolHandler handler, Boolean overridesBuiltInTool, Boolean skipPermission, ToolDefer defer) { - this(name, description, parameters, handler, overridesBuiltInTool, skipPermission, defer, null); + this(name, description, parameters, handler, overridesBuiltInTool, skipPermission, defer, null, null); + } + + /** + * Creates a tool definition without a terminality hint. + *

+ * Convenience overload equivalent to the canonical constructor with + * {@code isTerminal} set to {@code null}. + * + * @param name + * the unique name of the tool + * @param description + * a description of what the tool does + * @param parameters + * the JSON Schema for the tool's parameters + * @param handler + * the handler function to execute when invoked + * @param overridesBuiltInTool + * whether this tool overrides a built-in tool; {@code null} for the + * default + * @param skipPermission + * whether the tool may run without a permission check; {@code null} + * for the default + * @param defer + * the deferral mode; {@code null} lets the runtime decide + * @param metadata + * the opaque, host-defined metadata; {@code null} when unset + */ + public ToolDefinition(String name, String description, Object parameters, ToolHandler handler, + Boolean overridesBuiltInTool, Boolean skipPermission, ToolDefer defer, Map metadata) { + this(name, description, parameters, handler, overridesBuiltInTool, skipPermission, defer, metadata, null); } /** @@ -304,7 +339,8 @@ public static List fromClass(Class clazz) { */ @CopilotExperimental public ToolDefinition overridesBuiltInTool(boolean value) { - return new ToolDefinition(name, description, parameters, handler, value, skipPermission, defer, metadata); + return new ToolDefinition(name, description, parameters, handler, value, skipPermission, defer, metadata, + isTerminal); } /** @@ -318,7 +354,8 @@ public ToolDefinition overridesBuiltInTool(boolean value) { */ @CopilotExperimental public ToolDefinition skipPermission(boolean value) { - return new ToolDefinition(name, description, parameters, handler, overridesBuiltInTool, value, defer, metadata); + return new ToolDefinition(name, description, parameters, handler, overridesBuiltInTool, value, defer, metadata, + isTerminal); } /** @@ -333,7 +370,7 @@ public ToolDefinition skipPermission(boolean value) { @CopilotExperimental public ToolDefinition defer(ToolDefer value) { return new ToolDefinition(name, description, parameters, handler, overridesBuiltInTool, skipPermission, value, - metadata); + metadata, isTerminal); } /** @@ -348,7 +385,22 @@ public ToolDefinition defer(ToolDefer value) { @CopilotExperimental public ToolDefinition metadata(Map value) { return new ToolDefinition(name, description, parameters, handler, overridesBuiltInTool, skipPermission, defer, - value); + value, isTerminal); + } + + /** + * Returns a copy with the {@code isTerminal} flag set. + * + * @param value + * {@code true} to end the agent turn after a successful call to this + * tool + * @return a new {@code ToolDefinition} with the flag applied + * @since 1.0.11 + */ + @CopilotExperimental + public ToolDefinition isTerminal(boolean value) { + return new ToolDefinition(name, description, parameters, handler, overridesBuiltInTool, skipPermission, defer, + metadata, value); } // ------------------------------------------------------------------ diff --git a/java/src/main/java/com/github/copilot/rpc/ToolHandler.java b/java/sdk/src/main/java/com/github/copilot/rpc/ToolHandler.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/ToolHandler.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ToolHandler.java diff --git a/java/src/main/java/com/github/copilot/rpc/ToolInvocation.java b/java/sdk/src/main/java/com/github/copilot/rpc/ToolInvocation.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/ToolInvocation.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ToolInvocation.java diff --git a/java/src/main/java/com/github/copilot/rpc/ToolResultObject.java b/java/sdk/src/main/java/com/github/copilot/rpc/ToolResultObject.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/ToolResultObject.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ToolResultObject.java diff --git a/java/src/main/java/com/github/copilot/rpc/ToolSearchConfig.java b/java/sdk/src/main/java/com/github/copilot/rpc/ToolSearchConfig.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/ToolSearchConfig.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ToolSearchConfig.java diff --git a/java/src/main/java/com/github/copilot/rpc/ToolSet.java b/java/sdk/src/main/java/com/github/copilot/rpc/ToolSet.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/ToolSet.java rename to java/sdk/src/main/java/com/github/copilot/rpc/ToolSet.java diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/UriRuntimeConnection.java b/java/sdk/src/main/java/com/github/copilot/rpc/UriRuntimeConnection.java new file mode 100644 index 000000000..c26098584 --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/rpc/UriRuntimeConnection.java @@ -0,0 +1,57 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.rpc; + +import com.github.copilot.CopilotExperimental; + +/** + * Connects to an already-running runtime at the configured URL. Construct with + * {@link RuntimeConnection#forUri(String)}. + * + * @since 1.0.0 + */ +@CopilotExperimental +public final class UriRuntimeConnection extends RuntimeConnection { + + private final String url; + private String connectionToken; + + UriRuntimeConnection(String url) { + if (url == null || url.isEmpty()) { + throw new IllegalArgumentException("UriRuntimeConnection url must be a non-empty string"); + } + this.url = url; + } + + /** + * Returns the URL of the runtime to connect to. + * + * @return the URL; accepts {@code "port"}, {@code "host:port"}, or a full URL + */ + public String getUrl() { + return url; + } + + /** + * Returns the shared secret used to authenticate the connection. + * + * @return the token, or {@code null} if the runtime does not require one + */ + public String getConnectionToken() { + return connectionToken; + } + + /** + * Sets the shared secret used to authenticate the connection. + * + * @param connectionToken + * the token, or {@code null} if the runtime does not require one + * @return this instance for method chaining + */ + public UriRuntimeConnection setConnectionToken(String connectionToken) { + this.connectionToken = connectionToken; + return this; + } +} diff --git a/java/src/main/java/com/github/copilot/rpc/UserInputHandler.java b/java/sdk/src/main/java/com/github/copilot/rpc/UserInputHandler.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/UserInputHandler.java rename to java/sdk/src/main/java/com/github/copilot/rpc/UserInputHandler.java diff --git a/java/src/main/java/com/github/copilot/rpc/UserInputInvocation.java b/java/sdk/src/main/java/com/github/copilot/rpc/UserInputInvocation.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/UserInputInvocation.java rename to java/sdk/src/main/java/com/github/copilot/rpc/UserInputInvocation.java diff --git a/java/src/main/java/com/github/copilot/rpc/UserInputRequest.java b/java/sdk/src/main/java/com/github/copilot/rpc/UserInputRequest.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/UserInputRequest.java rename to java/sdk/src/main/java/com/github/copilot/rpc/UserInputRequest.java diff --git a/java/src/main/java/com/github/copilot/rpc/UserInputResponse.java b/java/sdk/src/main/java/com/github/copilot/rpc/UserInputResponse.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/UserInputResponse.java rename to java/sdk/src/main/java/com/github/copilot/rpc/UserInputResponse.java diff --git a/java/src/main/java/com/github/copilot/rpc/UserPromptSubmittedHandler.java b/java/sdk/src/main/java/com/github/copilot/rpc/UserPromptSubmittedHandler.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/UserPromptSubmittedHandler.java rename to java/sdk/src/main/java/com/github/copilot/rpc/UserPromptSubmittedHandler.java diff --git a/java/src/main/java/com/github/copilot/rpc/UserPromptSubmittedHookInput.java b/java/sdk/src/main/java/com/github/copilot/rpc/UserPromptSubmittedHookInput.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/UserPromptSubmittedHookInput.java rename to java/sdk/src/main/java/com/github/copilot/rpc/UserPromptSubmittedHookInput.java diff --git a/java/src/main/java/com/github/copilot/rpc/UserPromptSubmittedHookOutput.java b/java/sdk/src/main/java/com/github/copilot/rpc/UserPromptSubmittedHookOutput.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/UserPromptSubmittedHookOutput.java rename to java/sdk/src/main/java/com/github/copilot/rpc/UserPromptSubmittedHookOutput.java diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/UserPromptTransformedHandler.java b/java/sdk/src/main/java/com/github/copilot/rpc/UserPromptTransformedHandler.java new file mode 100644 index 000000000..ac8496078 --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/rpc/UserPromptTransformedHandler.java @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.rpc; + +import java.util.concurrent.CompletableFuture; + +/** + * Handler for user-prompt-transformed hooks. + * + * @since 1.0.11 + */ +@FunctionalInterface +public interface UserPromptTransformedHandler { + + /** + * Handles a transformed user prompt before it is stored or sent to the model. + * + * @param input + * the hook input + * @param invocation + * metadata about the hook invocation + * @return a future resolving to the hook output, or {@code null} + */ + CompletableFuture handle(UserPromptTransformedHookInput input, + HookInvocation invocation); +} diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/UserPromptTransformedHookInput.java b/java/sdk/src/main/java/com/github/copilot/rpc/UserPromptTransformedHookInput.java new file mode 100644 index 000000000..ea1759658 --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/rpc/UserPromptTransformedHookInput.java @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.rpc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Input for user-prompt-transformed hooks. + * + * @param sessionId + * the runtime session ID + * @param timestamp + * Unix timestamp in milliseconds + * @param cwd + * the current working directory + * @param prompt + * the prompt after user-prompt-submitted hooks + * @param transformedPrompt + * the model-facing prompt after runtime transformations + * @since 1.0.11 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public record UserPromptTransformedHookInput(@JsonProperty("sessionId") String sessionId, + @JsonProperty("timestamp") long timestamp, @JsonProperty("cwd") String cwd, + @JsonProperty("prompt") String prompt, @JsonProperty("transformedPrompt") String transformedPrompt) { +} diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/UserPromptTransformedHookOutput.java b/java/sdk/src/main/java/com/github/copilot/rpc/UserPromptTransformedHookOutput.java new file mode 100644 index 000000000..615f4ea7b --- /dev/null +++ b/java/sdk/src/main/java/com/github/copilot/rpc/UserPromptTransformedHookOutput.java @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.rpc; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Output for user-prompt-transformed hooks. + * + * @param modifiedTransformedPrompt + * replacement model-facing prompt to persist and send to the model + * @since 1.0.11 + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public record UserPromptTransformedHookOutput( + @JsonProperty("modifiedTransformedPrompt") String modifiedTransformedPrompt) { +} diff --git a/java/src/main/java/com/github/copilot/rpc/package-info.java b/java/sdk/src/main/java/com/github/copilot/rpc/package-info.java similarity index 100% rename from java/src/main/java/com/github/copilot/rpc/package-info.java rename to java/sdk/src/main/java/com/github/copilot/rpc/package-info.java diff --git a/java/src/main/java/com/github/copilot/tool/CopilotTool.java b/java/sdk/src/main/java/com/github/copilot/tool/CopilotTool.java similarity index 97% rename from java/src/main/java/com/github/copilot/tool/CopilotTool.java rename to java/sdk/src/main/java/com/github/copilot/tool/CopilotTool.java index db9e3ca62..28cd75928 100644 --- a/java/src/main/java/com/github/copilot/tool/CopilotTool.java +++ b/java/sdk/src/main/java/com/github/copilot/tool/CopilotTool.java @@ -48,6 +48,9 @@ /** Whether to skip permission checks. */ boolean skipPermission() default false; + /** Whether a successful call to this tool ends the agent turn. */ + boolean isTerminal() default false; + /** Defer configuration for this tool. */ ToolDefer defer() default ToolDefer.NONE; diff --git a/java/src/main/java/com/github/copilot/tool/CopilotToolMetadataProvider.java b/java/sdk/src/main/java/com/github/copilot/tool/CopilotToolMetadataProvider.java similarity index 100% rename from java/src/main/java/com/github/copilot/tool/CopilotToolMetadataProvider.java rename to java/sdk/src/main/java/com/github/copilot/tool/CopilotToolMetadataProvider.java diff --git a/java/src/main/java/com/github/copilot/tool/CopilotToolParam.java b/java/sdk/src/main/java/com/github/copilot/tool/CopilotToolParam.java similarity index 73% rename from java/src/main/java/com/github/copilot/tool/CopilotToolParam.java rename to java/sdk/src/main/java/com/github/copilot/tool/CopilotToolParam.java index 0b667d9d7..144ea2e61 100644 --- a/java/src/main/java/com/github/copilot/tool/CopilotToolParam.java +++ b/java/sdk/src/main/java/com/github/copilot/tool/CopilotToolParam.java @@ -47,4 +47,22 @@ /** Optional default value when the argument is omitted. */ String defaultValue() default ""; + + /** + * Optional explicit JSON Schema for this parameter as a JSON string literal. + * When non-empty, bypasses automatic schema generation from the parameter type. + * The value must be a valid JSON object string. + * + *

+ * Example: + * + *

+     * @CopilotTool("Schedule meeting")
+     * public String schedule(
+     * 		@CopilotToolParam(value = "When to meet", schema = "{\"type\":\"string\",\"format\":\"date-time\"}") MyCustomDateTime when) {
+     * 	// ...
+     * }
+     * 
+ */ + String schema() default ""; } diff --git a/java/src/main/java/com/github/copilot/tool/CopilotToolProcessor.java b/java/sdk/src/main/java/com/github/copilot/tool/CopilotToolProcessor.java similarity index 73% rename from java/src/main/java/com/github/copilot/tool/CopilotToolProcessor.java rename to java/sdk/src/main/java/com/github/copilot/tool/CopilotToolProcessor.java index 03af4a7cd..f88c1ac7c 100644 --- a/java/src/main/java/com/github/copilot/tool/CopilotToolProcessor.java +++ b/java/sdk/src/main/java/com/github/copilot/tool/CopilotToolProcessor.java @@ -99,6 +99,20 @@ public boolean process(Set annotations, RoundEnvironment "@CopilotToolParam(required=false) primitive parameters must provide defaultValue or use a boxed/Optional type", param); } + if (paramAnnotation != null && !paramAnnotation.schema().isEmpty() + && !paramAnnotation.defaultValue().isEmpty()) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, + "@CopilotToolParam cannot have both schema and defaultValue — express defaults inside the schema if needed", + param); + } + if (paramAnnotation != null && !paramAnnotation.schema().isEmpty()) { + String schemaJson = paramAnnotation.schema().trim(); + if (!schemaJson.startsWith("{") || !schemaJson.endsWith("}")) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, + "@CopilotToolParam schema must be a valid JSON object string (must start with '{' and end with '}')", + param); + } + } } if (toolInvocationParamCount > 1) { processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, @@ -118,6 +132,11 @@ public boolean process(Set annotations, RoundEnvironment "@CopilotToolParam(defaultValue=...) is not supported on single-record tool parameters; use record component defaults or a non-record parameter", singleParam); } + if (!paramAnnotation.schema().isEmpty()) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, + "@CopilotToolParam(schema=...) is not supported on single-record tool parameters", + singleParam); + } if (!paramAnnotation.name().isEmpty() || !paramAnnotation.value().isEmpty() || !paramAnnotation.required()) { processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, @@ -127,6 +146,24 @@ public boolean process(Set annotations, RoundEnvironment } } } + + // Validate blank @CopilotToolParam descriptions (exempt single-record wrappers) + boolean isSingleRecordWrapper = schemaParameters.size() == 1 && isRecord(schemaParameters.get(0).asType()); + for (VariableElement param : schemaParameters) { + if (isSingleRecordWrapper && param.equals(schemaParameters.get(0))) { + continue; + } + CopilotToolParam paramAnnotation = param.getAnnotation(CopilotToolParam.class); + if (paramAnnotation != null && paramAnnotation.value().isBlank()) { + TypeElement enclosingClass = (TypeElement) method.getEnclosingElement(); + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, + "@CopilotToolParam on parameter '" + param.getSimpleName() + "' in '" + + enclosingClass.getSimpleName() + "." + method.getSimpleName() + + "' has a blank value (description). " + + "Descriptions are required so the LLM can correctly select and invoke the tool", + param); + } + } } // Group methods by enclosing type @@ -210,6 +247,21 @@ private void writeMetaClass(PrintWriter out, String packageName, String simpleCl out.println(); } + if (needsJsonSourceHelpers(methods)) { + out.println(" private static Map mapOfNullable(Object... entries) {"); + out.println(" var result = new LinkedHashMap();"); + out.println(" for (int i = 0; i < entries.length; i += 2) {"); + out.println(" result.put((String) entries[i], entries[i + 1]);"); + out.println(" }"); + out.println(" return Collections.unmodifiableMap(result);"); + out.println(" }"); + out.println(); + out.println(" private static List listOfNullable(Object... items) {"); + out.println(" return Collections.unmodifiableList(Arrays.asList(items));"); + out.println(" }"); + out.println(); + } + // definitions method out.println(" @Override"); out.println(" @SuppressWarnings({\"unchecked\", \"rawtypes\"})"); @@ -245,6 +297,18 @@ private boolean needsWithMetaHelper(List methods) { return false; } + private boolean needsJsonSourceHelpers(List methods) { + for (ExecutableElement method : methods) { + for (VariableElement param : method.getParameters()) { + CopilotToolParam paramAnnotation = param.getAnnotation(CopilotToolParam.class); + if (paramAnnotation != null && !paramAnnotation.schema().isEmpty()) { + return true; + } + } + } + return false; + } + private void writeToolDefinition(PrintWriter out, ExecutableElement method) { CopilotTool annotation = method.getAnnotation(CopilotTool.class); String toolName = annotation.name().isEmpty() @@ -253,6 +317,7 @@ private void writeToolDefinition(PrintWriter out, ExecutableElement method) { String description = annotation.value(); boolean overridesBuiltIn = annotation.overridesBuiltInTool(); boolean skipPermission = annotation.skipPermission(); + boolean isTerminal = annotation.isTerminal(); com.github.copilot.rpc.ToolDefer defer = annotation.defer(); // Generate schema with @CopilotToolParam metadata (descriptions, names, @@ -265,6 +330,7 @@ private void writeToolDefinition(PrintWriter out, ExecutableElement method) { // Use the record constructor directly so all flags apply independently String overridesArg = overridesBuiltIn ? "Boolean.TRUE" : "null"; String skipPermArg = skipPermission ? "Boolean.TRUE" : "null"; + String isTerminalArg = isTerminal ? "Boolean.TRUE" : "null"; String deferArg = defer != com.github.copilot.rpc.ToolDefer.NONE ? "ToolDefer." + defer.name() : "null"; out.println(" new ToolDefinition("); @@ -277,7 +343,8 @@ private void writeToolDefinition(PrintWriter out, ExecutableElement method) { out.println(" " + overridesArg + ","); out.println(" " + skipPermArg + ","); out.println(" " + deferArg + ","); - out.println(" " + metadataSource(annotation)); + out.println(" " + metadataSource(annotation) + ","); + out.println(" " + isTerminalArg); out.print(" )"); } @@ -338,8 +405,19 @@ private String generateSchemaWithParamMetadata(List p CopilotToolParam paramAnnotation = param.getAnnotation(CopilotToolParam.class); // Generate the type schema for this parameter - String typeSchema = schemaGenerator.generateSchemaSource(paramType, processingEnv.getTypeUtils(), - processingEnv.getElementUtils()); + String typeSchema; + if (paramAnnotation != null && !paramAnnotation.schema().isEmpty()) { + try { + typeSchema = jsonToMapOfSource(paramAnnotation.schema()); + } catch (IllegalArgumentException e) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, + "@CopilotToolParam schema is not valid JSON: " + e.getMessage(), param); + continue; + } + } else { + typeSchema = schemaGenerator.generateSchemaSource(paramType, processingEnv.getTypeUtils(), + processingEnv.getElementUtils()); + } // Build property schema with description and default if present String propertySchema = buildPropertySchema(typeSchema, paramAnnotation, paramType); @@ -852,11 +930,289 @@ static String toSnakeCase(String name) { return sb.toString(); } + // ------------------------------------------------------------------ + // JSON-to-Java source code conversion + // ------------------------------------------------------------------ + + /** + * Converts a JSON object string to a Java source expression. Supports nested + * objects, arrays, strings, numbers, booleans, and null. + */ + static String jsonToMapOfSource(String json) { + JsonToSourceConverter converter = new JsonToSourceConverter(json); + String result = converter.parseObject(); + converter.skipWhitespace(); + if (converter.pos < json.length()) { + throw new IllegalArgumentException("Unexpected trailing content at position " + converter.pos + ": '" + + json.substring(converter.pos) + "'"); + } + return result; + } + + /** + * Minimal recursive-descent JSON parser that produces helper calls and literal + * Java source expressions from a JSON string. Only used at compile time by the + * annotation processor. + */ + private static final class JsonToSourceConverter { + + private final String input; + private int pos; + + JsonToSourceConverter(String input) { + this.input = input; + this.pos = 0; + } + + String parseObject() { + skipWhitespace(); + expect('{'); + skipWhitespace(); + List entries = new ArrayList<>(); + if (peek() != '}') { + do { + skipWhitespace(); + String key = parseString(); + skipWhitespace(); + expect(':'); + skipWhitespace(); + String value = parseValue(); + entries.add("\"" + escapeJava(key) + "\", " + value); + skipWhitespace(); + } while (tryConsume(',')); + } + expect('}'); + return "mapOfNullable(" + String.join(", ", entries) + ")"; + } + + private String parseArray() { + expect('['); + skipWhitespace(); + List items = new ArrayList<>(); + if (peek() != ']') { + do { + skipWhitespace(); + items.add(parseValue()); + skipWhitespace(); + } while (tryConsume(',')); + } + expect(']'); + return "listOfNullable(" + String.join(", ", items) + ")"; + } + + private String parseValue() { + skipWhitespace(); + char c = peek(); + if (c == '{') { + return parseObject(); + } + if (c == '[') { + return parseArray(); + } + if (c == '"') { + return "\"" + escapeJava(parseString()) + "\""; + } + if (c == 't' || c == 'f') { + return parseBoolean(); + } + if (c == 'n') { + return parseNull(); + } + return parseNumber(); + } + + private String parseString() { + expect('"'); + StringBuilder sb = new StringBuilder(); + while (pos < input.length() && input.charAt(pos) != '"') { + char current = input.charAt(pos++); + if (current == '\\') { + sb.append(parseEscape()); + } else { + if (current < 0x20) { + throw new IllegalArgumentException("Unescaped control character at position " + (pos - 1)); + } + sb.append(current); + } + } + expect('"'); + return sb.toString(); + } + + private char parseEscape() { + if (pos >= input.length()) { + throw new IllegalArgumentException("Unterminated string escape at position " + pos); + } + char escaped = input.charAt(pos++); + return switch (escaped) { + case '"', '\\', '/' -> escaped; + case 'b' -> '\b'; + case 'f' -> '\f'; + case 'n' -> '\n'; + case 'r' -> '\r'; + case 't' -> '\t'; + case 'u' -> parseUnicodeEscape(); + default -> throw new IllegalArgumentException( + "Invalid escape sequence \\" + escaped + " at position " + (pos - 2)); + }; + } + + private char parseUnicodeEscape() { + if (pos + 4 > input.length()) { + throw new IllegalArgumentException("Incomplete Unicode escape at position " + (pos - 2)); + } + int value = 0; + for (int i = 0; i < 4; i++) { + char hex = input.charAt(pos++); + if (!isAsciiHexDigit(hex)) { + throw new IllegalArgumentException("Invalid Unicode escape at position " + (pos - 1)); + } + int digit = Character.digit(hex, 16); + value = (value << 4) | digit; + } + return (char) value; + } + + private boolean isAsciiHexDigit(char c) { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); + } + + private String parseBoolean() { + if (input.startsWith("true", pos)) { + pos += 4; + return "true"; + } + if (input.startsWith("false", pos)) { + pos += 5; + return "false"; + } + throw new IllegalArgumentException("Expected boolean at position " + pos); + } + + private String parseNull() { + if (input.startsWith("null", pos)) { + pos += 4; + return "(Object) null"; + } + throw new IllegalArgumentException("Expected null at position " + pos); + } + + private String parseNumber() { + int start = pos; + if (pos < input.length() && input.charAt(pos) == '-') { + pos++; + } + if (pos >= input.length()) { + throw new IllegalArgumentException("Expected number at position " + start); + } + if (input.charAt(pos) == '0') { + pos++; + } else if (isDigitOneToNine(input.charAt(pos))) { + consumeDigits(); + } else { + throw new IllegalArgumentException("Expected number at position " + pos); + } + if (pos < input.length() && input.charAt(pos) == '.') { + pos++; + requireDigit("fraction"); + consumeDigits(); + } + if (pos < input.length() && (input.charAt(pos) == 'e' || input.charAt(pos) == 'E')) { + pos++; + if (pos < input.length() && (input.charAt(pos) == '+' || input.charAt(pos) == '-')) { + pos++; + } + requireDigit("exponent"); + consumeDigits(); + } + String number = input.substring(start, pos); + try { + new java.math.BigDecimal(number); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Number cannot be represented at position " + start + ": " + number, + e); + } + return "new java.math.BigDecimal(\"" + number + "\")"; + } + + private void requireDigit(String part) { + if (pos >= input.length() || !isAsciiDigit(input.charAt(pos))) { + throw new IllegalArgumentException("Expected digit in number " + part + " at position " + pos); + } + } + + private void consumeDigits() { + while (pos < input.length() && isAsciiDigit(input.charAt(pos))) { + pos++; + } + } + + private boolean isAsciiDigit(char c) { + return c >= '0' && c <= '9'; + } + + private boolean isDigitOneToNine(char c) { + return c >= '1' && c <= '9'; + } + + private void skipWhitespace() { + while (pos < input.length() && isJsonWhitespace(input.charAt(pos))) { + pos++; + } + } + + private boolean isJsonWhitespace(char c) { + return c == ' ' || c == '\t' || c == '\r' || c == '\n'; + } + + private char peek() { + if (pos >= input.length()) { + throw new IllegalArgumentException("Unexpected end of JSON"); + } + return input.charAt(pos); + } + + private void expect(char c) { + if (pos >= input.length() || input.charAt(pos) != c) { + throw new IllegalArgumentException("Expected '" + c + "' at position " + pos + " but got '" + + (pos < input.length() ? input.charAt(pos) : "EOF") + "'"); + } + pos++; + } + + private boolean tryConsume(char c) { + if (pos < input.length() && input.charAt(pos) == c) { + pos++; + return true; + } + return false; + } + } + private static String escapeJava(String s) { if (s == null) { return ""; } - return s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "\\r").replace("\t", - "\\t"); + StringBuilder escaped = new StringBuilder(s.length()); + for (int i = 0; i < s.length(); i++) { + char current = s.charAt(i); + switch (current) { + case '\\' -> escaped.append("\\\\"); + case '"' -> escaped.append("\\\""); + case '\b' -> escaped.append("\\b"); + case '\f' -> escaped.append("\\f"); + case '\n' -> escaped.append("\\n"); + case '\r' -> escaped.append("\\r"); + case '\t' -> escaped.append("\\t"); + default -> { + if (Character.isISOControl(current)) { + escaped.append(String.format("\\%03o", (int) current)); + } else { + escaped.append(current); + } + } + } + } + return escaped.toString(); } } diff --git a/java/src/main/java/com/github/copilot/tool/Param.java b/java/sdk/src/main/java/com/github/copilot/tool/Param.java similarity index 83% rename from java/src/main/java/com/github/copilot/tool/Param.java rename to java/sdk/src/main/java/com/github/copilot/tool/Param.java index bbe188ce0..0060205f6 100644 --- a/java/src/main/java/com/github/copilot/tool/Param.java +++ b/java/sdk/src/main/java/com/github/copilot/tool/Param.java @@ -37,18 +37,33 @@ public final class Param { private final String description; private final boolean required; private final String defaultValue; + private final String schema; - private Param(Class type, String name, String description, boolean required, String defaultValue) { + private Param(Class type, String name, String description, boolean required, String defaultValue, + String schema) { this.type = Objects.requireNonNull(type, "type"); this.name = requireNonBlank(name, "name"); this.description = requireNonBlank(description, "description"); this.defaultValue = defaultValue == null ? "" : defaultValue; + this.schema = schema == null ? "" : schema; this.required = required; if (this.required && !this.defaultValue.isEmpty()) { throw new IllegalArgumentException("required=true cannot be combined with a non-empty defaultValue"); } + if (!this.schema.isEmpty()) { + String trimmed = this.schema.trim(); + if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) { + throw new IllegalArgumentException( + "schema must be a valid JSON object string (must start with '{' and end with '}')"); + } + if (!this.defaultValue.isEmpty()) { + throw new IllegalArgumentException( + "schema cannot be combined with defaultValue — express defaults inside the schema if needed"); + } + } + validateDefaultValue(type, this.defaultValue); } @@ -70,7 +85,7 @@ private Param(Class type, String name, String description, boolean required, * if {@code name} or {@code description} is blank */ public static Param of(Class type, String name, String description) { - return new Param<>(type, name, description, true, ""); + return new Param<>(type, name, description, true, "", ""); } /** @@ -96,7 +111,7 @@ public static Param of(Class type, String name, String description) { */ public static Param of(Class type, String name, String description, boolean required, String defaultValue) { - return new Param<>(type, name, description, required, defaultValue); + return new Param<>(type, name, description, required, defaultValue, ""); } /** @@ -107,7 +122,7 @@ public static Param of(Class type, String name, String description, bo * @return a new {@code Param} with the updated name */ public Param name(String name) { - return new Param<>(this.type, name, this.description, this.required, this.defaultValue); + return new Param<>(this.type, name, this.description, this.required, this.defaultValue, this.schema); } /** @@ -118,7 +133,7 @@ public Param name(String name) { * @return a new {@code Param} with the updated description */ public Param description(String description) { - return new Param<>(this.type, this.name, description, this.required, this.defaultValue); + return new Param<>(this.type, this.name, description, this.required, this.defaultValue, this.schema); } /** @@ -129,7 +144,7 @@ public Param description(String description) { * @return a new {@code Param} with the updated required flag */ public Param required(boolean required) { - return new Param<>(this.type, this.name, this.description, required, this.defaultValue); + return new Param<>(this.type, this.name, this.description, required, this.defaultValue, this.schema); } /** @@ -142,7 +157,7 @@ public Param required(boolean required) { * false */ public Param defaultValue(String defaultValue) { - return new Param<>(this.type, this.name, this.description, false, defaultValue); + return new Param<>(this.type, this.name, this.description, false, defaultValue, this.schema); } /** Returns the Java type of this parameter. */ @@ -175,18 +190,37 @@ public boolean hasDefaultValue() { return !defaultValue.isEmpty(); } + /** + * Returns a copy with an explicit JSON Schema override. When set, bypasses + * automatic schema generation from the parameter type. + * + * @param schema + * a JSON object string (e.g., + * {@code "{\"type\":\"string\",\"format\":\"date-time\"}"} ) + * @return a new {@code Param} with the schema override + */ + public Param schema(String schema) { + return new Param<>(this.type, this.name, this.description, this.required, this.defaultValue, schema); + } + + /** Returns the explicit JSON Schema override, or empty if none. */ + public String schema() { + return schema; + } + @Override public boolean equals(Object o) { if (!(o instanceof Param other)) { return false; } return required == other.required && Objects.equals(type, other.type) && Objects.equals(name, other.name) - && Objects.equals(description, other.description) && Objects.equals(defaultValue, other.defaultValue); + && Objects.equals(description, other.description) && Objects.equals(defaultValue, other.defaultValue) + && Objects.equals(schema, other.schema); } @Override public int hashCode() { - return Objects.hash(type, name, description, required, defaultValue); + return Objects.hash(type, name, description, required, defaultValue, schema); } @Override diff --git a/java/src/main/java/com/github/copilot/tool/SchemaGenerator.java b/java/sdk/src/main/java/com/github/copilot/tool/SchemaGenerator.java similarity index 100% rename from java/src/main/java/com/github/copilot/tool/SchemaGenerator.java rename to java/sdk/src/main/java/com/github/copilot/tool/SchemaGenerator.java diff --git a/java/src/main/java/module-info.java b/java/sdk/src/main/java/module-info.java similarity index 94% rename from java/src/main/java/module-info.java rename to java/sdk/src/main/java/module-info.java index 38bc1f93d..8bc2dbd55 100644 --- a/java/src/main/java/module-info.java +++ b/java/sdk/src/main/java/module-info.java @@ -12,6 +12,7 @@ requires com.fasterxml.jackson.datatype.jsr310; requires static com.github.spotbugs.annotations; requires static java.compiler; + requires static com.sun.jna; requires java.net.http; requires java.logging; @@ -25,6 +26,7 @@ opens com.github.copilot.generated to com.fasterxml.jackson.databind; opens com.github.copilot.generated.rpc to com.fasterxml.jackson.databind; opens com.github.copilot.rpc to com.fasterxml.jackson.databind; + opens com.github.copilot.ffi to com.sun.jna; provides javax.annotation.processing.Processor with com.github.copilot.CopilotExperimentalProcessor, com.github.copilot.tool.CopilotToolProcessor; diff --git a/java/src/main/java25/com/github/copilot/InternalExecutorProvider.java b/java/sdk/src/main/java25/com/github/copilot/InternalExecutorProvider.java similarity index 100% rename from java/src/main/java25/com/github/copilot/InternalExecutorProvider.java rename to java/sdk/src/main/java25/com/github/copilot/InternalExecutorProvider.java diff --git a/java/sdk/src/main/java25/com/github/copilot/ffi/ReaderThreadFactory.java b/java/sdk/src/main/java25/com/github/copilot/ffi/ReaderThreadFactory.java new file mode 100644 index 000000000..a67346b88 --- /dev/null +++ b/java/sdk/src/main/java25/com/github/copilot/ffi/ReaderThreadFactory.java @@ -0,0 +1,15 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.ffi; + +/** + * JDK 25 multi-release variant of {@link ReaderThreadFactory}. + */ +final class ReaderThreadFactory { + + Thread create(Runnable task, String name) { + return Thread.ofVirtual().name(name).unstarted(task); + } +} diff --git a/java/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/java/sdk/src/main/resources/META-INF/services/javax.annotation.processing.Processor similarity index 100% rename from java/src/main/resources/META-INF/services/javax.annotation.processing.Processor rename to java/sdk/src/main/resources/META-INF/services/javax.annotation.processing.Processor diff --git a/java/sdk/src/main/resources/copilot-runtime.properties b/java/sdk/src/main/resources/copilot-runtime.properties new file mode 100644 index 000000000..290046444 --- /dev/null +++ b/java/sdk/src/main/resources/copilot-runtime.properties @@ -0,0 +1,3 @@ +# This file is processed by Maven resource filtering. +# The ${project.version} placeholder is replaced at build time. +version=${project.version} diff --git a/java/src/test/java/com/github/copilot/AgentInfoTest.java b/java/sdk/src/test/java/com/github/copilot/AgentInfoTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/AgentInfoTest.java rename to java/sdk/src/test/java/com/github/copilot/AgentInfoTest.java diff --git a/java/src/test/java/com/github/copilot/AgentModeTest.java b/java/sdk/src/test/java/com/github/copilot/AgentModeTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/AgentModeTest.java rename to java/sdk/src/test/java/com/github/copilot/AgentModeTest.java diff --git a/java/src/test/java/com/github/copilot/AskUserTest.java b/java/sdk/src/test/java/com/github/copilot/AskUserTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/AskUserTest.java rename to java/sdk/src/test/java/com/github/copilot/AskUserTest.java diff --git a/java/sdk/src/test/java/com/github/copilot/BuiltinPluginDirectoriesTest.java b/java/sdk/src/test/java/com/github/copilot/BuiltinPluginDirectoriesTest.java new file mode 100644 index 000000000..fa353e627 --- /dev/null +++ b/java/sdk/src/test/java/com/github/copilot/BuiltinPluginDirectoriesTest.java @@ -0,0 +1,128 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.JsonNode; +import com.github.copilot.rpc.CopilotClientOptions; + +class BuiltinPluginDirectoriesTest { + + @Test + void defaultAndEmptyDoNotCallRpc() throws Exception { + assertDoesNotCallRpc(new CopilotClientOptions()); + assertDoesNotCallRpc(new CopilotClientOptions().setBuiltinPluginDirectories(List.of())); + } + + @Test + void configuredDirectoriesCallRpcOnceBeforeStartCompletes() throws Exception { + var paths = List.of(Path.of("").toAbsolutePath().resolve("plugins/core"), + Path.of("").toAbsolutePath().resolve("plugins/github")); + + try (var server = new FakeRuntimeServer(); + var client = new CopilotClient( + new CopilotClientOptions().setCliUrl(server.url()).setBuiltinPluginDirectories(paths))) { + client.start().get(15, TimeUnit.SECONDS); + + assertEquals(1, server.builtinSetCount()); + JsonNode params = server.awaitBuiltinParams(); + assertEquals(paths.get(0).toString(), params.path("paths").get(0).asText()); + assertEquals(paths.get(1).toString(), params.path("paths").get(1).asText()); + } + } + + @Test + void relativeDirectoryIsRejected() { + assertThrows(IllegalArgumentException.class, + () -> new CopilotClientOptions().setBuiltinPluginDirectories(List.of(Path.of("plugins/core")))); + } + + private static void assertDoesNotCallRpc(CopilotClientOptions options) throws Exception { + try (var server = new FakeRuntimeServer(); var client = new CopilotClient(options.setCliUrl(server.url()))) { + client.start().get(15, TimeUnit.SECONDS); + assertEquals(0, server.builtinSetCount()); + } + } + + private static final class FakeRuntimeServer implements AutoCloseable { + + private final ServerSocket serverSocket; + private final Thread acceptThread; + private final CompletableFuture ready = new CompletableFuture<>(); + private final CompletableFuture builtinParams = new CompletableFuture<>(); + private final AtomicInteger builtinSetCount = new AtomicInteger(); + + FakeRuntimeServer() throws IOException { + serverSocket = new ServerSocket(0); + acceptThread = new Thread(this::acceptLoop, "builtin-plugin-runtime"); + acceptThread.setDaemon(true); + acceptThread.start(); + } + + String url() { + return "127.0.0.1:" + serverSocket.getLocalPort(); + } + + int builtinSetCount() { + return builtinSetCount.get(); + } + + JsonNode awaitBuiltinParams() throws Exception { + return builtinParams.get(15, TimeUnit.SECONDS); + } + + private void acceptLoop() { + try { + Socket socket = serverSocket.accept(); + JsonRpcClient server = JsonRpcClient.fromSocket(socket); + server.registerMethodHandler("connect", (id, params) -> respond(server, id, + Map.of("ok", true, "protocolVersion", 3, "version", "test"))); + server.registerMethodHandler("plugins.builtin.set", (id, params) -> { + builtinSetCount.incrementAndGet(); + builtinParams.complete(params); + respond(server, id, Map.of()); + }); + ready.complete(server); + } catch (IOException e) { + ready.completeExceptionally(e); + builtinParams.completeExceptionally(e); + } + } + + private static void respond(JsonRpcClient server, String id, Object result) { + if (id == null) { + return; + } + try { + server.sendResponse(id, result); + } catch (IOException e) { + // Connection teardown can race the response during test cleanup. + } + } + + @Override + public void close() throws Exception { + JsonRpcClient server = ready.getNow(null); + if (server != null) { + server.close(); + } + serverSocket.close(); + } + } +} diff --git a/java/src/test/java/com/github/copilot/ByokBearerTokenProviderE2ETest.java b/java/sdk/src/test/java/com/github/copilot/ByokBearerTokenProviderE2ETest.java similarity index 100% rename from java/src/test/java/com/github/copilot/ByokBearerTokenProviderE2ETest.java rename to java/sdk/src/test/java/com/github/copilot/ByokBearerTokenProviderE2ETest.java diff --git a/java/src/test/java/com/github/copilot/CapiProxy.java b/java/sdk/src/test/java/com/github/copilot/CapiProxy.java similarity index 95% rename from java/src/test/java/com/github/copilot/CapiProxy.java rename to java/sdk/src/test/java/com/github/copilot/CapiProxy.java index 6484f0581..53d5e1166 100644 --- a/java/src/test/java/com/github/copilot/CapiProxy.java +++ b/java/sdk/src/test/java/com/github/copilot/CapiProxy.java @@ -93,9 +93,10 @@ public String start() throws IOException, InterruptedException { // Start the harness server using npx tsx // On Windows, npx is installed as npx.cmd which requires cmd /c to launch boolean isWindows = System.getProperty("os.name").toLowerCase().contains("win"); + String npxPath = resolveCommand(isWindows ? "npx.cmd" : "npx"); var pb = isWindows - ? new ProcessBuilder("cmd", "/c", "npx", "tsx", "server.ts") - : new ProcessBuilder("npx", "tsx", "server.ts"); + ? new ProcessBuilder(System.getenv("COMSPEC"), "/c", npxPath, "tsx", "server.ts") + : new ProcessBuilder(npxPath, "tsx", "server.ts"); pb.directory(harnessDir.toFile()); pb.redirectErrorStream(false); // Tell the replaying proxy to fail fast on unmatched requests rather than @@ -516,6 +517,24 @@ private Path findHarnessDirectory() { return null; } + /** + * Resolves a command name to its absolute path by searching the system + * {@code PATH}. Falls back to the original name if not found. + */ + private static String resolveCommand(String command) { + String pathEnv = System.getenv("PATH"); + if (pathEnv == null) { + return command; + } + for (String dir : pathEnv.split(java.io.File.pathSeparator)) { + Path candidate = Path.of(dir, command); + if (java.nio.file.Files.isExecutable(candidate)) { + return candidate.toAbsolutePath().toString(); + } + } + return command; + } + /** * Test information record for configuring the proxy. */ diff --git a/java/src/test/java/com/github/copilot/CapiSessionOptionsTest.java b/java/sdk/src/test/java/com/github/copilot/CapiSessionOptionsTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/CapiSessionOptionsTest.java rename to java/sdk/src/test/java/com/github/copilot/CapiSessionOptionsTest.java diff --git a/java/src/test/java/com/github/copilot/CliServerManagerTest.java b/java/sdk/src/test/java/com/github/copilot/CliServerManagerTest.java similarity index 98% rename from java/src/test/java/com/github/copilot/CliServerManagerTest.java rename to java/sdk/src/test/java/com/github/copilot/CliServerManagerTest.java index b445d6153..353858135 100644 --- a/java/src/test/java/com/github/copilot/CliServerManagerTest.java +++ b/java/sdk/src/test/java/com/github/copilot/CliServerManagerTest.java @@ -72,7 +72,9 @@ void connectToServerTcpMode() throws Exception { private static Process startBlockingProcess() throws IOException { boolean isWindows = System.getProperty("os.name").toLowerCase().contains("windows"); - return (isWindows ? new ProcessBuilder("cmd", "/c", "more") : new ProcessBuilder("cat")).start(); + return (isWindows + ? new ProcessBuilder(System.getenv("COMSPEC"), "/c", "more") + : new ProcessBuilder("/usr/bin/cat")).start(); } @Test diff --git a/java/src/test/java/com/github/copilot/ClientOptionsE2ETest.java b/java/sdk/src/test/java/com/github/copilot/ClientOptionsE2ETest.java similarity index 100% rename from java/src/test/java/com/github/copilot/ClientOptionsE2ETest.java rename to java/sdk/src/test/java/com/github/copilot/ClientOptionsE2ETest.java diff --git a/java/src/test/java/com/github/copilot/ClosedSessionGuardTest.java b/java/sdk/src/test/java/com/github/copilot/ClosedSessionGuardTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/ClosedSessionGuardTest.java rename to java/sdk/src/test/java/com/github/copilot/ClosedSessionGuardTest.java diff --git a/java/src/test/java/com/github/copilot/CommandsTest.java b/java/sdk/src/test/java/com/github/copilot/CommandsTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/CommandsTest.java rename to java/sdk/src/test/java/com/github/copilot/CommandsTest.java diff --git a/java/src/test/java/com/github/copilot/CompactionTest.java b/java/sdk/src/test/java/com/github/copilot/CompactionTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/CompactionTest.java rename to java/sdk/src/test/java/com/github/copilot/CompactionTest.java diff --git a/java/src/test/java/com/github/copilot/ConfigCloneTest.java b/java/sdk/src/test/java/com/github/copilot/ConfigCloneTest.java similarity index 92% rename from java/src/test/java/com/github/copilot/ConfigCloneTest.java rename to java/sdk/src/test/java/com/github/copilot/ConfigCloneTest.java index 6986ef7f0..4c5a3fbef 100644 --- a/java/src/test/java/com/github/copilot/ConfigCloneTest.java +++ b/java/sdk/src/test/java/com/github/copilot/ConfigCloneTest.java @@ -120,6 +120,7 @@ void sessionConfigCloneBasic() { original.setReasoningSummary("detailed"); original.setContextTier("long_context"); original.setPluginDirectories(List.of("/plugins/a", "/plugins/b")); + original.setDisabledMcpServers(List.of("local-files", "remote-github")); original.setLargeOutput( new LargeToolOutputConfig().setEnabled(true).setMaxSizeBytes(1024L).setOutputDirectory("/tmp/out")); original.setMemory(new MemoryConfiguration().setEnabled(true)); @@ -133,6 +134,7 @@ void sessionConfigCloneBasic() { assertEquals(original.getReasoningSummary(), cloned.getReasoningSummary()); assertEquals(original.getContextTier(), cloned.getContextTier()); assertEquals(original.getPluginDirectories(), cloned.getPluginDirectories()); + assertEquals(original.getDisabledMcpServers(), cloned.getDisabledMcpServers()); assertEquals(original.getLargeOutput(), cloned.getLargeOutput()); assertEquals(original.getMemory(), cloned.getMemory()); assertEquals(original.isStreaming(), cloned.isStreaming()); @@ -146,6 +148,7 @@ void sessionConfigListIndependence() { toolList.add("bash"); original.setAvailableTools(toolList); original.setInstructionDirectories(new ArrayList<>(List.of("/path/a", "/path/b"))); + original.setDisabledMcpServers(new ArrayList<>(List.of("local-files"))); SessionConfig cloned = original.clone(); @@ -156,6 +159,7 @@ void sessionConfigListIndependence() { assertEquals(2, cloned.getAvailableTools().size()); assertEquals(3, original.getAvailableTools().size()); assertEquals(List.of("/path/a", "/path/b"), cloned.getInstructionDirectories()); + assertEquals(List.of("local-files"), cloned.getDisabledMcpServers()); } @Test @@ -177,13 +181,14 @@ void sessionConfigSessionPolicyOptionsCloned() { var sessionLimits = new SessionLimitsConfig(30.0); var excludedAgents = new ArrayList<>(List.of("explore")); SessionConfig original = new SessionConfig().setExcludedBuiltInAgents(excludedAgents).setEnableCitations(true) - .setSessionLimits(sessionLimits); + .setEnableFileChangeTracking(true).setSessionLimits(sessionLimits); SessionConfig cloned = original.clone(); excludedAgents.add("task"); assertEquals(List.of("explore"), cloned.getExcludedBuiltInAgents()); assertTrue(cloned.getEnableCitations().orElse(false)); + assertTrue(cloned.getEnableFileChangeTracking().orElse(false)); assertSame(sessionLimits, cloned.getSessionLimits()); } @@ -194,6 +199,7 @@ void resumeSessionConfigCloneBasic() { original.setReasoningSummary("none"); original.setContextTier("long_context"); original.setPluginDirectories(List.of("/plugins/r")); + original.setDisabledMcpServers(List.of("local-files-r")); original.setLargeOutput( new LargeToolOutputConfig().setEnabled(false).setMaxSizeBytes(2048L).setOutputDirectory("/tmp/resume")); original.setMemory(new MemoryConfiguration().setEnabled(false)); @@ -205,6 +211,7 @@ void resumeSessionConfigCloneBasic() { assertEquals(original.getReasoningSummary(), cloned.getReasoningSummary()); assertEquals(original.getContextTier(), cloned.getContextTier()); assertEquals(original.getPluginDirectories(), cloned.getPluginDirectories()); + assertEquals(original.getDisabledMcpServers(), cloned.getDisabledMcpServers()); assertEquals(original.getLargeOutput(), cloned.getLargeOutput()); assertEquals(original.getMemory(), cloned.getMemory()); assertEquals(original.isStreaming(), cloned.isStreaming()); @@ -229,13 +236,14 @@ void resumeSessionConfigSessionPolicyOptionsCloned() { var sessionLimits = new SessionLimitsConfig(30.0); var excludedAgents = new ArrayList<>(List.of("explore")); ResumeSessionConfig original = new ResumeSessionConfig().setExcludedBuiltInAgents(excludedAgents) - .setEnableCitations(true).setSessionLimits(sessionLimits); + .setEnableCitations(true).setEnableFileChangeTracking(true).setSessionLimits(sessionLimits); ResumeSessionConfig cloned = original.clone(); excludedAgents.add("task"); assertEquals(List.of("explore"), cloned.getExcludedBuiltInAgents()); assertTrue(cloned.getEnableCitations().orElse(false)); + assertTrue(cloned.getEnableFileChangeTracking().orElse(false)); assertSame(sessionLimits, cloned.getSessionLimits()); } @@ -377,6 +385,15 @@ void copilotClientOptionsSetEnvironmentNullClearsExisting() { assertTrue(env == null || env.isEmpty()); } + @Test + void copilotClientOptionsSetCwdNullClearsExisting() { + CopilotClientOptions opts = new CopilotClientOptions().setCwd("/tmp"); + + opts.setCwd(null); + + assertNull(opts.getCwd()); + } + @Test @SuppressWarnings("deprecation") void copilotClientOptionsDeprecatedGithubToken() { @@ -440,12 +457,15 @@ void resumeSessionConfigAllSetters() { void sessionConfigNewFieldsCloned() { SessionConfig original = new SessionConfig(); original.setGitHubToken("ghp_per_session_token"); + original.setAdditionalDirectories(new java.util.ArrayList<>(List.of("/repo/shared"))); DefaultAgentConfig defaultAgent = new DefaultAgentConfig().setExcludedTools(List.of("secret_tool")); original.setDefaultAgent(defaultAgent); SessionConfig cloned = original.clone(); assertEquals("ghp_per_session_token", cloned.getGitHubToken()); + assertEquals(List.of("/repo/shared"), cloned.getAdditionalDirectories()); + assertNotSame(original.getAdditionalDirectories(), cloned.getAdditionalDirectories()); assertSame(defaultAgent, cloned.getDefaultAgent()); } @@ -453,12 +473,15 @@ void sessionConfigNewFieldsCloned() { void resumeSessionConfigNewFieldsCloned() { ResumeSessionConfig original = new ResumeSessionConfig(); original.setGitHubToken("ghp_per_session_token"); + original.setAdditionalDirectories(new java.util.ArrayList<>(List.of("/repo/resumed"))); DefaultAgentConfig defaultAgent = new DefaultAgentConfig().setExcludedTools(List.of("secret_tool")); original.setDefaultAgent(defaultAgent); ResumeSessionConfig cloned = original.clone(); assertEquals("ghp_per_session_token", cloned.getGitHubToken()); + assertEquals(List.of("/repo/resumed"), cloned.getAdditionalDirectories()); + assertNotSame(original.getAdditionalDirectories(), cloned.getAdditionalDirectories()); assertSame(defaultAgent, cloned.getDefaultAgent()); } diff --git a/java/src/test/java/com/github/copilot/CopilotClientModeTest.java b/java/sdk/src/test/java/com/github/copilot/CopilotClientModeTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/CopilotClientModeTest.java rename to java/sdk/src/test/java/com/github/copilot/CopilotClientModeTest.java diff --git a/java/src/test/java/com/github/copilot/CopilotClientTest.java b/java/sdk/src/test/java/com/github/copilot/CopilotClientTest.java similarity index 99% rename from java/src/test/java/com/github/copilot/CopilotClientTest.java rename to java/sdk/src/test/java/com/github/copilot/CopilotClientTest.java index d977563ae..067571df1 100644 --- a/java/src/test/java/com/github/copilot/CopilotClientTest.java +++ b/java/sdk/src/test/java/com/github/copilot/CopilotClientTest.java @@ -610,9 +610,9 @@ void testListModels_WithCustomHandler_WorksWithoutStart() throws Exception { private static void setConnectionFuture(CopilotClient client, JsonRpcClient rpc, Process process) throws Exception { var connectionClass = Class.forName("com.github.copilot.CopilotClient$Connection"); var constructor = connectionClass.getDeclaredConstructor(JsonRpcClient.class, Process.class, - com.github.copilot.generated.rpc.ServerRpc.class); + com.github.copilot.generated.rpc.ServerRpc.class, AutoCloseable.class); constructor.setAccessible(true); - var connection = constructor.newInstance(rpc, process, null); + var connection = constructor.newInstance(rpc, process, null, null); Field field = CopilotClient.class.getDeclaredField("connectionFuture"); field.setAccessible(true); diff --git a/java/sdk/src/test/java/com/github/copilot/CopilotClientTransportTest.java b/java/sdk/src/test/java/com/github/copilot/CopilotClientTransportTest.java new file mode 100644 index 000000000..46223d56d --- /dev/null +++ b/java/sdk/src/test/java/com/github/copilot/CopilotClientTransportTest.java @@ -0,0 +1,387 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.channels.Channels; +import java.nio.channels.Pipe; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.copilot.rpc.CopilotClientOptions; +import com.github.copilot.rpc.InProcessRuntimeConnection; +import com.github.copilot.rpc.RuntimeConnection; +import com.github.copilot.rpc.StdioRuntimeConnection; +import com.github.copilot.rpc.TcpRuntimeConnection; +import com.github.copilot.rpc.TelemetryConfig; +import com.github.copilot.rpc.UriRuntimeConnection; + +/** + * Unit tests for transport selection through {@link RuntimeConnection}: the + * in-process code path, {@code COPILOT_SDK_DEFAULT_CONNECTION} resolution, the + * backward-compatibility bridge from the individual transport options, and + * option validation. + */ +@AllowCopilotExperimental +class CopilotClientTransportTest { + + // ===== In-process routing ===== + + @Test + void inProcessConnectionStartsThroughInProcessRuntimeHost() throws Exception { + var options = new CopilotClientOptions().setConnection(RuntimeConnection.forInProcess()); + try (var runtime = new FakeInProcessRuntime(); var client = new CopilotClient(options)) { + client.setInProcessTransportFactory(runtime::open); + + client.start().get(30, TimeUnit.SECONDS); + + assertTrue(runtime.opened.get(), "The in-process runtime must be used for an in-process connection"); + assertInstanceOf(InProcessRuntimeConnection.class, client.getRuntimeConnection()); + + client.stop().get(30, TimeUnit.SECONDS); + assertTrue(runtime.closed.get(), "Stopping the client must close the in-process runtime host"); + } + } + + @Test + void inProcessStartupFailurePropagates() { + var options = new CopilotClientOptions().setConnection(RuntimeConnection.forInProcess()); + try (var client = new CopilotClient(options)) { + client.setInProcessTransportFactory(opts -> { + throw new IOException("no runtime available"); + }); + var failure = assertThrows(Exception.class, () -> client.start().get(30, TimeUnit.SECONDS)); + assertTrue(rootMessage(failure).contains("no runtime available")); + } + } + + @Test + void cliTransportDoesNotUseTheInProcessRuntime() throws Exception { + var options = new CopilotClientOptions().setCliUrl("127.0.0.1:1"); + try (var client = new CopilotClient(options)) { + client.setInProcessTransportFactory(opts -> { + throw new AssertionError("The in-process runtime must not be used for a CLI transport"); + }); + + assertThrows(Exception.class, () -> client.start().get(30, TimeUnit.SECONDS)); + assertInstanceOf(UriRuntimeConnection.class, client.getRuntimeConnection()); + } + } + + // ===== COPILOT_SDK_DEFAULT_CONNECTION resolution ===== + + @Test + void defaultConnectionEnvVarSelectsInProcess() { + var connection = CopilotClient.resolveDefaultConnection(new CopilotClientOptions(), "inprocess"); + assertInstanceOf(InProcessRuntimeConnection.class, connection); + assertInstanceOf(InProcessRuntimeConnection.class, + CopilotClient.resolveDefaultConnection(new CopilotClientOptions(), "InProcess")); + } + + @Test + void defaultConnectionEnvVarStdioAndUnsetKeepTheConfiguredTransport() { + assertInstanceOf(StdioRuntimeConnection.class, + CopilotClient.resolveDefaultConnection(new CopilotClientOptions(), "stdio")); + assertInstanceOf(StdioRuntimeConnection.class, + CopilotClient.resolveDefaultConnection(new CopilotClientOptions(), null)); + assertInstanceOf(TcpRuntimeConnection.class, + CopilotClient.resolveDefaultConnection(new CopilotClientOptions().setUseStdio(false), "")); + assertInstanceOf(TcpRuntimeConnection.class, CopilotClient.resolveDefaultConnection( + new CopilotClientOptions().setUseStdio(false).setTcpConnectionToken("secret"), "inprocess")); + } + + @Test + void defaultConnectionEnvVarRejectsUnknownValues() { + var error = assertThrows(IllegalArgumentException.class, + () -> CopilotClient.resolveDefaultConnection(new CopilotClientOptions(), "websocket")); + assertTrue(error.getMessage().contains(CopilotClient.DEFAULT_CONNECTION_ENV_VAR)); + } + + // ===== Backward-compatibility bridge ===== + + @Test + void legacyStdioOptionsInferStdioConnection() { + try (var client = new CopilotClient(new CopilotClientOptions().setCliPath("/usr/local/bin/copilot"))) { + var connection = assertInstanceOf(StdioRuntimeConnection.class, client.getRuntimeConnection()); + assertEquals("/usr/local/bin/copilot", connection.getPath()); + } + } + + @Test + void legacyTcpOptionsInferTcpConnection() { + var options = new CopilotClientOptions().setUseStdio(false).setPort(4321).setTcpConnectionToken("secret"); + try (var client = new CopilotClient(options)) { + var connection = assertInstanceOf(TcpRuntimeConnection.class, client.getRuntimeConnection()); + assertEquals(4321, connection.getPort()); + assertEquals("secret", connection.getConnectionToken()); + } + } + + @Test + void legacyCliUrlInfersUriConnection() { + try (var client = new CopilotClient(new CopilotClientOptions().setCliUrl("localhost:3000"))) { + var connection = assertInstanceOf(UriRuntimeConnection.class, client.getRuntimeConnection()); + assertEquals("localhost:3000", connection.getUrl()); + } + } + + // ===== Connection applied to the transport options ===== + + @Test + void connectionIsProjectedOntoTransportOptions() { + var stdio = new CopilotClientOptions().setConnection(RuntimeConnection.forStdio("/opt/copilot")); + try (var client = new CopilotClient(stdio)) { + assertTrue(stdio.isUseStdio()); + assertEquals("/opt/copilot", stdio.getCliPath()); + } + + var tcp = new CopilotClientOptions().setConnection( + RuntimeConnection.forTcp().setPort(4321).setConnectionToken("secret").setArgs(List.of("--extra"))); + try (var client = new CopilotClient(tcp)) { + assertFalse(tcp.isUseStdio()); + assertEquals(4321, tcp.getPort()); + assertEquals("secret", tcp.getTcpConnectionToken()); + assertEquals(List.of("--extra"), List.of(tcp.getCliArgs())); + } + + var uri = new CopilotClientOptions().setConnection(RuntimeConnection.forUri("localhost:3000")); + try (var client = new CopilotClient(uri)) { + assertFalse(uri.isUseStdio()); + assertEquals("localhost:3000", uri.getCliUrl()); + } + } + + // ===== Conflicting configuration ===== + + @Test + void connectionCannotBeCombinedWithTransportOptions() { + assertConflict(new CopilotClientOptions().setConnection(RuntimeConnection.forStdio()) + .setCliPath("/usr/local/bin/copilot"), "CliPath"); + assertConflict( + new CopilotClientOptions().setConnection(RuntimeConnection.forInProcess()).setCliUrl("localhost:3000"), + "CliUrl"); + assertConflict(new CopilotClientOptions().setConnection(RuntimeConnection.forStdio()).setUseStdio(false), + "UseStdio"); + assertConflict(new CopilotClientOptions().setConnection(RuntimeConnection.forTcp()).setPort(4321), "Port"); + assertConflict( + new CopilotClientOptions().setConnection(RuntimeConnection.forTcp()).setTcpConnectionToken("secret"), + "TcpConnectionToken"); + assertConflict(new CopilotClientOptions().setConnection(RuntimeConnection.forStdio()) + .setCliArgs(new String[]{"--extra"}), "CliArgs"); + } + + @Test + void connectionCanBeReusedForSeveralClients() { + var options = new CopilotClientOptions().setConnection(RuntimeConnection.forStdio("/opt/copilot")); + try (var first = new CopilotClient(options); var second = new CopilotClient(options)) { + assertInstanceOf(StdioRuntimeConnection.class, first.getRuntimeConnection()); + assertInstanceOf(StdioRuntimeConnection.class, second.getRuntimeConnection()); + } + } + + private static void assertConflict(CopilotClientOptions options, String optionName) { + var error = assertThrows(IllegalArgumentException.class, () -> new CopilotClient(options)); + assertTrue(error.getMessage().contains(optionName), "Expected '" + optionName + "' in: " + error.getMessage()); + } + + // ===== Options rejected for the in-process transport ===== + + @Test + void inProcessRejectsPerProcessOptions() { + assertInProcessRejected(new CopilotClientOptions().setEnvironment(Map.of("FOO", "bar")), "Environment"); + assertInProcessRejected(new CopilotClientOptions().setTelemetry(new TelemetryConfig()), "Telemetry"); + assertInProcessRejected(new CopilotClientOptions().setCwd("/tmp"), "Cwd"); + assertInProcessRejected(new CopilotClientOptions().setCliArgs(new String[]{"--extra"}), "CliArgs"); + } + + @Test + void e2eContextClearsInProcessIncompatibleOptions() throws Exception { + try (var context = E2ETestContext.create()) { + var options = new CopilotClientOptions().setConnection(RuntimeConnection.forInProcess()) + .setEnvironment(Map.of("TEST_KEY", "test-value")).setCwd(context.getWorkDir().toString()) + .setCliArgs(new String[]{"--subprocess-only"}); + + try (var client = context.createClient(options)) { + assertInstanceOf(InProcessRuntimeConnection.class, client.getRuntimeConnection()); + assertTrue(options.getEnvironment() == null || options.getEnvironment().isEmpty()); + assertEquals(null, options.getCwd()); + assertTrue(options.getCliArgs() == null || options.getCliArgs().length == 0); + } + } + } + + private static void assertInProcessRejected(CopilotClientOptions options, String optionName) { + options.setConnection(RuntimeConnection.forInProcess()); + var error = assertThrows(IllegalArgumentException.class, () -> new CopilotClient(options)); + assertTrue(error.getMessage().contains(optionName), "Expected '" + optionName + "' in: " + error.getMessage()); + assertTrue(error.getMessage().contains("forInProcess"), + "Expected the in-process transport to be named in: " + error.getMessage()); + } + + private static String rootMessage(Throwable error) { + Throwable cause = error; + while (cause.getCause() != null) { + cause = cause.getCause(); + } + return String.valueOf(cause.getMessage()); + } + + /** + * Minimal loopback stand-in for the in-process runtime: it speaks just enough + * JSON-RPC for {@link CopilotClient#start()} to complete, so the test can + * assert that the client wires its transport to the in-process host rather than + * to a child process. + */ + private static final class FakeInProcessRuntime implements AutoCloseable { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + private final AtomicBoolean opened = new AtomicBoolean(); + private final AtomicBoolean closed = new AtomicBoolean(); + private final BytePipe toClient; + private final BytePipe toRuntime; + private final InputStream runtimeInput; + private final OutputStream runtimeOutput; + private final Thread responder; + + FakeInProcessRuntime() throws IOException { + this.toClient = new BytePipe(); + this.toRuntime = new BytePipe(); + this.runtimeInput = toRuntime.inputStream(); + this.runtimeOutput = toClient.outputStream(); + this.responder = new Thread(this::respondToRequests, "fake-inprocess-runtime"); + this.responder.setDaemon(true); + this.responder.start(); + } + + CopilotClient.InProcessTransport open(CopilotClientOptions options) { + opened.set(true); + return new CopilotClient.InProcessTransport(toClient.inputStream(), toRuntime.outputStream(), this::close); + } + + @Override + public void close() { + if (!closed.compareAndSet(false, true)) { + return; + } + toRuntime.close(); + toClient.close(); + } + + private void respondToRequests() { + try { + while (!closed.get()) { + JsonNode request = readMessage(runtimeInput); + if (request == null) { + return; + } + if (!request.hasNonNull("id")) { + continue; + } + var response = MAPPER.createObjectNode(); + response.put("jsonrpc", "2.0"); + response.set("id", request.get("id")); + var result = response.putObject("result"); + if ("connect".equals(request.path("method").asText())) { + result.put("protocolVersion", SdkProtocolVersion.get()); + } + writeMessage(runtimeOutput, response); + } + } catch (IOException e) { + // The streams are closed when the client shuts down. + } + } + + private static JsonNode readMessage(InputStream in) throws IOException { + int contentLength = -1; + var line = new ByteArrayOutputStream(); + while (true) { + int b = in.read(); + if (b == -1) { + return null; + } + if (b == '\n') { + String header = line.toString(StandardCharsets.UTF_8).trim(); + line.reset(); + if (header.isEmpty()) { + break; + } + if (header.toLowerCase(Locale.ROOT).startsWith("content-length:")) { + contentLength = Integer.parseInt(header.substring(header.indexOf(':') + 1).trim()); + } + } else if (b != '\r') { + line.write(b); + } + } + if (contentLength < 0) { + throw new IOException("Missing Content-Length header"); + } + byte[] body = in.readNBytes(contentLength); + if (body.length != contentLength) { + return null; + } + return MAPPER.readTree(body); + } + + private static void writeMessage(OutputStream out, JsonNode message) throws IOException { + byte[] body = MAPPER.writeValueAsBytes(message); + out.write(("Content-Length: " + body.length + "\r\n\r\n").getBytes(StandardCharsets.UTF_8)); + out.write(body); + out.flush(); + } + } + + /** + * Duplex byte channel used by {@link FakeInProcessRuntime} to emulate the + * streams of an in-process runtime. + */ + private static final class BytePipe { + + private final Pipe pipe; + + BytePipe() throws IOException { + this.pipe = Pipe.open(); + } + + InputStream inputStream() { + return Channels.newInputStream(pipe.source()); + } + + OutputStream outputStream() { + return Channels.newOutputStream(pipe.sink()); + } + + void close() { + closeQuietly(pipe.sink()); + closeQuietly(pipe.source()); + } + + private static void closeQuietly(Closeable closeable) { + try { + closeable.close(); + } catch (IOException e) { + // Nothing useful to do while tearing down a test pipe. + } + } + } +} diff --git a/java/src/test/java/com/github/copilot/CopilotExperimentalProcessorTest.java b/java/sdk/src/test/java/com/github/copilot/CopilotExperimentalProcessorTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/CopilotExperimentalProcessorTest.java rename to java/sdk/src/test/java/com/github/copilot/CopilotExperimentalProcessorTest.java diff --git a/java/src/test/java/com/github/copilot/CopilotRequestCancelErrorE2ETest.java b/java/sdk/src/test/java/com/github/copilot/CopilotRequestCancelErrorE2ETest.java similarity index 100% rename from java/src/test/java/com/github/copilot/CopilotRequestCancelErrorE2ETest.java rename to java/sdk/src/test/java/com/github/copilot/CopilotRequestCancelErrorE2ETest.java diff --git a/java/src/test/java/com/github/copilot/CopilotRequestHandlerE2ETest.java b/java/sdk/src/test/java/com/github/copilot/CopilotRequestHandlerE2ETest.java similarity index 100% rename from java/src/test/java/com/github/copilot/CopilotRequestHandlerE2ETest.java rename to java/sdk/src/test/java/com/github/copilot/CopilotRequestHandlerE2ETest.java diff --git a/java/src/test/java/com/github/copilot/CopilotRequestSessionIdE2ETest.java b/java/sdk/src/test/java/com/github/copilot/CopilotRequestSessionIdE2ETest.java similarity index 100% rename from java/src/test/java/com/github/copilot/CopilotRequestSessionIdE2ETest.java rename to java/sdk/src/test/java/com/github/copilot/CopilotRequestSessionIdE2ETest.java diff --git a/java/src/test/java/com/github/copilot/CopilotRequestTestSupport.java b/java/sdk/src/test/java/com/github/copilot/CopilotRequestTestSupport.java similarity index 99% rename from java/src/test/java/com/github/copilot/CopilotRequestTestSupport.java rename to java/sdk/src/test/java/com/github/copilot/CopilotRequestTestSupport.java index ecbf92068..aa173ef30 100644 --- a/java/src/test/java/com/github/copilot/CopilotRequestTestSupport.java +++ b/java/sdk/src/test/java/com/github/copilot/CopilotRequestTestSupport.java @@ -72,7 +72,8 @@ static CopilotClient newLlmClient(E2ETestContext ctx, CopilotRequestHandler hand env.put(entry.substring(0, eq), entry.substring(eq + 1)); } } - return ctx.createClient(new CopilotClientOptions().setEnvironment(env).setRequestHandler(handler)); + return ctx.createClient( + new CopilotClientOptions().setCliPath(ctx.getCliPath()).setEnvironment(env).setRequestHandler(handler)); } /** diff --git a/java/src/test/java/com/github/copilot/CopilotSessionTest.java b/java/sdk/src/test/java/com/github/copilot/CopilotSessionTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/CopilotSessionTest.java rename to java/sdk/src/test/java/com/github/copilot/CopilotSessionTest.java diff --git a/java/src/test/java/com/github/copilot/CreateSessionReKeyEntryTest.java b/java/sdk/src/test/java/com/github/copilot/CreateSessionReKeyEntryTest.java similarity index 99% rename from java/src/test/java/com/github/copilot/CreateSessionReKeyEntryTest.java rename to java/sdk/src/test/java/com/github/copilot/CreateSessionReKeyEntryTest.java index 156c96848..79e968cd3 100644 --- a/java/src/test/java/com/github/copilot/CreateSessionReKeyEntryTest.java +++ b/java/sdk/src/test/java/com/github/copilot/CreateSessionReKeyEntryTest.java @@ -170,8 +170,9 @@ private static void injectConnection(CopilotClient client, JsonRpcClient rpc) th var ctor = connClass.getDeclaredConstructors()[0]; ctor.setAccessible(true); - // Connection(JsonRpcClient rpc, Process process, ServerRpc serverRpc) - Object connection = ctor.newInstance(rpc, null, null); + // Connection(JsonRpcClient rpc, Process process, ServerRpc serverRpc, + // AutoCloseable runtimeHost) + Object connection = ctor.newInstance(rpc, null, null, null); Field f = CopilotClient.class.getDeclaredField("connectionFuture"); f.setAccessible(true); diff --git a/java/src/test/java/com/github/copilot/DataObjectCoverageTest.java b/java/sdk/src/test/java/com/github/copilot/DataObjectCoverageTest.java similarity index 92% rename from java/src/test/java/com/github/copilot/DataObjectCoverageTest.java rename to java/sdk/src/test/java/com/github/copilot/DataObjectCoverageTest.java index f38a03836..f95c5bcc5 100644 --- a/java/src/test/java/com/github/copilot/DataObjectCoverageTest.java +++ b/java/sdk/src/test/java/com/github/copilot/DataObjectCoverageTest.java @@ -139,6 +139,27 @@ void permissionRequestSetExtensionData() { assertEquals("value", req.getExtensionData().get("key")); } + @Test + void permissionRequestPreservesMcpExtensionData() { + var request = PermissionRequest.fromJsonValue( + java.util.Map.of("kind", "mcp", "serverName", "playwright", "toolName", "playwright-browser_navigate", + "args", java.util.Map.of("url", "http://127.0.0.1:8106/docs/target-app/"))); + + assertEquals("mcp", request.getKind()); + assertEquals("playwright", request.getExtensionData().get("serverName")); + assertEquals("playwright-browser_navigate", request.getExtensionData().get("toolName")); + @SuppressWarnings("unchecked") + var args = (java.util.Map) request.getExtensionData().get("args"); + assertEquals("http://127.0.0.1:8106/docs/target-app/", args.get("url")); + } + + @Test + void permissionRequestWithoutExtensionDataPreservesNull() { + var request = PermissionRequest.fromJsonValue(java.util.Map.of("kind", "read", "toolCallId", "tool-123")); + + assertNull(request.getExtensionData()); + } + // ===== SectionOverride setContent ===== @Test diff --git a/java/src/test/java/com/github/copilot/DocumentationSamplesTest.java b/java/sdk/src/test/java/com/github/copilot/DocumentationSamplesTest.java similarity index 99% rename from java/src/test/java/com/github/copilot/DocumentationSamplesTest.java rename to java/sdk/src/test/java/com/github/copilot/DocumentationSamplesTest.java index f7170f4fd..4e1396aa9 100644 --- a/java/src/test/java/com/github/copilot/DocumentationSamplesTest.java +++ b/java/sdk/src/test/java/com/github/copilot/DocumentationSamplesTest.java @@ -132,7 +132,7 @@ private static String stripStringsAndComments(String input) { private static List documentationFiles() throws IOException { Path root = Path.of("").toAbsolutePath(); List files = new ArrayList<>(); - files.add(root.resolve("README.md")); + files.add(root.resolve("../README.md")); files.add(root.resolve("jbang-example.java")); return files; } diff --git a/java/src/test/java/com/github/copilot/E2ETestContext.java b/java/sdk/src/test/java/com/github/copilot/E2ETestContext.java similarity index 90% rename from java/src/test/java/com/github/copilot/E2ETestContext.java rename to java/sdk/src/test/java/com/github/copilot/E2ETestContext.java index f524b33da..60dcf1fa3 100644 --- a/java/src/test/java/com/github/copilot/E2ETestContext.java +++ b/java/sdk/src/test/java/com/github/copilot/E2ETestContext.java @@ -18,7 +18,10 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import com.github.copilot.ffi.InProcessEnvGuard; import com.github.copilot.rpc.CopilotClientOptions; +import com.github.copilot.rpc.InProcessRuntimeConnection; +import com.github.copilot.rpc.RuntimeConnection; /** * E2E test context that manages the test environment including the CapiProxy, @@ -71,6 +74,7 @@ public class E2ETestContext implements AutoCloseable { private String proxyUrl; private final CapiProxy proxy; private final Path repoRoot; + private final List inProcessEnvGuards = new ArrayList<>(); private Path currentSnapshotFile; private E2ETestContext(String cliPath, Path homeDir, Path workDir, String proxyUrl, CapiProxy proxy, @@ -322,10 +326,8 @@ public Map getEnvironment() { * @return a new CopilotClient */ public CopilotClient createClient() { - CopilotClientOptions options = new CopilotClientOptions().setCliPath(cliPath).setCwd(workDir.toString()) - .setEnvironment(getEnvironment()).setGitHubToken(DEFAULT_GITHUB_TOKEN); - - return new CopilotClient(options); + CopilotClientOptions options = new CopilotClientOptions().setGitHubToken(DEFAULT_GITHUB_TOKEN); + return createClient(options); } /** @@ -338,6 +340,31 @@ public CopilotClient createClient() { * @return a new CopilotClient */ public CopilotClient createClient(CopilotClientOptions options) { + CopilotClient client = applyContextOptions(options); + if (client != null) { + return client; + } + if (options.getGitHubToken() == null) { + options.setGitHubToken(DEFAULT_GITHUB_TOKEN); + } + + return new CopilotClient(options); + } + + private CopilotClient applyContextOptions(CopilotClientOptions options) { + if (isInProcessMode(options)) { + InProcessEnvGuard guard = new InProcessEnvGuard(buildInProcessEnvironment(options)); + inProcessEnvGuards.add(guard); + try { + options.setEnvironment(null); + options.setCwd(null); + options.setCliArgs(null); + return new CopilotClient(options, guard::close); + } catch (RuntimeException e) { + guard.close(); + throw e; + } + } if (options.getCliPath() == null) { options.setCliPath(cliPath); } @@ -347,11 +374,30 @@ public CopilotClient createClient(CopilotClientOptions options) { if (options.getEnvironment() == null || options.getEnvironment().isEmpty()) { options.setEnvironment(getEnvironment()); } - if (options.getGitHubToken() == null) { - options.setGitHubToken(DEFAULT_GITHUB_TOKEN); + return null; + } + + private boolean isInProcessMode(CopilotClientOptions options) { + RuntimeConnection connection = options.getConnection(); + if (connection != null) { + return connection instanceof InProcessRuntimeConnection; + } + if (options.getRequestHandler() != null || options.getCliUrl() != null || options.getCliPath() != null + || options.getPort() != 0) { + return false; } + String defaultConnection = System.getenv("COPILOT_SDK_DEFAULT_CONNECTION"); + return defaultConnection != null && "inprocess".equalsIgnoreCase(defaultConnection.trim()); + } - return new CopilotClient(options); + private Map buildInProcessEnvironment(CopilotClientOptions options) { + Map env = new HashMap<>(getEnvironment()); + Map optionEnvironment = options.getEnvironment(); + if (optionEnvironment != null && !optionEnvironment.isEmpty()) { + env.putAll(optionEnvironment); + options.setEnvironment(null); + } + return env; } /** @@ -428,6 +474,9 @@ public void initializeProxy() throws IOException, InterruptedException { @Override public void close() throws Exception { + for (int i = inProcessEnvGuards.size() - 1; i >= 0; i--) { + inProcessEnvGuards.get(i).close(); + } proxy.stop(); // Clean up temp directories (best effort) diff --git a/java/src/test/java/com/github/copilot/ElicitationTest.java b/java/sdk/src/test/java/com/github/copilot/ElicitationTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/ElicitationTest.java rename to java/sdk/src/test/java/com/github/copilot/ElicitationTest.java diff --git a/java/src/test/java/com/github/copilot/ErrorHandlingTest.java b/java/sdk/src/test/java/com/github/copilot/ErrorHandlingTest.java similarity index 94% rename from java/src/test/java/com/github/copilot/ErrorHandlingTest.java rename to java/sdk/src/test/java/com/github/copilot/ErrorHandlingTest.java index 32579ffc4..46f6741a0 100644 --- a/java/src/test/java/com/github/copilot/ErrorHandlingTest.java +++ b/java/sdk/src/test/java/com/github/copilot/ErrorHandlingTest.java @@ -158,6 +158,10 @@ void testShouldHandlePermissionHandlerErrorsGracefully_deniesPermission() throws || content.contains("permission") || content.contains("denied"), "Response should indicate permission was denied: " + content); + // Verify that the error handler was wired correctly. Whether error events are + // actually emitted depends on the CLI version and the scenario's replay data. + LOG.info("Collected " + errorEvents.size() + " error event(s) from permission handler crash"); + session.close(); } } @@ -198,9 +202,10 @@ void testPermissionHandlerErrors_sessionErrorEventContainsDetails() throws Excep session.close(); } - // Note: Whether error events are emitted depends on the CLI version and - // scenario - // This test verifies the handler can receive them when they occur + // Whether error events are emitted depends on the CLI version and scenario. + // This test verifies the handler can receive them when they occur. + // Access the list to confirm it was populated (even if empty is acceptable). + LOG.info("Collected " + errorEvents.size() + " error event(s)"); } /** diff --git a/java/src/test/java/com/github/copilot/EventFidelityTest.java b/java/sdk/src/test/java/com/github/copilot/EventFidelityTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/EventFidelityTest.java rename to java/sdk/src/test/java/com/github/copilot/EventFidelityTest.java diff --git a/java/src/test/java/com/github/copilot/ExecutorWiringTest.java b/java/sdk/src/test/java/com/github/copilot/ExecutorWiringTest.java similarity index 95% rename from java/src/test/java/com/github/copilot/ExecutorWiringTest.java rename to java/sdk/src/test/java/com/github/copilot/ExecutorWiringTest.java index 78764db0f..a8319475c 100644 --- a/java/src/test/java/com/github/copilot/ExecutorWiringTest.java +++ b/java/sdk/src/test/java/com/github/copilot/ExecutorWiringTest.java @@ -86,8 +86,7 @@ int getTaskCount() { } private CopilotClientOptions createOptionsWithExecutor(TrackingExecutor executor) { - CopilotClientOptions options = new CopilotClientOptions().setCliPath(ctx.getCliPath()) - .setCwd(ctx.getWorkDir().toString()).setEnvironment(ctx.getEnvironment()).setExecutor(executor) + CopilotClientOptions options = new CopilotClientOptions().setExecutor(executor) .setGitHubToken("fake-token-for-e2e-tests"); return options; } @@ -111,7 +110,7 @@ void testClientStartUsesProvidedExecutor() throws Exception { TrackingExecutor trackingExecutor = new TrackingExecutor(ForkJoinPool.commonPool()); int beforeStart = trackingExecutor.getTaskCount(); - try (CopilotClient client = new CopilotClient(createOptionsWithExecutor(trackingExecutor))) { + try (CopilotClient client = ctx.createClient(createOptionsWithExecutor(trackingExecutor))) { client.start().get(30, TimeUnit.SECONDS); assertTrue(trackingExecutor.getTaskCount() > beforeStart, @@ -156,7 +155,7 @@ void testToolCallDispatchUsesProvidedExecutor() throws Exception { }); // Reset count after client construction to isolate tool-call dispatch - try (CopilotClient client = new CopilotClient(createOptionsWithExecutor(trackingExecutor))) { + try (CopilotClient client = ctx.createClient(createOptionsWithExecutor(trackingExecutor))) { CopilotSession session = client.createSession(new SessionConfig().setTools(List.of(encryptTool)) .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get(); @@ -198,7 +197,7 @@ void testPermissionDispatchUsesProvidedExecutor() throws Exception { var config = new SessionConfig().setOnPermissionRequest((request, invocation) -> CompletableFuture .completedFuture(new PermissionRequestResult().setKind(PermissionRequestResultKind.APPROVED))); - try (CopilotClient client = new CopilotClient(createOptionsWithExecutor(trackingExecutor))) { + try (CopilotClient client = ctx.createClient(createOptionsWithExecutor(trackingExecutor))) { CopilotSession session = client.createSession(config).get(); Path testFile = ctx.getWorkDir().resolve("test.txt"); @@ -247,7 +246,7 @@ void testUserInputDispatchUsesProvidedExecutor() throws Exception { .completedFuture(new UserInputResponse().setAnswer(answer).setWasFreeform(wasFreeform)); }); - try (CopilotClient client = new CopilotClient(createOptionsWithExecutor(trackingExecutor))) { + try (CopilotClient client = ctx.createClient(createOptionsWithExecutor(trackingExecutor))) { CopilotSession session = client.createSession(config).get(); int beforeSend = trackingExecutor.getTaskCount(); @@ -286,7 +285,7 @@ void testHooksDispatchUsesProvidedExecutor() throws Exception { .setHooks(new SessionHooks().setOnPreToolUse( (input, invocation) -> CompletableFuture.completedFuture(PreToolUseHookOutput.allow()))); - try (CopilotClient client = new CopilotClient(createOptionsWithExecutor(trackingExecutor))) { + try (CopilotClient client = ctx.createClient(createOptionsWithExecutor(trackingExecutor))) { CopilotSession session = client.createSession(config).get(); Path testFile = ctx.getWorkDir().resolve("hello.txt"); @@ -342,7 +341,7 @@ void testClientStopUsesProvidedExecutor() throws Exception { return CompletableFuture.completedFuture(input.toUpperCase()); }); - CopilotClient client = new CopilotClient(createOptionsWithExecutor(trackingExecutor)); + CopilotClient client = ctx.createClient(createOptionsWithExecutor(trackingExecutor)); client.createSession(new SessionConfig().setTools(List.of(encryptTool)) .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get(); diff --git a/java/src/test/java/com/github/copilot/FakeUpstreamServer.java b/java/sdk/src/test/java/com/github/copilot/FakeUpstreamServer.java similarity index 97% rename from java/src/test/java/com/github/copilot/FakeUpstreamServer.java rename to java/sdk/src/test/java/com/github/copilot/FakeUpstreamServer.java index 7af60d4d3..909cd1406 100644 --- a/java/src/test/java/com/github/copilot/FakeUpstreamServer.java +++ b/java/sdk/src/test/java/com/github/copilot/FakeUpstreamServer.java @@ -162,7 +162,10 @@ private void serveHttp(InputStream in, OutputStream out, String path, Map headers) throws Exception { String key = headers.get("sec-websocket-key"); - MessageDigest sha1 = MessageDigest.getInstance("SHA-1"); + // SHA-1 is mandated by the WebSocket protocol (RFC 6455 §4.2.2) for the + // Sec-WebSocket-Accept handshake hash. This is NOT used for security purposes. + @SuppressWarnings("codeql[java/weak-cryptographic-algorithm]") + MessageDigest sha1 = MessageDigest.getInstance("SHA-1"); // lgtm[java/weak-cryptographic-algorithm] byte[] digest = sha1.digest((key + WS_MAGIC).getBytes(StandardCharsets.US_ASCII)); String accept = Base64.getEncoder().encodeToString(digest); String response = "HTTP/1.1 101 Switching Protocols\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" diff --git a/java/src/test/java/com/github/copilot/ForwardCompatibilityTest.java b/java/sdk/src/test/java/com/github/copilot/ForwardCompatibilityTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/ForwardCompatibilityTest.java rename to java/sdk/src/test/java/com/github/copilot/ForwardCompatibilityTest.java diff --git a/java/src/test/java/com/github/copilot/GitHubTelemetryForwardingIT.java b/java/sdk/src/test/java/com/github/copilot/GitHubTelemetryForwardingIT.java similarity index 100% rename from java/src/test/java/com/github/copilot/GitHubTelemetryForwardingIT.java rename to java/sdk/src/test/java/com/github/copilot/GitHubTelemetryForwardingIT.java diff --git a/java/src/test/java/com/github/copilot/GitHubTelemetryTest.java b/java/sdk/src/test/java/com/github/copilot/GitHubTelemetryTest.java similarity index 91% rename from java/src/test/java/com/github/copilot/GitHubTelemetryTest.java rename to java/sdk/src/test/java/com/github/copilot/GitHubTelemetryTest.java index 8e35bd9a9..7b0deb997 100644 --- a/java/src/test/java/com/github/copilot/GitHubTelemetryTest.java +++ b/java/sdk/src/test/java/com/github/copilot/GitHubTelemetryTest.java @@ -259,23 +259,24 @@ void sendTelemetry(Object params) throws Exception { private void acceptLoop() { try { Socket socket = serverSocket.accept(); - JsonRpcClient server = JsonRpcClient.fromSocket(socket); - server.registerMethodHandler("connect", (id, params) -> { - connectParams.complete(params); - respond(server, id, Map.of("protocolVersion", 2)); + JsonRpcClient server = JsonRpcClient.fromSocket(socket, rpc -> { + rpc.registerMethodHandler("connect", (id, params) -> { + connectParams.complete(params); + respond(rpc, id, Map.of("protocolVersion", 2)); + }); + rpc.registerMethodHandler("session.create", (id, params) -> { + createParams.complete(params); + respond(rpc, id, Map.of("sessionId", params.path("sessionId").asText("created"), + "workspacePath", "/workspace")); + }); + rpc.registerMethodHandler("session.resume", (id, params) -> { + resumeParams.complete(params); + respond(rpc, id, Map.of("sessionId", params.path("sessionId").asText("resume-1"), + "workspacePath", "/workspace")); + }); + rpc.registerMethodHandler("session.destroy", (id, params) -> respond(rpc, id, Map.of())); + rpc.registerMethodHandler("runtime.shutdown", (id, params) -> respond(rpc, id, Map.of())); }); - server.registerMethodHandler("session.create", (id, params) -> { - createParams.complete(params); - respond(server, id, Map.of("sessionId", params.path("sessionId").asText("created"), "workspacePath", - "/workspace")); - }); - server.registerMethodHandler("session.resume", (id, params) -> { - resumeParams.complete(params); - respond(server, id, Map.of("sessionId", params.path("sessionId").asText("resume-1"), - "workspacePath", "/workspace")); - }); - server.registerMethodHandler("session.destroy", (id, params) -> respond(server, id, Map.of())); - server.registerMethodHandler("runtime.shutdown", (id, params) -> respond(server, id, Map.of())); ready.complete(server); } catch (IOException e) { ready.completeExceptionally(e); diff --git a/java/src/test/java/com/github/copilot/HooksTest.java b/java/sdk/src/test/java/com/github/copilot/HooksTest.java similarity index 71% rename from java/src/test/java/com/github/copilot/HooksTest.java rename to java/sdk/src/test/java/com/github/copilot/HooksTest.java index 329883581..c3833891c 100644 --- a/java/src/test/java/com/github/copilot/HooksTest.java +++ b/java/sdk/src/test/java/com/github/copilot/HooksTest.java @@ -18,6 +18,8 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import com.github.copilot.rpc.AgentStopHookInput; +import com.github.copilot.rpc.AgentStopHookOutput; import com.github.copilot.rpc.MessageOptions; import com.github.copilot.rpc.PermissionHandler; import com.github.copilot.rpc.PostToolUseHookInput; @@ -25,6 +27,8 @@ import com.github.copilot.rpc.PreToolUseHookOutput; import com.github.copilot.rpc.SessionConfig; import com.github.copilot.rpc.SessionHooks; +import com.github.copilot.rpc.UserPromptTransformedHookInput; +import com.github.copilot.rpc.UserPromptTransformedHookOutput; /** * Tests for hooks functionality (pre-tool-use and post-tool-use hooks). @@ -225,4 +229,74 @@ void testDenyToolExecutionWhenPreToolUseReturnsDeny() throws Exception { assertEquals(originalContent, Files.readString(testFile), "Denied preToolUse hook should block file edits"); } } + + /** + * Verifies that agent-stop can block a natural stop and enqueue another turn. + * + * @see Snapshot: + * hooks_extended/should_invoke_agentstop_hook_and_apply_block_response + */ + @Test + void testInvokeAgentStopHookAndApplyBlockResponse() throws Exception { + ctx.configureForTest("hooks_extended", "should_invoke_agentstop_hook_and_apply_block_response"); + + var inputs = new ArrayList(); + final String[] sessionIdHolder = new String[1]; + var config = new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) + .setHooks(new SessionHooks().setOnAgentStop((input, invocation) -> { + assertEquals(sessionIdHolder[0], invocation.getSessionId()); + inputs.add(input); + if (inputs.size() == 1) { + return CompletableFuture.completedFuture(new AgentStopHookOutput().setDecision("block") + .setReason("Reply with exactly: AGENT_STOP_CONTINUED")); + } + return CompletableFuture.completedFuture(null); + })); + + try (CopilotClient client = ctx.createClient()) { + CopilotSession session = client.createSession(config).get(); + sessionIdHolder[0] = session.getSessionId(); + + var response = session.sendAndWait(new MessageOptions().setPrompt("Reply with exactly: AGENT_STOP_INITIAL")) + .get(60, TimeUnit.SECONDS); + + assertEquals(2, inputs.size()); + assertNotEquals(Boolean.TRUE, inputs.get(0).getStopHookActive()); + assertEquals(Boolean.TRUE, inputs.get(1).getStopHookActive()); + assertEquals("end_turn", inputs.get(0).getStopReason()); + assertFalse(inputs.get(0).getTranscriptPath().isBlank()); + assertNotNull(response); + assertTrue(response.getData().content().contains("AGENT_STOP_CONTINUED")); + } + } + + @Test + void testInvokeUserPromptTransformedHookAndModifyTransformedPrompt() throws Exception { + ctx.configureForTest("hooks_extended", + "should_invoke_userprompttransformed_hook_and_modify_transformed_prompt"); + + var inputs = new ArrayList(); + var config = new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) + .setHooks(new SessionHooks().setOnUserPromptTransformed((input, invocation) -> { + assertFalse(invocation.getSessionId().isBlank()); + inputs.add(input); + return CompletableFuture.completedFuture( + new UserPromptTransformedHookOutput("Reply with exactly: HOOKED_TRANSFORMED_PROMPT")); + })); + + try (CopilotClient client = ctx.createClient()) { + CopilotSession session = client.createSession(config).get(); + var response = session.sendAndWait(new MessageOptions().setPrompt("Answer the request above.")).get(60, + TimeUnit.SECONDS); + + assertFalse(inputs.isEmpty()); + assertTrue(inputs.get(0).prompt().contains("Answer the request above.")); + assertTrue(inputs.get(0).transformedPrompt().contains("Answer the request above.")); + assertTrue(inputs.get(0).transformedPrompt().contains("")); + assertTrue(inputs.get(0).timestamp() > 0); + assertFalse(inputs.get(0).cwd().isBlank()); + assertNotNull(response); + assertTrue(response.getData().content().contains("HOOKED_TRANSFORMED_PROMPT")); + } + } } diff --git a/java/src/test/java/com/github/copilot/InternalExecutorProviderIT.java b/java/sdk/src/test/java/com/github/copilot/InternalExecutorProviderIT.java similarity index 100% rename from java/src/test/java/com/github/copilot/InternalExecutorProviderIT.java rename to java/sdk/src/test/java/com/github/copilot/InternalExecutorProviderIT.java diff --git a/java/src/test/java/com/github/copilot/InternalExecutorProviderProbe.java b/java/sdk/src/test/java/com/github/copilot/InternalExecutorProviderProbe.java similarity index 100% rename from java/src/test/java/com/github/copilot/InternalExecutorProviderProbe.java rename to java/sdk/src/test/java/com/github/copilot/InternalExecutorProviderProbe.java diff --git a/java/src/test/java/com/github/copilot/InternalExecutorProviderTest.java b/java/sdk/src/test/java/com/github/copilot/InternalExecutorProviderTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/InternalExecutorProviderTest.java rename to java/sdk/src/test/java/com/github/copilot/InternalExecutorProviderTest.java diff --git a/java/src/test/java/com/github/copilot/JsonIncludeNonNullTest.java b/java/sdk/src/test/java/com/github/copilot/JsonIncludeNonNullTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/JsonIncludeNonNullTest.java rename to java/sdk/src/test/java/com/github/copilot/JsonIncludeNonNullTest.java diff --git a/java/src/test/java/com/github/copilot/JsonRpcClientTest.java b/java/sdk/src/test/java/com/github/copilot/JsonRpcClientTest.java similarity index 99% rename from java/src/test/java/com/github/copilot/JsonRpcClientTest.java rename to java/sdk/src/test/java/com/github/copilot/JsonRpcClientTest.java index 3491ac8ab..d6c0b5e14 100644 --- a/java/src/test/java/com/github/copilot/JsonRpcClientTest.java +++ b/java/sdk/src/test/java/com/github/copilot/JsonRpcClientTest.java @@ -135,7 +135,9 @@ void testIsConnectedWithSocketClosed() throws Exception { private static Process startBlockingProcess() throws IOException { boolean isWindows = System.getProperty("os.name").toLowerCase().contains("windows"); - return (isWindows ? new ProcessBuilder("cmd", "/c", "more") : new ProcessBuilder("cat")).start(); + return (isWindows + ? new ProcessBuilder(System.getenv("COMSPEC"), "/c", "more") + : new ProcessBuilder("/usr/bin/cat")).start(); } @Test diff --git a/java/src/test/java/com/github/copilot/LifecycleEventManagerTest.java b/java/sdk/src/test/java/com/github/copilot/LifecycleEventManagerTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/LifecycleEventManagerTest.java rename to java/sdk/src/test/java/com/github/copilot/LifecycleEventManagerTest.java diff --git a/java/src/test/java/com/github/copilot/LowLevelToolDefinitionIT.java b/java/sdk/src/test/java/com/github/copilot/LowLevelToolDefinitionIT.java similarity index 100% rename from java/src/test/java/com/github/copilot/LowLevelToolDefinitionIT.java rename to java/sdk/src/test/java/com/github/copilot/LowLevelToolDefinitionIT.java diff --git a/java/sdk/src/test/java/com/github/copilot/ManagedSettingsTest.java b/java/sdk/src/test/java/com/github/copilot/ManagedSettingsTest.java new file mode 100644 index 000000000..dbd19f3c9 --- /dev/null +++ b/java/sdk/src/test/java/com/github/copilot/ManagedSettingsTest.java @@ -0,0 +1,90 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ +package com.github.copilot; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.copilot.generated.rpc.DisableBypassPermissionsMode; +import com.github.copilot.rpc.ManagedSettings; +import com.github.copilot.rpc.ManagedSettingsPermissions; +import com.github.copilot.rpc.PermissionRequestResult; +import com.github.copilot.rpc.ResumeSessionConfig; +import com.github.copilot.rpc.SessionConfig; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.jupiter.api.Test; + +class ManagedSettingsTest { + @Test + void forwardsManagedSettingsOnCreateAndResume() throws Exception { + var permissions = new ManagedSettingsPermissions() + .setDisableBypassPermissionsMode(DisableBypassPermissionsMode.DISABLE).setDeny(List.of("Shell(rm *)")) + .setAsk(List.of("Domain(publish.example)")).setAllow(List.of("Read(**)")); + var managedSettings = new ManagedSettings().setPermissions(permissions); + + var create = SessionRequestBuilder.buildCreateRequest( + new SessionConfig().setEnableManagedSettings(true).setManagedSettings(managedSettings), + "managed-create"); + var resume = SessionRequestBuilder.buildResumeRequest("managed-resume", + new ResumeSessionConfig().setEnableManagedSettings(true).setManagedSettings(managedSettings)); + + assertEquals(managedSettings, create.getManagedSettings()); + assertEquals(managedSettings, resume.getManagedSettings()); + var json = new ObjectMapper().writeValueAsString(create); + assertTrue(json.contains("\"enableManagedSettings\":true")); + assertTrue(json.contains("\"managedSettings\":{\"permissions\"")); + assertTrue(json.contains("\"disableBypassPermissionsMode\":\"disable\"")); + } + + @Test + void preservesExplicitEmptyPermissionArrays() throws Exception { + // Security-critical: a present empty allow list admits nothing, while an + // absent (null) list imposes no such restriction. Jackson NON_NULL must + // emit an explicit empty array as `[]` and omit null fields, so the two + // remain distinguishable on the wire. + var permissions = new ManagedSettingsPermissions().setDeny(List.of()).setAsk(List.of()).setAllow(List.of()); + var managedSettings = new ManagedSettings().setPermissions(permissions); + var create = SessionRequestBuilder.buildCreateRequest(new SessionConfig().setManagedSettings(managedSettings), + "managed-empty"); + + var json = new ObjectMapper().writeValueAsString(create); + assertTrue(json.contains("\"deny\":[]"), json); + assertTrue(json.contains("\"ask\":[]"), json); + assertTrue(json.contains("\"allow\":[]"), json); + } + + @Test + void distinguishesExplicitEmptyAllowFromAbsentAllow() throws Exception { + // Present empty allow admits nothing; the null deny/ask must be omitted. + var permissions = new ManagedSettingsPermissions().setAllow(List.of()); + var managedSettings = new ManagedSettings().setPermissions(permissions); + var create = SessionRequestBuilder.buildCreateRequest(new SessionConfig().setManagedSettings(managedSettings), + "managed-mixed"); + + var json = new ObjectMapper().writeValueAsString(create); + assertTrue(json.contains("\"allow\":[]"), json); + assertFalse(json.contains("\"deny\""), json); + assertFalse(json.contains("\"ask\""), json); + } + + @Test + void directInjectionEnablesManagedSafeguards() throws Exception { + var session = new CopilotSession("session-1", null); + var settings = new ManagedSettings().setPermissions(new ManagedSettingsPermissions()); + var managedSettingsEnabled = new AtomicBoolean(); + var config = new SessionConfig().setManagedSettings(settings).setOnPermissionRequest((request, invocation) -> { + managedSettingsEnabled.set(invocation.isManagedSettingsEnabled()); + return CompletableFuture.completedFuture(PermissionRequestResult.noResult()); + }); + + SessionRequestBuilder.configureSession(session, config); + session.handlePermissionRequest(new ObjectMapper().readTree("{\"kind\":\"read\"}")).get(); + + assertTrue(managedSettingsEnabled.get()); + } +} diff --git a/java/src/test/java/com/github/copilot/McpAndAgentsTest.java b/java/sdk/src/test/java/com/github/copilot/McpAndAgentsTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/McpAndAgentsTest.java rename to java/sdk/src/test/java/com/github/copilot/McpAndAgentsTest.java diff --git a/java/src/test/java/com/github/copilot/McpAuthInterestRegistrationTest.java b/java/sdk/src/test/java/com/github/copilot/McpAuthInterestRegistrationTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/McpAuthInterestRegistrationTest.java rename to java/sdk/src/test/java/com/github/copilot/McpAuthInterestRegistrationTest.java diff --git a/java/src/test/java/com/github/copilot/McpOAuthE2ETest.java b/java/sdk/src/test/java/com/github/copilot/McpOAuthE2ETest.java similarity index 100% rename from java/src/test/java/com/github/copilot/McpOAuthE2ETest.java rename to java/sdk/src/test/java/com/github/copilot/McpOAuthE2ETest.java diff --git a/java/src/test/java/com/github/copilot/McpOAuthResumeE2ETest.java b/java/sdk/src/test/java/com/github/copilot/McpOAuthResumeE2ETest.java similarity index 100% rename from java/src/test/java/com/github/copilot/McpOAuthResumeE2ETest.java rename to java/sdk/src/test/java/com/github/copilot/McpOAuthResumeE2ETest.java diff --git a/java/src/test/java/com/github/copilot/MessageAttachmentTest.java b/java/sdk/src/test/java/com/github/copilot/MessageAttachmentTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/MessageAttachmentTest.java rename to java/sdk/src/test/java/com/github/copilot/MessageAttachmentTest.java diff --git a/java/src/test/java/com/github/copilot/MetadataApiTest.java b/java/sdk/src/test/java/com/github/copilot/MetadataApiTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/MetadataApiTest.java rename to java/sdk/src/test/java/com/github/copilot/MetadataApiTest.java diff --git a/java/src/test/java/com/github/copilot/ModeHandlersTest.java b/java/sdk/src/test/java/com/github/copilot/ModeHandlersTest.java similarity index 91% rename from java/src/test/java/com/github/copilot/ModeHandlersTest.java rename to java/sdk/src/test/java/com/github/copilot/ModeHandlersTest.java index 5128a31ac..942b2efe6 100644 --- a/java/src/test/java/com/github/copilot/ModeHandlersTest.java +++ b/java/sdk/src/test/java/com/github/copilot/ModeHandlersTest.java @@ -7,18 +7,19 @@ import static org.junit.jupiter.api.Assertions.*; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import com.github.copilot.generated.ExitPlanModeAction; import com.github.copilot.generated.ExitPlanModeCompletedEvent; import com.github.copilot.generated.ExitPlanModeRequestedEvent; +import com.github.copilot.rpc.AgentMode; import com.github.copilot.rpc.AutoModeSwitchRequest; import com.github.copilot.rpc.AutoModeSwitchResponse; import com.github.copilot.rpc.CopilotClientOptions; @@ -68,7 +69,6 @@ private void configureAuthenticatedUser(String testName) throws Exception { } @Test - @Disabled("Snapshot needs re-recording for CLI 1.0.57: https://github.com/github/copilot-sdk/issues/1547") void shouldInvokeExitPlanModeHandlerWhenModelUsesTool() throws Exception { final String summary = "Greeting file implementation plan"; configureAuthenticatedUser("should_invoke_exit_plan_mode_handler_when_model_uses_tool"); @@ -99,20 +99,23 @@ void shouldInvokeExitPlanModeHandlerWhenModelUsesTool() throws Exception { var response = session.sendAndWait(new MessageOptions().setPrompt( "Create a brief implementation plan for adding a greeting.txt file, then request approval with exit_plan_mode.") - .setMode("plan")).get(120, TimeUnit.SECONDS); + .setAgentMode(AgentMode.PLAN)).get(120, TimeUnit.SECONDS); var request = handlerCalled.get(10, TimeUnit.SECONDS); assertEquals(summary, request.getSummary()); - assertNotNull(request.getActions()); - assertTrue(request.getActions().contains("interactive")); + // Canonical action order after CLI 1.0.57+ (aligned with #2023 / other SDKs). + assertEquals(List.of("autopilot", "interactive", "exit_only"), request.getActions()); + assertEquals("interactive", request.getRecommendedAction()); assertNotNull(request.getPlanContent()); var reqEvent = requestedEvent.get(10, TimeUnit.SECONDS); assertEquals(request.getSummary(), reqEvent.getData().summary()); + assertEquals(ExitPlanModeAction.INTERACTIVE, reqEvent.getData().recommendedAction()); var compEvent = completedEvent.get(10, TimeUnit.SECONDS); assertTrue(compEvent.getData().approved()); assertEquals(ExitPlanModeAction.INTERACTIVE, compEvent.getData().selectedAction()); + assertEquals("Approved by the Java E2E test", compEvent.getData().feedback()); assertNotNull(response); diff --git a/java/src/test/java/com/github/copilot/ModelInfoTest.java b/java/sdk/src/test/java/com/github/copilot/ModelInfoTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/ModelInfoTest.java rename to java/sdk/src/test/java/com/github/copilot/ModelInfoTest.java diff --git a/java/src/test/java/com/github/copilot/ModuleDescriptorTest.java b/java/sdk/src/test/java/com/github/copilot/ModuleDescriptorTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/ModuleDescriptorTest.java rename to java/sdk/src/test/java/com/github/copilot/ModuleDescriptorTest.java diff --git a/java/src/test/java/com/github/copilot/MultiProviderConfigTest.java b/java/sdk/src/test/java/com/github/copilot/MultiProviderConfigTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/MultiProviderConfigTest.java rename to java/sdk/src/test/java/com/github/copilot/MultiProviderConfigTest.java diff --git a/java/src/test/java/com/github/copilot/MultiProviderRegistryE2ETest.java b/java/sdk/src/test/java/com/github/copilot/MultiProviderRegistryE2ETest.java similarity index 100% rename from java/src/test/java/com/github/copilot/MultiProviderRegistryE2ETest.java rename to java/sdk/src/test/java/com/github/copilot/MultiProviderRegistryE2ETest.java diff --git a/java/src/test/java/com/github/copilot/OptionalApiAndJacksonTest.java b/java/sdk/src/test/java/com/github/copilot/OptionalApiAndJacksonTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/OptionalApiAndJacksonTest.java rename to java/sdk/src/test/java/com/github/copilot/OptionalApiAndJacksonTest.java diff --git a/java/src/test/java/com/github/copilot/PerSessionAuthTest.java b/java/sdk/src/test/java/com/github/copilot/PerSessionAuthTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/PerSessionAuthTest.java rename to java/sdk/src/test/java/com/github/copilot/PerSessionAuthTest.java diff --git a/java/src/test/java/com/github/copilot/PermissionRequestResultKindTest.java b/java/sdk/src/test/java/com/github/copilot/PermissionRequestResultKindTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/PermissionRequestResultKindTest.java rename to java/sdk/src/test/java/com/github/copilot/PermissionRequestResultKindTest.java diff --git a/java/sdk/src/test/java/com/github/copilot/PermissionRequestResultTest.java b/java/sdk/src/test/java/com/github/copilot/PermissionRequestResultTest.java new file mode 100644 index 000000000..c1ca9191b --- /dev/null +++ b/java/sdk/src/test/java/com/github/copilot/PermissionRequestResultTest.java @@ -0,0 +1,165 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +import com.github.copilot.generated.PermissionRequestedEvent; +import com.github.copilot.rpc.PermissionRequestResult; +import com.github.copilot.rpc.PermissionHandler; +import com.github.copilot.rpc.PermissionInvocation; +import com.github.copilot.rpc.PermissionRequest; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.annotation.JsonInclude; + +/** + * Tests for {@link PermissionRequestResult} factory methods and feedback field. + */ +public class PermissionRequestResultTest { + + private static final ObjectMapper MAPPER = JsonMapper.builder().serializationInclusion(JsonInclude.Include.NON_NULL) + .build(); + + @Test + void testApproveOnce() { + var result = PermissionRequestResult.approveOnce(); + assertEquals("approve-once", result.getKind()); + assertNull(result.getFeedback()); + } + + @Test + void testRejectWithFeedback() { + var result = PermissionRequestResult.reject("Not allowed"); + assertEquals("reject", result.getKind()); + assertEquals("Not allowed", result.getFeedback()); + } + + @Test + void testRejectWithoutFeedback() { + var result = PermissionRequestResult.reject(null); + assertEquals("reject", result.getKind()); + assertNull(result.getFeedback()); + } + + @Test + void testUserNotAvailable() { + var result = PermissionRequestResult.userNotAvailable(); + assertEquals("user-not-available", result.getKind()); + assertNull(result.getFeedback()); + } + + @Test + void testNoResult() { + var result = PermissionRequestResult.noResult(); + assertEquals("no-result", result.getKind()); + assertNull(result.getFeedback()); + } + + @Test + void testFeedbackSerialized() throws Exception { + var result = PermissionRequestResult.reject("Unsafe operation"); + var json = MAPPER.writeValueAsString(result); + assertTrue(json.contains("\"feedback\":\"Unsafe operation\"")); + assertTrue(json.contains("\"kind\":\"reject\"")); + } + + @Test + void testFeedbackNotSerializedWhenNull() throws Exception { + var result = PermissionRequestResult.approveOnce(); + var json = MAPPER.writeValueAsString(result); + assertFalse(json.contains("feedback")); + } + + @Test + void testPermissionRequestExposesManagedApprovalRequired() throws Exception { + var request = MAPPER.readValue(""" + { + "kind": "read", + "path": "/workspace/file.txt", + "managedApprovalRequired": true + } + """, PermissionRequest.class); + + assertTrue(request.getManagedApprovalRequired()); + } + + @Test + void testMalformedManagedApprovalRequiredFailsClosed() throws Exception { + var request = MAPPER.readValue(""" + { + "kind": "read", + "managedApprovalRequired": 0 + } + """, PermissionRequest.class); + + assertTrue(request.getManagedApprovalRequired()); + var result = PermissionHandler.APPROVE_ALL.handle(request, new PermissionInvocation()).join(); + assertEquals("no-result", result.getKind()); + } + + @Test + void testManagedApprovalRequiredPreservesFalse() throws Exception { + var request = MAPPER.readValue(""" + { + "kind": "read", + "managedApprovalRequired": false + } + """, PermissionRequest.class); + + assertFalse(request.getManagedApprovalRequired()); + var result = PermissionHandler.APPROVE_ALL.handle(request, new PermissionInvocation()).join(); + assertEquals("approve-once", result.getKind()); + } + + @Test + void testPermissionEventValueConvertsToTypedRequest() { + var event = MAPPER + .convertValue( + java.util.Map.of("type", "permission.requested", "data", + java.util.Map.of("requestId", "permission-1", "permissionRequest", java.util.Map.of( + "kind", "url", "managedApprovalRequired", true, "url", "https://example.com"))), + PermissionRequestedEvent.class); + var request = PermissionRequest.fromJsonValue(event.getData().permissionRequest()); + + assertTrue(request.getManagedApprovalRequired()); + } + + @Test + void testApproveAllFailsWhenManagedSettingsEnabled() { + var request = new PermissionRequest(); + request.setKind("read"); + request.setManagedApprovalRequired(true); + + var invocation = new PermissionInvocation().setManagedSettingsEnabled(true); + var error = assertThrows(java.util.concurrent.CompletionException.class, + () -> PermissionHandler.APPROVE_ALL.handle(request, invocation).join()); + + assertTrue(error.getCause() instanceof IllegalStateException); + } + + @Test + void testApproveAllApprovesOrdinaryRequest() { + var request = new PermissionRequest(); + request.setKind("read"); + + var result = PermissionHandler.APPROVE_ALL.handle(request, new PermissionInvocation()).join(); + + assertEquals("approve-once", result.getKind()); + } + + @Test + void testApproveAllLeavesManagedRequestPendingWhenSessionFlagIsAbsent() { + var request = new PermissionRequest(); + request.setKind("read"); + request.setManagedApprovalRequired(true); + + var result = PermissionHandler.APPROVE_ALL.handle(request, new PermissionInvocation()).join(); + + assertEquals("no-result", result.getKind()); + } +} diff --git a/java/src/test/java/com/github/copilot/PermissionsTest.java b/java/sdk/src/test/java/com/github/copilot/PermissionsTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/PermissionsTest.java rename to java/sdk/src/test/java/com/github/copilot/PermissionsTest.java diff --git a/java/src/test/java/com/github/copilot/PreMcpToolCallHookTest.java b/java/sdk/src/test/java/com/github/copilot/PreMcpToolCallHookTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/PreMcpToolCallHookTest.java rename to java/sdk/src/test/java/com/github/copilot/PreMcpToolCallHookTest.java diff --git a/java/src/test/java/com/github/copilot/ProviderConfigTest.java b/java/sdk/src/test/java/com/github/copilot/ProviderConfigTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/ProviderConfigTest.java rename to java/sdk/src/test/java/com/github/copilot/ProviderConfigTest.java diff --git a/java/src/test/java/com/github/copilot/ProviderEndpointE2ETest.java b/java/sdk/src/test/java/com/github/copilot/ProviderEndpointE2ETest.java similarity index 100% rename from java/src/test/java/com/github/copilot/ProviderEndpointE2ETest.java rename to java/sdk/src/test/java/com/github/copilot/ProviderEndpointE2ETest.java diff --git a/java/src/test/java/com/github/copilot/RemoteSessionTest.java b/java/sdk/src/test/java/com/github/copilot/RemoteSessionTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/RemoteSessionTest.java rename to java/sdk/src/test/java/com/github/copilot/RemoteSessionTest.java diff --git a/java/src/test/java/com/github/copilot/RpcHandlerDispatcherTest.java b/java/sdk/src/test/java/com/github/copilot/RpcHandlerDispatcherTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/RpcHandlerDispatcherTest.java rename to java/sdk/src/test/java/com/github/copilot/RpcHandlerDispatcherTest.java diff --git a/java/src/test/java/com/github/copilot/RpcServerE2ETest.java b/java/sdk/src/test/java/com/github/copilot/RpcServerE2ETest.java similarity index 100% rename from java/src/test/java/com/github/copilot/RpcServerE2ETest.java rename to java/sdk/src/test/java/com/github/copilot/RpcServerE2ETest.java diff --git a/java/src/test/java/com/github/copilot/RpcServerMiscE2ETest.java b/java/sdk/src/test/java/com/github/copilot/RpcServerMiscE2ETest.java similarity index 100% rename from java/src/test/java/com/github/copilot/RpcServerMiscE2ETest.java rename to java/sdk/src/test/java/com/github/copilot/RpcServerMiscE2ETest.java diff --git a/java/src/test/java/com/github/copilot/RpcSessionStateExtrasE2ETest.java b/java/sdk/src/test/java/com/github/copilot/RpcSessionStateExtrasE2ETest.java similarity index 99% rename from java/src/test/java/com/github/copilot/RpcSessionStateExtrasE2ETest.java rename to java/sdk/src/test/java/com/github/copilot/RpcSessionStateExtrasE2ETest.java index 83365455e..5022d2a56 100644 --- a/java/src/test/java/com/github/copilot/RpcSessionStateExtrasE2ETest.java +++ b/java/sdk/src/test/java/com/github/copilot/RpcSessionStateExtrasE2ETest.java @@ -65,7 +65,7 @@ void testShouldAddByokProviderAndModelAtRuntime() throws Exception { var selectionId = "java-e2e-provider/small"; session.getRpc().model - .switchTo(new SessionModelSwitchToParams(null, selectionId, null, null, null, null, null)) + .switchTo(new SessionModelSwitchToParams(null, selectionId, null, null, null, null, null, null)) .get(30, TimeUnit.SECONDS); var current = session.getRpc().model.getCurrent().get(30, TimeUnit.SECONDS); assertEquals(selectionId, current.modelId()); diff --git a/java/src/test/java/com/github/copilot/RpcTasksAndHandlersE2ETest.java b/java/sdk/src/test/java/com/github/copilot/RpcTasksAndHandlersE2ETest.java similarity index 100% rename from java/src/test/java/com/github/copilot/RpcTasksAndHandlersE2ETest.java rename to java/sdk/src/test/java/com/github/copilot/RpcTasksAndHandlersE2ETest.java diff --git a/java/src/test/java/com/github/copilot/RpcWrappersTest.java b/java/sdk/src/test/java/com/github/copilot/RpcWrappersTest.java similarity index 98% rename from java/src/test/java/com/github/copilot/RpcWrappersTest.java rename to java/sdk/src/test/java/com/github/copilot/RpcWrappersTest.java index 7493c6e47..1f1785cba 100644 --- a/java/src/test/java/com/github/copilot/RpcWrappersTest.java +++ b/java/sdk/src/test/java/com/github/copilot/RpcWrappersTest.java @@ -205,7 +205,7 @@ void sessionRpc_model_switchTo_merges_sessionId_with_extra_params() { var session = new SessionRpc(stub, "sess-xyz"); // switchTo takes extra params beyond sessionId - var switchParams = new SessionModelSwitchToParams(null, "gpt-5", null, null, null, null, null); + var switchParams = new SessionModelSwitchToParams(null, "gpt-5", null, null, null, null, null, null); session.model.switchTo(switchParams); assertEquals(1, stub.calls.size()); @@ -230,8 +230,8 @@ void sessionRpc_agent_list_injects_sessionId() { assertEquals("session.agent.list", stub.calls.get(0).method()); var params = stub.calls.get(0).params(); - assertInstanceOf(Map.class, params); - assertEquals("sess-999", ((Map) params).get("sessionId")); + assertInstanceOf(com.fasterxml.jackson.databind.node.ObjectNode.class, params); + assertEquals("sess-999", ((com.fasterxml.jackson.databind.node.ObjectNode) params).get("sessionId").asText()); } @Test diff --git a/java/src/test/java/com/github/copilot/SchedulerShutdownRaceTest.java b/java/sdk/src/test/java/com/github/copilot/SchedulerShutdownRaceTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/SchedulerShutdownRaceTest.java rename to java/sdk/src/test/java/com/github/copilot/SchedulerShutdownRaceTest.java diff --git a/java/src/test/java/com/github/copilot/SessionCanvasSnapshotTest.java b/java/sdk/src/test/java/com/github/copilot/SessionCanvasSnapshotTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/SessionCanvasSnapshotTest.java rename to java/sdk/src/test/java/com/github/copilot/SessionCanvasSnapshotTest.java diff --git a/java/src/test/java/com/github/copilot/SessionConfigE2ETest.java b/java/sdk/src/test/java/com/github/copilot/SessionConfigE2ETest.java similarity index 100% rename from java/src/test/java/com/github/copilot/SessionConfigE2ETest.java rename to java/sdk/src/test/java/com/github/copilot/SessionConfigE2ETest.java diff --git a/java/src/test/java/com/github/copilot/SessionEventDeserializationTest.java b/java/sdk/src/test/java/com/github/copilot/SessionEventDeserializationTest.java similarity index 96% rename from java/src/test/java/com/github/copilot/SessionEventDeserializationTest.java rename to java/sdk/src/test/java/com/github/copilot/SessionEventDeserializationTest.java index 516d1ecf4..8d9b70a34 100644 --- a/java/src/test/java/com/github/copilot/SessionEventDeserializationTest.java +++ b/java/sdk/src/test/java/com/github/copilot/SessionEventDeserializationTest.java @@ -113,6 +113,54 @@ void testParseSessionIdleEvent() throws Exception { assertEquals("session.idle", event.getType()); } + @Test + void testManagedSettingsResolvedClientProvenance() throws Exception { + assertEquals("server", ManagedSettingsResolvedSource.SERVER.getValue()); + assertEquals("device", ManagedSettingsResolvedSource.DEVICE.getValue()); + assertEquals("client", ManagedSettingsResolvedSource.CLIENT.getValue()); + assertEquals("mixed", ManagedSettingsResolvedSource.MIXED.getValue()); + assertEquals("none", ManagedSettingsResolvedSource.NONE.getValue()); + + String clientJson = """ + { + "type": "session.managed_settings_resolved", + "data": { + "source": "client", + "serverManaged": false, + "deviceManaged": false, + "clientManaged": true, + "failClosed": false, + "bypassPermissionsDisabled": true, + "managedKeys": ["permissions"] + } + } + """; + + var clientEvent = assertInstanceOf(SessionManagedSettingsResolvedEvent.class, parseJson(clientJson)); + assertEquals(ManagedSettingsResolvedSource.CLIENT, clientEvent.getData().source()); + assertEquals(Boolean.TRUE, clientEvent.getData().clientManaged()); + assertTrue(MAPPER.writeValueAsString(clientEvent).contains("\"clientManaged\":true")); + + String mixedJson = """ + { + "type": "session.managed_settings_resolved", + "data": { + "source": "mixed", + "serverManaged": true, + "deviceManaged": true, + "failClosed": false, + "bypassPermissionsDisabled": true, + "managedKeys": ["permissions"] + } + } + """; + + var mixedEvent = assertInstanceOf(SessionManagedSettingsResolvedEvent.class, parseJson(mixedJson)); + assertEquals(ManagedSettingsResolvedSource.MIXED, mixedEvent.getData().source()); + assertNull(mixedEvent.getData().clientManaged()); + assertFalse(MAPPER.writeValueAsString(mixedEvent).contains("\"clientManaged\"")); + } + @Test void testParseSessionInfoEvent() throws Exception { String json = """ @@ -897,15 +945,16 @@ void testParseEmptyJson() throws Exception { @Test void testParseAllEventTypes() throws Exception { String[] types = {"session.start", "session.resume", "session.error", "session.idle", "session.info", - "session.model_change", "session.mode_changed", "session.plan_changed", - "session.workspace_file_changed", "session.handoff", "session.truncation", "session.snapshot_rewind", - "session.usage_info", "session.compaction_start", "session.compaction_complete", "user.message", - "pending_messages.modified", "assistant.turn_start", "assistant.intent", "assistant.reasoning", - "assistant.reasoning_delta", "assistant.message", "assistant.message_delta", "assistant.turn_end", - "assistant.usage", "abort", "tool.user_requested", "tool.execution_start", - "tool.execution_partial_result", "tool.execution_progress", "tool.execution_complete", - "subagent.started", "subagent.completed", "subagent.failed", "subagent.selected", "hook.start", - "hook.end", "system.message", "session.shutdown", "skill.invoked"}; + "session.model_change", "session.mode_changed", "session.managed_settings_resolved", + "session.managed_settings_enforced", "session.plan_changed", "session.workspace_file_changed", + "session.handoff", "session.truncation", "session.snapshot_rewind", "session.usage_info", + "session.compaction_start", "session.compaction_complete", "user.message", "pending_messages.modified", + "assistant.turn_start", "assistant.intent", "assistant.reasoning", "assistant.reasoning_delta", + "assistant.message", "assistant.message_delta", "assistant.turn_end", "assistant.usage", "abort", + "tool.user_requested", "tool.execution_start", "tool.execution_partial_result", + "tool.execution_progress", "tool.execution_complete", "subagent.started", "subagent.completed", + "subagent.failed", "subagent.selected", "hook.start", "hook.end", "system.message", "session.shutdown", + "skill.invoked"}; for (String type : types) { String json = """ @@ -2553,7 +2602,8 @@ void testParseSessionTaskCompleteEvent() throws Exception { assertEquals("Task completed successfully", castedEvent.getData().summary()); // Verify setData round-trip - castedEvent.setData(new SessionTaskCompleteEvent.SessionTaskCompleteEventData("New summary", null)); + castedEvent.setData( + new SessionTaskCompleteEvent.SessionTaskCompleteEventData("New summary", null, null, null, null)); assertEquals("New summary", castedEvent.getData().summary()); } diff --git a/java/src/test/java/com/github/copilot/SessionEventHandlingTest.java b/java/sdk/src/test/java/com/github/copilot/SessionEventHandlingTest.java similarity index 99% rename from java/src/test/java/com/github/copilot/SessionEventHandlingTest.java rename to java/sdk/src/test/java/com/github/copilot/SessionEventHandlingTest.java index 1cf3ceff1..bd38d4962 100644 --- a/java/src/test/java/com/github/copilot/SessionEventHandlingTest.java +++ b/java/sdk/src/test/java/com/github/copilot/SessionEventHandlingTest.java @@ -180,7 +180,7 @@ void testHandlerReceivesCorrectEventData() { SessionStartEvent startEvent = createSessionStartEvent(); startEvent.setData(new SessionStartEvent.SessionStartEventData("my-session-123", null, null, null, null, null, - null, null, null, null, null, null, null, null, null)); + null, null, null, null, null, null, null, null, null, null)); dispatchEvent(startEvent); AssistantMessageEvent msgEvent = createAssistantMessageEvent("Test content"); @@ -857,7 +857,7 @@ private SessionStartEvent createSessionStartEvent() { private SessionStartEvent createSessionStartEvent(String sessionId) { var event = new SessionStartEvent(); var data = new SessionStartEvent.SessionStartEventData(sessionId, null, null, null, null, null, null, null, - null, null, null, null, null, null, null); + null, null, null, null, null, null, null, null); event.setData(data); return event; } @@ -865,7 +865,7 @@ private SessionStartEvent createSessionStartEvent(String sessionId) { private AssistantMessageEvent createAssistantMessageEvent(String content) { var event = new AssistantMessageEvent(); var data = new AssistantMessageEvent.AssistantMessageEventData(null, null, content, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null); + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null); event.setData(data); return event; } diff --git a/java/src/test/java/com/github/copilot/SessionEventsE2ETest.java b/java/sdk/src/test/java/com/github/copilot/SessionEventsE2ETest.java similarity index 98% rename from java/src/test/java/com/github/copilot/SessionEventsE2ETest.java rename to java/sdk/src/test/java/com/github/copilot/SessionEventsE2ETest.java index 161839a53..dad75db52 100644 --- a/java/src/test/java/com/github/copilot/SessionEventsE2ETest.java +++ b/java/sdk/src/test/java/com/github/copilot/SessionEventsE2ETest.java @@ -5,6 +5,7 @@ package com.github.copilot; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import java.nio.file.Files; @@ -184,6 +185,9 @@ void testShouldReceiveSessionEvents_assistantUsageEvent() throws Exception { // Usage events may or may not be emitted depending on the model/API version // This test verifies the event handler works when they are emitted // We don't assert they must be present since it depends on the backend + if (!usageEvents.isEmpty()) { + assertNotNull(usageEvents.get(0).getData(), "Usage event should carry data"); + } } } diff --git a/java/src/test/java/com/github/copilot/SessionHandlerTest.java b/java/sdk/src/test/java/com/github/copilot/SessionHandlerTest.java similarity index 85% rename from java/src/test/java/com/github/copilot/SessionHandlerTest.java rename to java/sdk/src/test/java/com/github/copilot/SessionHandlerTest.java index 1b672e6e2..345fdccff 100644 --- a/java/src/test/java/com/github/copilot/SessionHandlerTest.java +++ b/java/sdk/src/test/java/com/github/copilot/SessionHandlerTest.java @@ -16,6 +16,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.copilot.rpc.AgentStopHookOutput; import com.github.copilot.rpc.PermissionRequestResult; import com.github.copilot.rpc.PermissionRequestResultKind; import com.github.copilot.rpc.SessionEndHookOutput; @@ -25,6 +26,7 @@ import com.github.copilot.rpc.UserInputRequest; import com.github.copilot.rpc.UserInputResponse; import com.github.copilot.rpc.UserPromptSubmittedHookOutput; +import com.github.copilot.rpc.UserPromptTransformedHookOutput; /** * Unit tests for CopilotSession internal handler methods. @@ -224,6 +226,26 @@ void testHandleHooksInvokeUserPromptSubmitted() throws Exception { assertEquals("modified prompt", output.modifiedPrompt()); } + @Test + void testHandleHooksInvokeUserPromptTransformed() throws Exception { + var hooks = new SessionHooks().setOnUserPromptTransformed((hookInput, invocation) -> { + assertEquals("handler-test-session", invocation.getSessionId()); + assertEquals("original prompt", hookInput.prompt()); + assertEquals("transformed prompt", hookInput.transformedPrompt()); + return CompletableFuture.completedFuture(new UserPromptTransformedHookOutput("replacement prompt")); + }); + session.registerHooks(hooks); + + JsonNode input = MAPPER.valueToTree(Map.of("sessionId", "runtime-session", "timestamp", 1735689600L, "cwd", + "/tmp", "prompt", "original prompt", "transformedPrompt", "transformed prompt")); + + Object result = session.handleHooksInvoke("userPromptTransformed", input).get(); + + assertInstanceOf(UserPromptTransformedHookOutput.class, result); + var output = (UserPromptTransformedHookOutput) result; + assertEquals("replacement prompt", output.modifiedTransformedPrompt()); + } + // ===== handleHooksInvoke: sessionStart ===== @Test @@ -262,6 +284,32 @@ void testHandleHooksInvokeSessionEnd() throws Exception { assertEquals("summary", output.sessionSummary()); } + // ===== handleHooksInvoke: agentStop ===== + + @Test + void testHandleHooksInvokeAgentStop() throws Exception { + var hooks = new SessionHooks().setOnAgentStop((hookInput, invocation) -> { + assertEquals("handler-test-session", invocation.getSessionId()); + assertEquals("runtime-session-123", hookInput.getSessionId()); + assertEquals("end_turn", hookInput.getStopReason()); + assertEquals("/tmp/transcript.jsonl", hookInput.getTranscriptPath()); + assertTrue(hookInput.getStopHookActive()); + return CompletableFuture.completedFuture( + new AgentStopHookOutput().setDecision("block").setReason("finish the remaining work")); + }); + session.registerHooks(hooks); + + JsonNode input = MAPPER.valueToTree(Map.of("sessionId", "runtime-session-123", "timestamp", 1735689600L, "cwd", + "/tmp", "stopReason", "end_turn", "transcriptPath", "/tmp/transcript.jsonl", "stop_hook_active", true)); + + Object result = session.handleHooksInvoke("agentStop", input).get(); + + assertInstanceOf(AgentStopHookOutput.class, result); + var output = (AgentStopHookOutput) result; + assertEquals("block", output.getDecision()); + assertEquals("finish the remaining work", output.getReason()); + } + // ===== handleHooksInvoke: sessionId deserialization on hook inputs ===== @Test diff --git a/java/src/test/java/com/github/copilot/SessionRequestBuilderTest.java b/java/sdk/src/test/java/com/github/copilot/SessionRequestBuilderTest.java similarity index 86% rename from java/src/test/java/com/github/copilot/SessionRequestBuilderTest.java rename to java/sdk/src/test/java/com/github/copilot/SessionRequestBuilderTest.java index 652be026b..0525786de 100644 --- a/java/src/test/java/com/github/copilot/SessionRequestBuilderTest.java +++ b/java/sdk/src/test/java/com/github/copilot/SessionRequestBuilderTest.java @@ -16,6 +16,7 @@ import com.github.copilot.rpc.AutoModeSwitchResponse; import com.github.copilot.rpc.CloudSessionOptions; import com.github.copilot.rpc.CloudSessionRepository; +import com.github.copilot.rpc.CopilotClientMode; import com.github.copilot.rpc.CopilotExpAssignmentResponse; import com.github.copilot.rpc.CreateSessionRequest; import com.github.copilot.rpc.DefaultAgentConfig; @@ -24,6 +25,7 @@ import com.github.copilot.rpc.ElicitationResultAction; import com.github.copilot.rpc.ExitPlanModeResult; import com.github.copilot.rpc.ExpConfigEntry; +import com.github.copilot.rpc.GitHubMcpToolConfig; import com.github.copilot.rpc.LargeToolOutputConfig; import com.github.copilot.rpc.MemoryConfiguration; import com.github.copilot.rpc.ResumeSessionConfig; @@ -88,6 +90,36 @@ void testBuildCreateRequestAlwaysSetsRequestPermissionTrue() { "requestPermission should always be true to enable deny-by-default behavior"); } + @Test + void testBuildRequestsResolveAndSerializeCustomAgentsLocalOnly() throws Exception { + var mapper = JsonRpcClient.getObjectMapper(); + + var explicitCreate = SessionRequestBuilder + .buildCreateRequest(new SessionConfig().setCustomAgentsLocalOnly(false), "create-explicit"); + var explicitResume = SessionRequestBuilder.buildResumeRequest("resume-explicit", + new ResumeSessionConfig().setCustomAgentsLocalOnly(false)); + assertFalse(explicitCreate.getCustomAgentsLocalOnly()); + assertFalse(explicitResume.getCustomAgentsLocalOnly()); + assertTrue(mapper.writeValueAsString(explicitCreate).contains("\"customAgentsLocalOnly\":false")); + assertTrue(mapper.writeValueAsString(explicitResume).contains("\"customAgentsLocalOnly\":false")); + + var emptyCreate = SessionRequestBuilder.buildCreateRequest(new SessionConfig(), "create-empty", + CopilotClientMode.EMPTY); + var emptyResume = SessionRequestBuilder.buildResumeRequest("resume-empty", new ResumeSessionConfig(), + CopilotClientMode.EMPTY); + assertTrue(emptyCreate.getCustomAgentsLocalOnly()); + assertTrue(emptyResume.getCustomAgentsLocalOnly()); + assertTrue(mapper.writeValueAsString(emptyCreate).contains("\"customAgentsLocalOnly\":true")); + assertTrue(mapper.writeValueAsString(emptyResume).contains("\"customAgentsLocalOnly\":true")); + + var cliCreate = SessionRequestBuilder.buildCreateRequest(new SessionConfig(), "create-cli"); + var cliResume = SessionRequestBuilder.buildResumeRequest("resume-cli", new ResumeSessionConfig()); + assertNull(cliCreate.getCustomAgentsLocalOnly()); + assertNull(cliResume.getCustomAgentsLocalOnly()); + assertFalse(mapper.writeValueAsString(cliCreate).contains("\"customAgentsLocalOnly\"")); + assertFalse(mapper.writeValueAsString(cliResume).contains("\"customAgentsLocalOnly\"")); + } + @Test void testBuildCreateRequestSetsClientName() { var config = new SessionConfig().setClientName("my-app"); @@ -95,6 +127,13 @@ void testBuildCreateRequestSetsClientName() { assertEquals("my-app", request.getClientName()); } + @Test + void testBuildCreateRequestSetsAdditionalDirectories() { + var config = new SessionConfig().setAdditionalDirectories(List.of("/repo/shared", "/repo/generated")); + CreateSessionRequest request = SessionRequestBuilder.buildCreateRequest(config); + assertEquals(List.of("/repo/shared", "/repo/generated"), request.getAdditionalDirectories()); + } + @Test void testBuildCreateRequestSetsReasoningSummary() { var config = new SessionConfig().setReasoningSummary("concise"); @@ -102,6 +141,26 @@ void testBuildCreateRequestSetsReasoningSummary() { assertEquals("concise", request.getReasoningSummary()); } + @Test + void testBuildCreateRequestSetsEnableExperimentalMode() { + var config = new SessionConfig().setEnableExperimentalMode(false); + CreateSessionRequest request = SessionRequestBuilder.buildCreateRequest(config); + assertFalse(request.getIsExperimentalMode()); + } + + @Test + void testBuildCreateRequestOmitsEnableExperimentalModeWhenNotSet() { + CreateSessionRequest request = SessionRequestBuilder.buildCreateRequest(new SessionConfig()); + assertNull(request.getIsExperimentalMode()); + } + + @Test + void testBuildCreateRequestDefaultsEnableExperimentalModeFalseInEmptyMode() { + CreateSessionRequest request = SessionRequestBuilder.buildCreateRequest(new SessionConfig(), "sid-empty", + CopilotClientMode.EMPTY); + assertFalse(request.getIsExperimentalMode()); + } + @Test void testBuildCreateRequestSetsContextTier() { var config = new SessionConfig().setContextTier("long_context"); @@ -110,13 +169,17 @@ void testBuildCreateRequestSetsContextTier() { } @Test - void testBuildCreateRequestSetsPluginDirectoriesAndLargeOutput() { + void testBuildCreateRequestSetsPluginDirectoriesAndLargeOutput() throws Exception { var largeOutput = new LargeToolOutputConfig().setEnabled(true).setMaxSizeBytes(1024L) .setOutputDirectory("/tmp/out"); - var config = new SessionConfig().setPluginDirectories(List.of("/plugins/a")).setLargeOutput(largeOutput); + var config = new SessionConfig().setPluginDirectories(List.of("/plugins/a")) + .setDisabledMcpServers(List.of("local-files", "remote-github")).setLargeOutput(largeOutput); CreateSessionRequest request = SessionRequestBuilder.buildCreateRequest(config); assertEquals(List.of("/plugins/a"), request.getPluginDirectories()); + assertEquals(List.of("local-files", "remote-github"), request.getDisabledMcpServers()); assertEquals(largeOutput, request.getLargeOutput()); + assertTrue(JsonRpcClient.getObjectMapper().writeValueAsString(request) + .contains("\"disabledMcpServers\":[\"local-files\",\"remote-github\"]")); } @Test @@ -166,12 +229,13 @@ void testBuildCreateRequestForwardsExplicitMcpOAuthTokenStorage() { void testBuildCreateRequestForwardsSessionPolicyOptions() { var sessionLimits = new SessionLimitsConfig(30.0); var config = new SessionConfig().setExcludedBuiltInAgents(List.of("explore")).setEnableCitations(true) - .setSessionLimits(sessionLimits); + .setEnableFileChangeTracking(true).setSessionLimits(sessionLimits); CreateSessionRequest request = SessionRequestBuilder.buildCreateRequest(config, "session-policy"); assertEquals(List.of("explore"), request.getExcludedBuiltInAgents()); assertTrue(request.getEnableCitations()); + assertTrue(request.getEnableFileChangeTracking()); assertSame(sessionLimits, request.getSessionLimits()); } @@ -208,6 +272,27 @@ void testBuildResumeRequestOmitsEnableSessionTelemetryWhenNotSet() { assertNull(request.getEnableSessionTelemetry()); } + @Test + void testBuildResumeRequestSetsEnableExperimentalMode() { + var config = new ResumeSessionConfig().setEnableExperimentalMode(true); + ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("sid-1", config); + assertTrue(request.getIsExperimentalMode()); + } + + @Test + void testBuildResumeRequestOmitsEnableExperimentalModeWhenNotSet() { + var config = new ResumeSessionConfig(); + ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("sid-1", config); + assertNull(request.getIsExperimentalMode()); + } + + @Test + void testBuildResumeRequestDefaultsEnableExperimentalModeFalseInEmptyMode() { + ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("sid-empty", new ResumeSessionConfig(), + CopilotClientMode.EMPTY); + assertFalse(request.getIsExperimentalMode()); + } + @Test void testBuildResumeRequestWithTools() { var tool = ToolDefinition.create("my_tool", "A tool", Map.of("type", "object"), @@ -289,6 +374,13 @@ void testBuildResumeRequestSetsClientName() { assertEquals("my-app", request.getClientName()); } + @Test + void testBuildResumeRequestSetsAdditionalDirectories() { + var config = new ResumeSessionConfig().setAdditionalDirectories(List.of("/repo/resumed")); + ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("sid-additional-directories", config); + assertEquals(List.of("/repo/resumed"), request.getAdditionalDirectories()); + } + @Test void testBuildCreateRequestPropagatesGranularMultitenancyFields() { var config = new SessionConfig().setSkipEmbeddingRetrieval(true) @@ -343,12 +435,13 @@ void testBuildResumeRequestForwardsExplicitMcpOAuthTokenStorage() { void testBuildResumeRequestForwardsSessionPolicyOptions() { var sessionLimits = new SessionLimitsConfig(30.0); var config = new ResumeSessionConfig().setExcludedBuiltInAgents(List.of("explore")).setEnableCitations(true) - .setSessionLimits(sessionLimits); + .setEnableFileChangeTracking(true).setSessionLimits(sessionLimits); ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("sid-policy", config); assertEquals(List.of("explore"), request.getExcludedBuiltInAgents()); assertTrue(request.getEnableCitations()); + assertTrue(request.getEnableFileChangeTracking()); assertSame(sessionLimits, request.getSessionLimits()); } @@ -373,13 +466,17 @@ void testBuildResumeRequestSetsContextTier() { } @Test - void testBuildResumeRequestSetsPluginDirectoriesAndLargeOutput() { + void testBuildResumeRequestSetsPluginDirectoriesAndLargeOutput() throws Exception { var largeOutput = new LargeToolOutputConfig().setEnabled(false).setMaxSizeBytes(2048L) .setOutputDirectory("/tmp/resume"); - var config = new ResumeSessionConfig().setPluginDirectories(List.of("/plugins/r")).setLargeOutput(largeOutput); + var config = new ResumeSessionConfig().setPluginDirectories(List.of("/plugins/r")) + .setDisabledMcpServers(List.of("local-files-r")).setLargeOutput(largeOutput); ResumeSessionRequest request = SessionRequestBuilder.buildResumeRequest("sid-16", config); assertEquals(List.of("/plugins/r"), request.getPluginDirectories()); + assertEquals(List.of("local-files-r"), request.getDisabledMcpServers()); assertEquals(largeOutput, request.getLargeOutput()); + assertTrue(JsonRpcClient.getObjectMapper().writeValueAsString(request) + .contains("\"disabledMcpServers\":[\"local-files-r\"]")); } @Test @@ -958,4 +1055,23 @@ void testClonePreservesAndForwardsExpAssignments() throws Exception { assertEquals(resumeAssignments, resumeRequest.getExpAssignments()); assertTrue(mapper.writeValueAsString(resumeRequest).contains("\"Id\":\"exp-resume\"")); } + + @Test + void githubMcpToolConfigIsMappedAndSerializedForCreateAndResume() throws Exception { + var config = new GitHubMcpToolConfig().setEnableAllTools(true).setAdditionalToolsets(List.of("repos")) + .setAdditionalTools(List.of("get_issue")).setEnableInsidersMode(true).setDisableFormDeferral(true); + var createRequest = SessionRequestBuilder.buildCreateRequest(new SessionConfig().setGitHubMcpToolConfig(config), + "session-1"); + var resumeRequest = SessionRequestBuilder.buildResumeRequest("session-1", + new ResumeSessionConfig().setGitHubMcpToolConfig(config)); + + assertSame(config, createRequest.getGitHubMcpToolConfig()); + assertSame(config, resumeRequest.getGitHubMcpToolConfig()); + var mapper = JsonRpcClient.getObjectMapper(); + assertTrue(mapper.writeValueAsString(createRequest).contains("\"githubMcpToolConfig\"")); + assertTrue(mapper.writeValueAsString(resumeRequest).contains("\"githubMcpToolConfig\"")); + assertFalse( + mapper.writeValueAsString(SessionRequestBuilder.buildCreateRequest(new SessionConfig(), "session-2")) + .contains("\"githubMcpToolConfig\"")); + } } diff --git a/java/src/test/java/com/github/copilot/SessionTodosChangedTest.java b/java/sdk/src/test/java/com/github/copilot/SessionTodosChangedTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/SessionTodosChangedTest.java rename to java/sdk/src/test/java/com/github/copilot/SessionTodosChangedTest.java diff --git a/java/src/test/java/com/github/copilot/SkillsTest.java b/java/sdk/src/test/java/com/github/copilot/SkillsTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/SkillsTest.java rename to java/sdk/src/test/java/com/github/copilot/SkillsTest.java diff --git a/java/src/test/java/com/github/copilot/SlashCommandsIT.java b/java/sdk/src/test/java/com/github/copilot/SlashCommandsIT.java similarity index 98% rename from java/src/test/java/com/github/copilot/SlashCommandsIT.java rename to java/sdk/src/test/java/com/github/copilot/SlashCommandsIT.java index 634c0bad9..5dec06464 100644 --- a/java/src/test/java/com/github/copilot/SlashCommandsIT.java +++ b/java/sdk/src/test/java/com/github/copilot/SlashCommandsIT.java @@ -22,6 +22,8 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import com.github.copilot.e2e.SkipInProcess; + import com.github.copilot.generated.rpc.SessionCommandsListResult; import com.github.copilot.generated.rpc.SessionCommandsInvokeParams; import com.github.copilot.generated.rpc.SlashCommandAgentPromptResult; @@ -41,6 +43,7 @@ * Requires the CLI to be installed and the user to be signed in. Uses * {@link TestUtil#findCliPath()} so the test harness binary is found in CI. */ +@SkipInProcess("Requires a live signed-in CLI subprocess and logged-in-user transport behavior rather than the replayed in-process harness") class SlashCommandsIT { private static CopilotClient client; diff --git a/java/src/test/java/com/github/copilot/StreamingFidelityTest.java b/java/sdk/src/test/java/com/github/copilot/StreamingFidelityTest.java similarity index 86% rename from java/src/test/java/com/github/copilot/StreamingFidelityTest.java rename to java/sdk/src/test/java/com/github/copilot/StreamingFidelityTest.java index 631496a8f..3701cf9c1 100644 --- a/java/src/test/java/com/github/copilot/StreamingFidelityTest.java +++ b/java/sdk/src/test/java/com/github/copilot/StreamingFidelityTest.java @@ -249,33 +249,37 @@ void testShouldNotProduceDeltasAfterSessionResumeWithStreamingDisabled() throws */ @Test void testShouldEmitStreamingDeltasWithReasoningEffortConfigured() throws Exception { - ctx.configureForTest("streaming_fidelity", "should_emit_streaming_deltas_with_reasoning_effort_configured"); + try (E2ETestContext isolatedContext = E2ETestContext.create()) { + isolatedContext.configureForTest("streaming_fidelity", + "should_emit_streaming_deltas_with_reasoning_effort_configured"); - try (CopilotClient client = ctx.createClient()) { - CopilotSession session = client - .createSession(new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) - .setStreaming(true).setReasoningEffort("high")) - .get(); + try (CopilotClient client = isolatedContext.createClient()) { + CopilotSession session = client + .createSession(new SessionConfig().setOnPermissionRequest(PermissionHandler.APPROVE_ALL) + .setModel("gpt-5.4").setStreaming(true).setReasoningEffort("high")) + .get(); - List events = new ArrayList<>(); - session.on(events::add); + List events = new ArrayList<>(); + session.on(events::add); - session.sendAndWait(new MessageOptions().setPrompt("What is 15 * 17?")).get(60, TimeUnit.SECONDS); + session.sendAndWait(new MessageOptions().setPrompt("What is 15 * 17?")).get(60, TimeUnit.SECONDS); - // With streaming + reasoning effort, we should still get content deltas - List deltaEvents = events.stream() - .filter(e -> e instanceof AssistantMessageDeltaEvent).map(e -> (AssistantMessageDeltaEvent) e) - .toList(); - assertFalse(deltaEvents.isEmpty(), "Should have received delta events with reasoning effort configured"); + // With streaming + reasoning effort, we should still get content deltas + List deltaEvents = events.stream() + .filter(e -> e instanceof AssistantMessageDeltaEvent).map(e -> (AssistantMessageDeltaEvent) e) + .toList(); + assertFalse(deltaEvents.isEmpty(), + "Should have received delta events with reasoning effort configured"); - // And a final assistant.message with the answer - List assistantEvents = events.stream() - .filter(e -> e instanceof AssistantMessageEvent).map(e -> (AssistantMessageEvent) e).toList(); - assertFalse(assistantEvents.isEmpty(), "Should have received assistant message events"); - assertTrue(assistantEvents.get(assistantEvents.size() - 1).getData().content().contains("255"), - "Response should contain 255"); + // And a final assistant.message with the answer + List assistantEvents = events.stream() + .filter(e -> e instanceof AssistantMessageEvent).map(e -> (AssistantMessageEvent) e).toList(); + assertFalse(assistantEvents.isEmpty(), "Should have received assistant message events"); + assertTrue(assistantEvents.get(assistantEvents.size() - 1).getData().content().contains("255"), + "Response should contain 255"); - session.close(); + session.close(); + } } } } diff --git a/java/src/test/java/com/github/copilot/SubagentHooksE2ETest.java b/java/sdk/src/test/java/com/github/copilot/SubagentHooksE2ETest.java similarity index 100% rename from java/src/test/java/com/github/copilot/SubagentHooksE2ETest.java rename to java/sdk/src/test/java/com/github/copilot/SubagentHooksE2ETest.java diff --git a/java/src/test/java/com/github/copilot/SystemMessageSectionsIT.java b/java/sdk/src/test/java/com/github/copilot/SystemMessageSectionsIT.java similarity index 100% rename from java/src/test/java/com/github/copilot/SystemMessageSectionsIT.java rename to java/sdk/src/test/java/com/github/copilot/SystemMessageSectionsIT.java diff --git a/java/src/test/java/com/github/copilot/TelemetryConfigTest.java b/java/sdk/src/test/java/com/github/copilot/TelemetryConfigTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/TelemetryConfigTest.java rename to java/sdk/src/test/java/com/github/copilot/TelemetryConfigTest.java diff --git a/java/src/test/java/com/github/copilot/TestUtil.java b/java/sdk/src/test/java/com/github/copilot/TestUtil.java similarity index 100% rename from java/src/test/java/com/github/copilot/TestUtil.java rename to java/sdk/src/test/java/com/github/copilot/TestUtil.java diff --git a/java/src/test/java/com/github/copilot/TimeoutEdgeCaseTest.java b/java/sdk/src/test/java/com/github/copilot/TimeoutEdgeCaseTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/TimeoutEdgeCaseTest.java rename to java/sdk/src/test/java/com/github/copilot/TimeoutEdgeCaseTest.java diff --git a/java/src/test/java/com/github/copilot/ToolDefinitionTest.java b/java/sdk/src/test/java/com/github/copilot/ToolDefinitionTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/ToolDefinitionTest.java rename to java/sdk/src/test/java/com/github/copilot/ToolDefinitionTest.java diff --git a/java/src/test/java/com/github/copilot/ToolInvocationTest.java b/java/sdk/src/test/java/com/github/copilot/ToolInvocationTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/ToolInvocationTest.java rename to java/sdk/src/test/java/com/github/copilot/ToolInvocationTest.java diff --git a/java/src/test/java/com/github/copilot/ToolResultObjectSerializationTest.java b/java/sdk/src/test/java/com/github/copilot/ToolResultObjectSerializationTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/ToolResultObjectSerializationTest.java rename to java/sdk/src/test/java/com/github/copilot/ToolResultObjectSerializationTest.java diff --git a/java/src/test/java/com/github/copilot/ToolResultsTest.java b/java/sdk/src/test/java/com/github/copilot/ToolResultsTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/ToolResultsTest.java rename to java/sdk/src/test/java/com/github/copilot/ToolResultsTest.java diff --git a/java/src/test/java/com/github/copilot/ToolSetTest.java b/java/sdk/src/test/java/com/github/copilot/ToolSetTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/ToolSetTest.java rename to java/sdk/src/test/java/com/github/copilot/ToolSetTest.java diff --git a/java/src/test/java/com/github/copilot/ToolsTest.java b/java/sdk/src/test/java/com/github/copilot/ToolsTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/ToolsTest.java rename to java/sdk/src/test/java/com/github/copilot/ToolsTest.java diff --git a/java/src/test/java/com/github/copilot/UpdateSessionOptionsForModeTest.java b/java/sdk/src/test/java/com/github/copilot/UpdateSessionOptionsForModeTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/UpdateSessionOptionsForModeTest.java rename to java/sdk/src/test/java/com/github/copilot/UpdateSessionOptionsForModeTest.java diff --git a/java/src/test/java/com/github/copilot/ZeroTimeoutContractTest.java b/java/sdk/src/test/java/com/github/copilot/ZeroTimeoutContractTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/ZeroTimeoutContractTest.java rename to java/sdk/src/test/java/com/github/copilot/ZeroTimeoutContractTest.java diff --git a/java/src/test/java/com/github/copilot/e2e/ErgonomicTestTools$$CopilotToolMeta.java b/java/sdk/src/test/java/com/github/copilot/e2e/ErgonomicTestTools$$CopilotToolMeta.java similarity index 100% rename from java/src/test/java/com/github/copilot/e2e/ErgonomicTestTools$$CopilotToolMeta.java rename to java/sdk/src/test/java/com/github/copilot/e2e/ErgonomicTestTools$$CopilotToolMeta.java diff --git a/java/src/test/java/com/github/copilot/e2e/ErgonomicTestTools.java b/java/sdk/src/test/java/com/github/copilot/e2e/ErgonomicTestTools.java similarity index 100% rename from java/src/test/java/com/github/copilot/e2e/ErgonomicTestTools.java rename to java/sdk/src/test/java/com/github/copilot/e2e/ErgonomicTestTools.java diff --git a/java/src/test/java/com/github/copilot/e2e/ErgonomicToolDefinitionIT.java b/java/sdk/src/test/java/com/github/copilot/e2e/ErgonomicToolDefinitionIT.java similarity index 100% rename from java/src/test/java/com/github/copilot/e2e/ErgonomicToolDefinitionIT.java rename to java/sdk/src/test/java/com/github/copilot/e2e/ErgonomicToolDefinitionIT.java diff --git a/java/sdk/src/test/java/com/github/copilot/e2e/InProcessTransportIT.java b/java/sdk/src/test/java/com/github/copilot/e2e/InProcessTransportIT.java new file mode 100644 index 000000000..1b8595401 --- /dev/null +++ b/java/sdk/src/test/java/com/github/copilot/e2e/InProcessTransportIT.java @@ -0,0 +1,101 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.e2e; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.Map; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import com.github.copilot.AllowCopilotExperimental; +import com.github.copilot.CopilotClient; +import com.github.copilot.E2ETestContext; +import com.github.copilot.ffi.InProcessEnvGuard; +import com.github.copilot.rpc.CopilotClientOptions; +import com.github.copilot.rpc.PingResponse; +import com.github.copilot.rpc.RuntimeConnection; + +/** + * Failsafe integration test for the in-process (FFI) transport. + * + *

+ * Loads the real {@code runtime.node} native library into this test process via + * {@link com.github.copilot.ffi.FfiRuntimeHost}, performs a purely local + * {@code ping} round-trip through the runtime, and stops cleanly. {@code ping} + * is answered by the runtime itself, so no auth or replay proxy is involved — + * this mirrors {@code nodejs/test/e2e/inprocess_ffi.e2e.test.ts}, + * {@code go/internal/e2e/inprocess_ffi_e2e_test.go}, and + * {@code python/e2e/test_inprocess_ffi_e2e.py}. + * + *

+ * {@link InProcessEnvGuard} demonstrates how the harness redirects the native + * runtime's HTTP traffic to the replay proxy (via {@code COPILOT_API_URL}) for + * tests that need session/message round trips over the in-process transport: + * the native library reads environment variables from the live OS process + * environment block, not from the JVM's {@code System.getenv()} snapshot, so + * only a JNA-backed native call can make it visible to code already loaded + * in-process. + * + *

+ * Run with {@code mvn verify -Pinprocess} from the {@code java} reactor root, + * which builds the {@code copilot-sdk-java-runtime} artifact and sets + * {@code COPILOT_CLI_PATH} to the pinned CLI whose sibling {@code runtime.node} + * this test loads, and forces {@code forkCount=1} because the FFI host and env + * guard mutate process-global state. + * + *

+ * {@link RequireInProcess} disables this test unless the {@code -Pinprocess} + * profile is active: without it, the {@code copilot-sdk-java-runtime} + * classifier JAR providing {@code runtime.node} is not on the classpath, so the + * test would fail with a {@code FileNotFoundException} rather than being + * skipped. + */ +@AllowCopilotExperimental +@RequireInProcess +class InProcessTransportIT { + + private static E2ETestContext ctx; + + @BeforeAll + static void setup() throws Exception { + ctx = E2ETestContext.create(); + } + + @AfterAll + static void teardown() throws Exception { + if (ctx != null) { + ctx.close(); + } + } + + @Test + void shouldStartPingAndStopOverInProcessFfi() throws Exception { + // Route the native runtime's HTTP traffic (should it make any) at the + // replay proxy, mirroring how a session-level in-process test would + // redirect COPILOT_API_URL. `ping` never reaches the network, but this + // demonstrates the guard's intended usage for future in-process tests. + // COPILOT_CLI_PATH is intentionally NOT set here: NativeRuntimeLoader and + // CopilotClient.resolveInProcessEntrypoint() read it via + // System.getenv(), which is a JVM-startup-time snapshot that native + // setenv() calls made after the JVM starts cannot update — it must be + // set before the JVM starts (see the -Pinprocess Maven profile). + try (InProcessEnvGuard envGuard = new InProcessEnvGuard(Map.of("COPILOT_API_URL", ctx.getProxyUrl()))) { + CopilotClientOptions options = new CopilotClientOptions().setConnection(RuntimeConnection.forInProcess()); + try (CopilotClient client = new CopilotClient(options)) { + client.start().get(); + + PingResponse pong = client.ping("ffi message").get(); + assertEquals("pong: ffi message", pong.message()); + assertNotNull(pong.timestamp()); + + client.stop().get(); + } + } + } +} diff --git a/java/sdk/src/test/java/com/github/copilot/e2e/RequireInProcess.java b/java/sdk/src/test/java/com/github/copilot/e2e/RequireInProcess.java new file mode 100644 index 000000000..12de4e5b7 --- /dev/null +++ b/java/sdk/src/test/java/com/github/copilot/e2e/RequireInProcess.java @@ -0,0 +1,64 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.e2e; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Enables an annotated test class or method only when the E2E suite runs under + * the in-process (FFI) transport, i.e. when + * {@code COPILOT_SDK_DEFAULT_CONNECTION} is set to {@code inprocess}. + * + *

+ * Use this for tests that require the real {@code runtime.node} native library + * to be present on the classpath, which only the {@code -Pinprocess} Maven + * profile guarantees (see {@link InProcessTransportIT}). Without this profile, + * standard {@code mvn verify} runs would fail with a + * {@code FileNotFoundException} because the classifier JAR providing + * {@code runtime.node} is not on the classpath. + *

+ * + *

+ * The inverse of {@link SkipInProcess}. + *

+ */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +@ExtendWith(RequireInProcess.Condition.class) +public @interface RequireInProcess { + + /** + * Explains why the annotated test requires the in-process transport. + * + * @return the skip reason used when the in-process transport is not active + */ + String value() default "Requires the -Pinprocess Maven profile"; + + /** + * JUnit 5 execution condition backing {@link RequireInProcess}. + */ + public static final class Condition implements org.junit.jupiter.api.extension.ExecutionCondition { + + private static final String DEFAULT_CONNECTION_ENV_VAR = "COPILOT_SDK_DEFAULT_CONNECTION"; + + @Override + public org.junit.jupiter.api.extension.ConditionEvaluationResult evaluateExecutionCondition( + org.junit.jupiter.api.extension.ExtensionContext context) { + String envValue = System.getenv(DEFAULT_CONNECTION_ENV_VAR); + if ("inprocess".equalsIgnoreCase(envValue)) { + return org.junit.jupiter.api.extension.ConditionEvaluationResult + .enabled("Running under the in-process transport"); + } + String reason = context.getElement().map(element -> element.getAnnotation(RequireInProcess.class)) + .map(RequireInProcess::value).orElse("Requires the -Pinprocess Maven profile"); + return org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled(reason); + } + } +} diff --git a/java/sdk/src/test/java/com/github/copilot/e2e/RewindIT.java b/java/sdk/src/test/java/com/github/copilot/e2e/RewindIT.java new file mode 100644 index 000000000..eff48dde0 --- /dev/null +++ b/java/sdk/src/test/java/com/github/copilot/e2e/RewindIT.java @@ -0,0 +1,128 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.e2e; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import com.github.copilot.AllowCopilotExperimental; +import com.github.copilot.CopilotClient; +import com.github.copilot.CopilotSession; +import com.github.copilot.E2ETestContext; +import com.github.copilot.generated.AssistantMessageEvent; +import com.github.copilot.generated.rpc.HistoryRewindMode; +import com.github.copilot.generated.rpc.HistoryRewindOutcome; +import com.github.copilot.generated.rpc.SessionHistoryListRewindPointsResult; +import com.github.copilot.generated.rpc.SessionHistoryPreviewRewindParams; +import com.github.copilot.generated.rpc.SessionHistoryRewindParams; +import com.github.copilot.rpc.MessageOptions; +import com.github.copilot.rpc.PermissionHandler; +import com.github.copilot.rpc.SessionConfig; + +@AllowCopilotExperimental +class RewindIT { + + private static final String FILE_NAME = "rewind-sdk.txt"; + private static final String FILE_CONTENT = "SDK rewind content"; + + private static E2ETestContext ctx; + + @BeforeAll + static void setup() throws Exception { + ctx = E2ETestContext.create(); + } + + @AfterAll + static void teardown() throws Exception { + if (ctx != null) { + ctx.close(); + } + } + + @Test + void shouldRestoreTrackedFileAndConversation() throws Exception { + ctx.configureForTest("rewind", "should_restore_tracked_file_and_conversation"); + Path filePath = ctx.getWorkDir().resolve(FILE_NAME); + + try (CopilotClient client = ctx.createClient(); + CopilotSession session = client + .createSession( + new SessionConfig().setModel("claude-sonnet-4.5").setEnableFileChangeTracking(true) + .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)) + .get(30, TimeUnit.SECONDS)) { + AssistantMessageEvent response = session + .sendAndWait(new MessageOptions().setPrompt( + "Use the create tool to create " + FILE_NAME + " containing exactly " + FILE_CONTENT + + ". After the tool succeeds, reply with exactly SDK_REWIND_DONE."), + 30_000) + .get(60, TimeUnit.SECONDS); + + assertNotNull(response); + assertEquals("SDK_REWIND_DONE", response.getData().content()); + assertEquals(FILE_CONTENT, Files.readString(filePath)); + + SessionHistoryListRewindPointsResult rewindPoints = waitForRewindPoints(session); + assertTrue(Boolean.TRUE.equals(rewindPoints.fileChangeTrackingEnabled())); + assertEquals(1, rewindPoints.points().size()); + var rewindPoint = rewindPoints.points().get(0); + assertTrue(Boolean.TRUE.equals(rewindPoint.canRestoreFiles())); + assertEquals(1L, rewindPoint.fileCount()); + + var preview = session.getRpc().history + .previewRewind(new SessionHistoryPreviewRewindParams(null, rewindPoint.eventId())) + .get(10, TimeUnit.SECONDS); + assertTrue(Boolean.TRUE.equals(preview.available())); + assertEquals(1, preview.files().size()); + assertSamePath(filePath, preview.files().get(0).path()); + + var rewind = session.getRpc().history.rewind(new SessionHistoryRewindParams(null, rewindPoint.eventId(), + HistoryRewindMode.CONVERSATION_AND_FILES)).get(10, TimeUnit.SECONDS); + assertEquals(HistoryRewindOutcome.SUCCESS, rewind.outcome()); + assertTrue(rewind.eventsRemoved() != null && rewind.eventsRemoved() > 0); + assertEquals(1, rewind.restoredFiles().size()); + assertSamePath(filePath, rewind.restoredFiles().get(0)); + assertFalse(Files.exists(filePath)); + + var events = session.getMessages().get(10, TimeUnit.SECONDS); + assertTrue(events.stream().noneMatch(event -> event.getId().toString().equals(rewindPoint.eventId()))); + } + } + + private static SessionHistoryListRewindPointsResult waitForRewindPoints(CopilotSession session) throws Exception { + long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(10); + SessionHistoryListRewindPointsResult result; + do { + result = session.getRpc().history.listRewindPoints().get(10, TimeUnit.SECONDS); + if (result.unavailableReason() == null) { + return result; + } + TimeUnit.MILLISECONDS.sleep(100); + } while (System.nanoTime() < deadline); + + assertNull(result.unavailableReason(), "Timed out waiting for rewind points to become available"); + return result; + } + + private static void assertSamePath(Path expected, String actual) { + String expectedPath = expected.toAbsolutePath().normalize().toString(); + String actualPath = Path.of(actual).toAbsolutePath().normalize().toString(); + if (System.getProperty("os.name").startsWith("Windows")) { + assertTrue(expectedPath.equalsIgnoreCase(actualPath)); + } else { + assertEquals(expectedPath, actualPath); + } + } +} diff --git a/java/sdk/src/test/java/com/github/copilot/e2e/SkipInProcess.java b/java/sdk/src/test/java/com/github/copilot/e2e/SkipInProcess.java new file mode 100644 index 000000000..3f626e133 --- /dev/null +++ b/java/sdk/src/test/java/com/github/copilot/e2e/SkipInProcess.java @@ -0,0 +1,64 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.e2e; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Disables an annotated test class or method when the E2E suite runs under the + * in-process (FFI) transport, i.e. when {@code COPILOT_SDK_DEFAULT_CONNECTION} + * is set to {@code inprocess}. + * + *

+ * Use this for tests that rely on per-client process settings the in-process + * transport cannot honor — for example per-client environment variables, since + * the in-process runtime shares the host process's single environment (see + * {@link com.github.copilot.rpc.InProcessRuntimeConnection} and + * issue #1934). + *

+ * + *

+ * Mirrors {@code skip_inprocess(reason)} in the Rust E2E harness. + *

+ */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +@ExtendWith(SkipInProcess.Condition.class) +public @interface SkipInProcess { + + /** + * Explains why the annotated test is incompatible with the in-process + * transport. + * + * @return the skip reason + */ + String value() default "Not supported under the in-process (FFI) transport"; + + /** + * JUnit 5 execution condition backing {@link SkipInProcess}. + */ + public static final class Condition implements org.junit.jupiter.api.extension.ExecutionCondition { + + private static final String DEFAULT_CONNECTION_ENV_VAR = "COPILOT_SDK_DEFAULT_CONNECTION"; + + @Override + public org.junit.jupiter.api.extension.ConditionEvaluationResult evaluateExecutionCondition( + org.junit.jupiter.api.extension.ExtensionContext context) { + String envValue = System.getenv(DEFAULT_CONNECTION_ENV_VAR); + if (!"inprocess".equalsIgnoreCase(envValue)) { + return org.junit.jupiter.api.extension.ConditionEvaluationResult + .enabled("Not running under the in-process transport"); + } + String reason = context.getElement().map(element -> element.getAnnotation(SkipInProcess.class)) + .map(SkipInProcess::value).orElse("Not supported under the in-process (FFI) transport"); + return org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled(reason); + } + } +} diff --git a/java/sdk/src/test/java/com/github/copilot/ffi/FfiRuntimeHostTest.java b/java/sdk/src/test/java/com/github/copilot/ffi/FfiRuntimeHostTest.java new file mode 100644 index 000000000..cc98d24f6 --- /dev/null +++ b/java/sdk/src/test/java/com/github/copilot/ffi/FfiRuntimeHostTest.java @@ -0,0 +1,362 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.ffi; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.copilot.rpc.CopilotClientMode; +import com.github.copilot.rpc.CopilotClientOptions; +import com.sun.jna.Memory; +import com.sun.jna.Pointer; + +class FfiRuntimeHostTest { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + @Test + void startBuildsExpectedArgvAndEnvJson() throws Exception { + class RecordingBinding implements NativeBinding { + byte[] argv; + byte[] env; + + @Override + public int hostStart(byte[] argvJson, int argvJsonLen, byte[] envJson, int envJsonLen) { + this.argv = argvJson; + this.env = envJson; + return 11; + } + + @Override + public boolean hostShutdown(int serverId) { + return true; + } + + @Override + public int connectionOpen(int serverId, OutboundCallback callback, Pointer userData, byte[] extSource, + int extSourceLen, byte[] extName, int extNameLen, byte[] connToken, int connTokenLen) { + return 21; + } + + @Override + public boolean connectionWrite(int connectionId, byte[] data, int dataLen) { + return true; + } + + @Override + public boolean connectionClose(int connectionId) { + return true; + } + } + + RecordingBinding binding = new RecordingBinding(); + CopilotClientOptions options = new CopilotClientOptions().setLogLevel("debug").setGitHubToken("gh-token") + .setCopilotHome("/tmp/copilot-home").setUseLoggedInUser(false).setSessionIdleTimeoutSeconds(42) + .setRemote(true).setMode(CopilotClientMode.EMPTY).setCliArgs(new String[]{"--extra-flag"}); + + FfiRuntimeHost host = new FfiRuntimeHost(binding, "/tmp/runtime.node"); + host.start("/tmp/entrypoint.js", options); + + List argv = MAPPER.readValue(binding.argv, new TypeReference>() { + }); + assertEquals("node", argv.get(0)); + assertEquals("/tmp/entrypoint.js", argv.get(1)); + assertTrue(argv.contains("--embedded-host")); + assertTrue(argv.contains("--no-auto-update")); + assertTrue(argv.contains("--auth-token-env")); + assertTrue(argv.contains("COPILOT_SDK_AUTH_TOKEN")); + assertTrue(argv.contains("--no-auto-login")); + assertTrue(argv.contains("--session-idle-timeout")); + assertTrue(argv.contains("42")); + assertTrue(argv.contains("--remote")); + assertTrue(argv.contains("--extra-flag")); + + Map env = MAPPER.readValue(binding.env, new TypeReference>() { + }); + assertEquals("gh-token", env.get("COPILOT_SDK_AUTH_TOKEN")); + assertEquals("/tmp/copilot-home", env.get("COPILOT_HOME")); + assertEquals("1", env.get("COPILOT_DISABLE_KEYTAR")); + } + + @Test + void callbackExceptionIsContainedAndDoesNotEscapeAcrossFfiBoundary() { + AtomicBoolean callbackReturned = new AtomicBoolean(false); + NativeBinding binding = new NativeBinding() { + @Override + public int hostStart(byte[] argvJson, int argvJsonLen, byte[] envJson, int envJsonLen) { + return 1; + } + + @Override + public boolean hostShutdown(int serverId) { + return true; + } + + @Override + public int connectionOpen(int serverId, OutboundCallback callback, Pointer userData, byte[] extSource, + int extSourceLen, byte[] extName, int extNameLen, byte[] connToken, int connTokenLen) { + Memory mem = new Memory(5); + mem.write(0, "hello".getBytes(StandardCharsets.UTF_8), 0, 5); + callback.invoke(Pointer.NULL, mem, new SizeT(5)); + callbackReturned.set(true); + return 2; + } + + @Override + public boolean connectionWrite(int connectionId, byte[] data, int dataLen) { + return true; + } + + @Override + public boolean connectionClose(int connectionId) { + return true; + } + }; + + QueueInputStream throwingStream = new QueueInputStream() { + @Override + void enqueue(byte[] bytes) { + throw new RuntimeException("boom"); + } + }; + + FfiRuntimeHost host = new FfiRuntimeHost(binding, "test-lib", throwingStream); + assertDoesNotThrow(() -> host.start("/tmp/entrypoint", new CopilotClientOptions())); + assertTrue(callbackReturned.get(), "callback should return normally even when enqueue throws"); + } + + @Test + void closeNeverThrowsEvenWhenNativeCloseFails() { + NativeBinding binding = new NativeBinding() { + @Override + public int hostStart(byte[] argvJson, int argvJsonLen, byte[] envJson, int envJsonLen) { + return 5; + } + + @Override + public boolean hostShutdown(int serverId) { + throw new RuntimeException("shutdown failed"); + } + + @Override + public int connectionOpen(int serverId, OutboundCallback callback, Pointer userData, byte[] extSource, + int extSourceLen, byte[] extName, int extNameLen, byte[] connToken, int connTokenLen) { + return 9; + } + + @Override + public boolean connectionWrite(int connectionId, byte[] data, int dataLen) { + return true; + } + + @Override + public boolean connectionClose(int connectionId) { + throw new RuntimeException("close failed"); + } + }; + + FfiRuntimeHost host = new FfiRuntimeHost(binding, "test-lib"); + host.start("/tmp/entrypoint", new CopilotClientOptions()); + assertDoesNotThrow(host::close); + } + + @Test + void failedConnectionOpenReleasesHostForSequentialStartup() { + AtomicInteger starts = new AtomicInteger(); + AtomicInteger shutdowns = new AtomicInteger(); + NativeBinding binding = new NativeBinding() { + @Override + public int hostStart(byte[] argvJson, int argvJsonLen, byte[] envJson, int envJsonLen) { + return starts.incrementAndGet(); + } + + @Override + public boolean hostShutdown(int serverId) { + shutdowns.incrementAndGet(); + return true; + } + + @Override + public int connectionOpen(int serverId, OutboundCallback callback, Pointer userData, byte[] extSource, + int extSourceLen, byte[] extName, int extNameLen, byte[] connToken, int connTokenLen) { + return serverId == 1 ? 0 : 22; + } + + @Override + public boolean connectionWrite(int connectionId, byte[] data, int dataLen) { + return true; + } + + @Override + public boolean connectionClose(int connectionId) { + return true; + } + }; + + try (FfiRuntimeHost failedHost = new FfiRuntimeHost(binding, "test-lib")) { + assertThrows(IllegalStateException.class, + () -> failedHost.start("/tmp/entrypoint", new CopilotClientOptions())); + } + assertEquals(1, shutdowns.get(), "failed connection startup must release its native host"); + + try (FfiRuntimeHost nextHost = new FfiRuntimeHost(binding, "test-lib")) { + assertDoesNotThrow(() -> nextHost.start("/tmp/entrypoint", new CopilotClientOptions())); + } + assertEquals(2, shutdowns.get(), "the sequential host must also shut down cleanly"); + } + + @Test + void writeAndCloseAreSerializedByOperationLock() throws Exception { + CountDownLatch writeStarted = new CountDownLatch(1); + CountDownLatch allowWriteToFinish = new CountDownLatch(1); + AtomicInteger writes = new AtomicInteger(0); + + NativeBinding binding = new NativeBinding() { + @Override + public int hostStart(byte[] argvJson, int argvJsonLen, byte[] envJson, int envJsonLen) { + return 3; + } + + @Override + public boolean hostShutdown(int serverId) { + return true; + } + + @Override + public int connectionOpen(int serverId, OutboundCallback callback, Pointer userData, byte[] extSource, + int extSourceLen, byte[] extName, int extNameLen, byte[] connToken, int connTokenLen) { + return 4; + } + + @Override + public boolean connectionWrite(int connectionId, byte[] data, int dataLen) { + writes.incrementAndGet(); + writeStarted.countDown(); + try { + allowWriteToFinish.await(5, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + return true; + } + + @Override + public boolean connectionClose(int connectionId) { + return true; + } + }; + + FfiRuntimeHost host = new FfiRuntimeHost(binding, "test-lib"); + host.start("/tmp/entrypoint", new CopilotClientOptions()); + + CompletableFuture writer = CompletableFuture.runAsync(() -> { + try { + host.getSendStream().write("ping".getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + + assertTrue(writeStarted.await(2, TimeUnit.SECONDS)); + CompletableFuture closer = CompletableFuture.runAsync(host::close); + allowWriteToFinish.countDown(); + + writer.get(5, TimeUnit.SECONDS); + closer.get(5, TimeUnit.SECONDS); + assertEquals(1, writes.get()); + assertThrows(IOException.class, () -> host.getSendStream().write("late".getBytes(StandardCharsets.UTF_8))); + } + + @Test + void closeDrainsActiveCallbacksBeforeHostShutdown() throws Exception { + CountDownLatch callbackEntered = new CountDownLatch(1); + CountDownLatch allowCallbackToReturn = new CountDownLatch(1); + AtomicBoolean shutdownObservedAfterCallbackReturn = new AtomicBoolean(false); + AtomicBoolean callbackFinished = new AtomicBoolean(false); + AtomicReference callbackRef = new AtomicReference<>(); + + NativeBinding binding = new NativeBinding() { + @Override + public int hostStart(byte[] argvJson, int argvJsonLen, byte[] envJson, int envJsonLen) { + return 7; + } + + @Override + public boolean hostShutdown(int serverId) { + shutdownObservedAfterCallbackReturn.set(callbackFinished.get()); + return true; + } + + @Override + public int connectionOpen(int serverId, OutboundCallback callback, Pointer userData, byte[] extSource, + int extSourceLen, byte[] extName, int extNameLen, byte[] connToken, int connTokenLen) { + callbackRef.set(callback); + return 8; + } + + @Override + public boolean connectionWrite(int connectionId, byte[] data, int dataLen) { + return true; + } + + @Override + public boolean connectionClose(int connectionId) { + return true; + } + }; + + QueueInputStream blockingStream = new QueueInputStream() { + @Override + void enqueue(byte[] bytes) { + callbackEntered.countDown(); + try { + allowCallbackToReturn.await(5, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + callbackFinished.set(true); + super.enqueue(bytes); + } + }; + + FfiRuntimeHost host = new FfiRuntimeHost(binding, "test-lib", blockingStream); + host.start("/tmp/entrypoint", new CopilotClientOptions()); + assertNotNull(callbackRef.get()); + + CompletableFuture callbackFuture = CompletableFuture.runAsync(() -> { + Memory mem = new Memory(1); + mem.setByte(0, (byte) 'x'); + callbackRef.get().invoke(Pointer.NULL, mem, new SizeT(1)); + }); + + assertTrue(callbackEntered.await(2, TimeUnit.SECONDS)); + CompletableFuture closeFuture = CompletableFuture.runAsync(host::close); + Thread.sleep(150); + assertFalse(closeFuture.isDone(), "close should wait for active callback to drain"); + allowCallbackToReturn.countDown(); + callbackFuture.get(5, TimeUnit.SECONDS); + closeFuture.get(5, TimeUnit.SECONDS); + assertTrue(shutdownObservedAfterCallbackReturn.get(), "host_shutdown should run after callback drains"); + } +} diff --git a/java/sdk/src/test/java/com/github/copilot/ffi/InProcessEnvGuard.java b/java/sdk/src/test/java/com/github/copilot/ffi/InProcessEnvGuard.java new file mode 100644 index 000000000..43df71371 --- /dev/null +++ b/java/sdk/src/test/java/com/github/copilot/ffi/InProcessEnvGuard.java @@ -0,0 +1,192 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.ffi; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.logging.Logger; + +import com.sun.jna.Library; +import com.sun.jna.Native; +import com.sun.jna.WString; + +/** + * Mutates the live process environment block so that native code loaded + * in-process (e.g. {@code runtime.node} via JNA) observes the given environment + * variables, and restores the previous values on {@link #close()}. + * + *

+ * Java has no public API to modify the process-level environment block: + * {@code System.setProperty()} only writes the JVM property bag, and + * {@code System.getenv()} is an immutable startup-time snapshot. Native code + * loaded via JNA reads the OS environment directly + * ({@code GetEnvironmentVariableW} on Windows, {@code getenv()} on POSIX), so + * the only way to make it see an overridden value is to call the OS API + * directly through JNA. + *

+ * + *

+ * This mirrors the Rust {@code InProcessEnvGuard} + * ({@code rust/tests/e2e/support.rs}) and the .NET + * {@code InProcessEnvIsolation} + * ({@code dotnet/test/Harness/InProcessEnvIsolation.cs}). + *

+ * + *

+ * Thread safety: this guard mutates process-global state. + * Tests that use it must run with test concurrency 1 (see the + * {@code -Pinprocess} Maven profile, which sets {@code failsafe.forkCount=1} + * and disables parallel execution). + *

+ */ +public final class InProcessEnvGuard implements AutoCloseable { + + private static final Logger LOG = Logger.getLogger(InProcessEnvGuard.class.getName()); + + /** + * Environment variables suppressed because replay snapshots expect Bearer/OAuth + * auth. + */ + private static final List SUPPRESSED_KEYS = List.of("COPILOT_HMAC_KEY", "CAPI_HMAC_KEY"); + + /** + * Windows kernel32: sets or deletes a variable in the process environment + * block. + */ + private interface Kernel32Env extends Library { + boolean SetEnvironmentVariableW(WString lpName, WString lpValue); + + int GetEnvironmentVariableW(WString lpName, char[] lpBuffer, int nSize); + } + + /** POSIX libc: sets or deletes a variable in the process environment block. */ + private interface LibcEnv extends Library { + int setenv(String name, String value, int overwrite); + + int unsetenv(String name); + + /** Returns null if the variable is not set. */ + String getenv(String name); + } + + /** + * Sentinel indicating the variable was not set (distinct from empty string). + */ + private static final String ABSENT_SENTINEL = new String("\0ABSENT\0"); + + /** + * name -> previous value ({@code null} means the variable was not set before). + */ + private final List> saved = new ArrayList<>(); + private boolean closed; + + /** + * Applies {@code applyEnv} to the native process environment block, saving the + * previous values for restoration by {@link #close()}. Also suppresses + * {@code COPILOT_HMAC_KEY} / {@code CAPI_HMAC_KEY} if present, since the replay + * proxy expects Bearer/OAuth auth rather than HMAC. + * + * @param applyEnv + * environment variables to apply; values must not be {@code null} + */ + public InProcessEnvGuard(Map applyEnv) { + for (Map.Entry entry : applyEnv.entrySet()) { + apply(entry.getKey(), entry.getValue()); + } + for (String key : SUPPRESSED_KEYS) { + String previous = nativeGetEnv(key); + if (previous != null && !previous.isEmpty()) { + apply(key, null); + } + } + } + + private void apply(String name, String value) { + String previous = nativeGetEnv(name); + saved.add(Map.entry(name, previous == null ? ABSENT_SENTINEL : previous)); + nativeSetEnv(name, value); + } + + /** + * Restores every environment variable this guard touched to the value it had + * before construction. + */ + @Override + public synchronized void close() { + if (closed) { + return; + } + closed = true; + List> reversed = new ArrayList<>(saved); + Collections.reverse(reversed); + for (Map.Entry entry : reversed) { + // ABSENT_SENTINEL uses a value ("\0ABSENT\0") impossible in real env vars. + String restoreValue = ABSENT_SENTINEL.equals(entry.getValue()) ? null : entry.getValue(); + nativeSetEnv(entry.getKey(), restoreValue); + } + } + + private static String nativeGetEnv(String name) { + if (isWindows()) { + return nativeGetEnvWindows(name); + } else { + return nativeGetEnvUnix(name); + } + } + + private static String nativeGetEnvWindows(String name) { + Kernel32Env kernel32 = Native.load("kernel32", Kernel32Env.class); + char[] buffer = new char[32767]; + int len = kernel32.GetEnvironmentVariableW(new WString(name), buffer, buffer.length); + if (len == 0) { + // Variable not set (or error — treat as absent) + return null; + } + return new String(buffer, 0, len); + } + + private static String nativeGetEnvUnix(String name) { + LibcEnv libc = Native.load("c", LibcEnv.class); + return libc.getenv(name); + } + + private static void nativeSetEnv(String name, String value) { + if (isWindows()) { + nativeSetEnvWindows(name, value); + } else { + nativeSetEnvUnix(name, value); + } + } + + private static void nativeSetEnvWindows(String name, String value) { + Kernel32Env kernel32 = Native.load("kernel32", Kernel32Env.class); + boolean ok = kernel32.SetEnvironmentVariableW(new WString(name), value != null ? new WString(value) : null); + if (!ok) { + LOG.warning("SetEnvironmentVariableW failed for key=" + name); + } + } + + private static void nativeSetEnvUnix(String name, String value) { + LibcEnv libc = Native.load("c", LibcEnv.class); + if (value != null) { + int rc = libc.setenv(name, value, 1); + if (rc != 0) { + LOG.warning("setenv() failed for key=" + name + " rc=" + rc); + } + } else { + int rc = libc.unsetenv(name); + if (rc != 0) { + LOG.warning("unsetenv() failed for key=" + name + " rc=" + rc); + } + } + } + + private static boolean isWindows() { + return System.getProperty("os.name", "").toLowerCase(Locale.ROOT).contains("win"); + } +} diff --git a/java/sdk/src/test/java/com/github/copilot/ffi/JnaNativeBindingTest.java b/java/sdk/src/test/java/com/github/copilot/ffi/JnaNativeBindingTest.java new file mode 100644 index 000000000..d7d218c0e --- /dev/null +++ b/java/sdk/src/test/java/com/github/copilot/ffi/JnaNativeBindingTest.java @@ -0,0 +1,344 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.ffi; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +import com.sun.jna.Pointer; + +import java.lang.ref.WeakReference; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Unit tests for {@link JnaNativeBinding}. + * + *

+ * Delegation and callback-tracking tests use a stub + * {@link JnaNativeBinding.CopilotRuntimeLibrary}. Library-loading guard tests + * exercise {@link JnaNativeBinding} directly against the real + * {@code runtime.node} when it is available on the test classpath. + * + *

+ * Tests that require the packaged native runtime are conditionally skipped when + * it is unavailable (for example, when not running with {@code -Pinprocess}). + */ +class JnaNativeBindingTest { + + // ------------------------------------------------------------------------- + // Stub CopilotRuntimeLibrary for delegation tests + // ------------------------------------------------------------------------- + + /** + * Minimal stub for testing {@link JnaNativeBinding} delegation without disk + * I/O. + */ + private static class StubRuntimeLibrary implements JnaNativeBinding.CopilotRuntimeLibrary { + int hostStartReturn = 1; + byte hostShutdownReturn = 1; + int connectionOpenReturn = 1; + byte connectionWriteReturn = 1; + byte connectionCloseReturn = 1; + + byte[] lastArgvJson; + int lastArgvJsonLen; + int lastServerId; + int lastConnectionId; + OutboundCallback lastCallback; + + @Override + public int copilot_runtime_host_start(byte[] argvJson, SizeT argvJsonLen, byte[] envJson, SizeT envJsonLen) { + lastArgvJson = argvJson; + lastArgvJsonLen = argvJsonLen.intValue(); + return hostStartReturn; + } + + @Override + public byte copilot_runtime_host_shutdown(int serverId) { + lastServerId = serverId; + return hostShutdownReturn; + } + + @Override + public int copilot_runtime_connection_open(int serverId, OutboundCallback callback, Pointer userData, + byte[] extSource, SizeT extSourceLen, byte[] extName, SizeT extNameLen, byte[] connToken, + SizeT connTokenLen) { + lastServerId = serverId; + lastCallback = callback; + return connectionOpenReturn; + } + + @Override + public byte copilot_runtime_connection_write(int connectionId, byte[] data, SizeT dataLen) { + lastConnectionId = connectionId; + return connectionWriteReturn; + } + + @Override + public byte copilot_runtime_connection_close(int connectionId) { + lastConnectionId = connectionId; + return connectionCloseReturn; + } + } + + // ------------------------------------------------------------------------- + // Helpers + // ------------------------------------------------------------------------- + + private static Path resolveNativeLib() { + try { + return NativeRuntimeLoader.resolve(); + } catch (Exception e) { + return null; + } + } + + @AfterEach + void resetStaticState() { + JnaNativeBinding.resetForTesting(); + } + + // ========================================================================= + // Delegation via testing constructor (stub — no disk I/O) + // ========================================================================= + + @Test + void hostStartDelegatesToLibraryAndReturnsHandle() { + StubRuntimeLibrary stub = new StubRuntimeLibrary(); + stub.hostStartReturn = 77; + JnaNativeBinding binding = new JnaNativeBinding(stub); + + byte[] argv = "[\"copilot\"]".getBytes(StandardCharsets.UTF_8); + int result = binding.hostStart(argv, argv.length, null, 0); + + assertEquals(77, result, "hostStart should return the stub's configured value"); + assertEquals(argv, stub.lastArgvJson, "argv bytes should be passed through unchanged"); + assertEquals(argv.length, stub.lastArgvJsonLen); + } + + @Test + void hostStartReturnsZeroOnFailure() { + StubRuntimeLibrary stub = new StubRuntimeLibrary(); + stub.hostStartReturn = 0; + JnaNativeBinding binding = new JnaNativeBinding(stub); + + byte[] argv = "[\"copilot\"]".getBytes(StandardCharsets.UTF_8); + assertEquals(0, binding.hostStart(argv, argv.length, null, 0), "hostStart must return 0 to signal failure"); + } + + @Test + void hostShutdownDelegatesToLibrary() { + StubRuntimeLibrary stub = new StubRuntimeLibrary(); + stub.hostShutdownReturn = 1; + JnaNativeBinding binding = new JnaNativeBinding(stub); + + assertTrue(binding.hostShutdown(42)); + assertEquals(42, stub.lastServerId); + } + + @Test + void hostShutdownReturnsFalseOnFailure() { + StubRuntimeLibrary stub = new StubRuntimeLibrary(); + stub.hostShutdownReturn = 0; + JnaNativeBinding binding = new JnaNativeBinding(stub); + assertFalse(binding.hostShutdown(1)); + } + + @Test + void connectionOpenDelegatesToLibrary() { + StubRuntimeLibrary stub = new StubRuntimeLibrary(); + stub.connectionOpenReturn = 55; + JnaNativeBinding binding = new JnaNativeBinding(stub); + + OutboundCallback noop = (ud, data, len) -> { + }; + int connId = binding.connectionOpen(42, noop, Pointer.NULL, null, 0, null, 0, null, 0); + + assertEquals(55, connId, "connectionOpen should return the stub's configured handle"); + assertEquals(42, stub.lastServerId); + } + + @Test + void connectionOpenReturnsZeroOnFailure() { + StubRuntimeLibrary stub = new StubRuntimeLibrary(); + stub.connectionOpenReturn = 0; + JnaNativeBinding binding = new JnaNativeBinding(stub); + + OutboundCallback noop = (ud, data, len) -> { + }; + assertEquals(0, binding.connectionOpen(1, noop, Pointer.NULL, null, 0, null, 0, null, 0), + "connectionOpen must return 0 to signal failure"); + } + + @Test + void connectionWriteDelegatesToLibrary() { + StubRuntimeLibrary stub = new StubRuntimeLibrary(); + stub.connectionWriteReturn = 1; + JnaNativeBinding binding = new JnaNativeBinding(stub); + + byte[] data = "hello".getBytes(StandardCharsets.UTF_8); + assertTrue(binding.connectionWrite(7, data, data.length)); + assertEquals(7, stub.lastConnectionId); + } + + @Test + void connectionWriteReturnsFalseOnFailure() { + StubRuntimeLibrary stub = new StubRuntimeLibrary(); + stub.connectionWriteReturn = 0; + JnaNativeBinding binding = new JnaNativeBinding(stub); + + byte[] data = "x".getBytes(StandardCharsets.UTF_8); + assertFalse(binding.connectionWrite(1, data, data.length), + "connectionWrite must propagate false return from the library"); + } + + @Test + void connectionCloseDelegatesToLibrary() { + StubRuntimeLibrary stub = new StubRuntimeLibrary(); + stub.connectionCloseReturn = 1; + JnaNativeBinding binding = new JnaNativeBinding(stub); + assertTrue(binding.connectionClose(7)); + assertEquals(7, stub.lastConnectionId); + } + + @Test + void connectionCloseReturnsFalseOnFailure() { + StubRuntimeLibrary stub = new StubRuntimeLibrary(); + stub.connectionCloseReturn = 0; + JnaNativeBinding binding = new JnaNativeBinding(stub); + assertFalse(binding.connectionClose(1)); + } + + @Test + void activeCallbacksStartsAtZero() { + StubRuntimeLibrary stub = new StubRuntimeLibrary(); + JnaNativeBinding binding = new JnaNativeBinding(stub); + assertEquals(0, binding.activeCallbacks.get(), "Active callback counter must start at zero"); + } + + @Test + void callbackWrapperRemainsReachableAfterConnectionClose() throws InterruptedException { + StubRuntimeLibrary stub = new StubRuntimeLibrary(); + stub.connectionOpenReturn = 99; + JnaNativeBinding binding = new JnaNativeBinding(stub); + AtomicInteger invocations = new AtomicInteger(); + + WeakReference callbackReference = openAndCloseConnection(binding, stub, + (userData, data, len) -> invocations.incrementAndGet()); + + awaitGarbageCollection(callbackReference); + + OutboundCallback callback = callbackReference.get(); + assertNotNull(callback, "Callback wrapper must remain strongly reachable after connection close"); + callback.invoke(Pointer.NULL, Pointer.NULL, new SizeT(0)); + assertEquals(1, invocations.get(), "A callback queued before close must remain safely invocable"); + } + + private static WeakReference openAndCloseConnection(JnaNativeBinding binding, + StubRuntimeLibrary stub, OutboundCallback callback) { + int connectionId = binding.connectionOpen(1, callback, Pointer.NULL, null, 0, null, 0, null, 0); + assertEquals(99, connectionId); + assertNotNull(stub.lastCallback); + + WeakReference callbackReference = new WeakReference<>(stub.lastCallback); + stub.lastCallback = null; + assertTrue(binding.connectionClose(connectionId)); + return callbackReference; + } + + private static void awaitGarbageCollection(WeakReference reference) throws InterruptedException { + for (int attempt = 0; attempt < 20 && reference.get() != null; attempt++) { + System.gc(); + System.runFinalization(); + Thread.sleep(10); + } + } + + // ========================================================================= + // Duplicate-load guard + // ========================================================================= + + @Test + void loadFromDifferentPathThrowsIllegalState(@TempDir Path tempDir) throws Exception { + Path nativeLib = resolveNativeLib(); + assumeTrue(nativeLib != null, "Native runtime not available (run with -Pinprocess)"); + Path altPath = tempDir.resolve("runtime-copy-alt.node"); + Files.copy(nativeLib, altPath); + + new JnaNativeBinding(nativeLib); + + IllegalStateException ex = assertThrows(IllegalStateException.class, () -> new JnaNativeBinding(altPath)); + + String msg = ex.getMessage(); + assertTrue(msg.contains("already loaded from"), "Diagnostic must mention 'already loaded from', got: " + msg); + assertTrue(msg.contains(nativeLib.toString()), "Diagnostic must contain path A, got: " + msg); + assertTrue(msg.contains(altPath.toString()), "Diagnostic must contain path B, got: " + msg); + } + + @Test + void duplicateLoadDiagnosticMentionsNotSupported(@TempDir Path tempDir) throws Exception { + Path nativeLib = resolveNativeLib(); + assumeTrue(nativeLib != null, "Native runtime not available (run with -Pinprocess)"); + Path altPath = tempDir.resolve("runtime-copy-b.node"); + Files.copy(nativeLib, altPath); + + new JnaNativeBinding(nativeLib); + + IllegalStateException ex = assertThrows(IllegalStateException.class, () -> new JnaNativeBinding(altPath)); + assertTrue(ex.getMessage().contains("not supported"), + "Diagnostic must mention 'not supported', got: " + ex.getMessage()); + } + + @Test + void resetForTestingAllowsReloadFromDifferentPath(@TempDir Path tempDir) throws Exception { + Path nativeLib = resolveNativeLib(); + assumeTrue(nativeLib != null, "Native runtime not available (run with -Pinprocess)"); + Path altPath = tempDir.resolve("runtime-copy-reset.node"); + Files.copy(nativeLib, altPath); + + new JnaNativeBinding(nativeLib); + + JnaNativeBinding.resetForTesting(); + + // After reset, a different path must succeed. + new JnaNativeBinding(altPath); + } + + @Test + void activeCallbackCountIsIncrementedDuringCallback() { + StubRuntimeLibrary stub = new StubRuntimeLibrary(); + stub.connectionOpenReturn = 99; + JnaNativeBinding binding = new JnaNativeBinding(stub); + + AtomicInteger observedDuringCallback = new AtomicInteger(-1); + + OutboundCallback userCallback = (userData, data, len) -> { + // Observe binding.activeCallbacks while inside the callback + observedDuringCallback.set(binding.activeCallbacks.get()); + }; + + binding.connectionOpen(1, userCallback, Pointer.NULL, null, 0, null, 0, null, 0); + + // The stub captured the tracked wrapper — invoke it to trigger tracking + assertNotNull(stub.lastCallback, "Stub must have captured the tracked callback"); + stub.lastCallback.invoke(Pointer.NULL, Pointer.NULL, new SizeT(0)); + + assertEquals(1, observedDuringCallback.get(), "binding.activeCallbacks must be 1 during callback execution"); + assertEquals(0, binding.activeCallbacks.get(), + "binding.activeCallbacks must return to 0 after callback completes"); + } + +} diff --git a/java/sdk/src/test/java/com/github/copilot/ffi/NativeRuntimeLoaderTest.java b/java/sdk/src/test/java/com/github/copilot/ffi/NativeRuntimeLoaderTest.java new file mode 100644 index 000000000..d6a9b3481 --- /dev/null +++ b/java/sdk/src/test/java/com/github/copilot/ffi/NativeRuntimeLoaderTest.java @@ -0,0 +1,611 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.ffi; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.AtomicMoveNotSupportedException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.PosixFilePermissions; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class NativeRuntimeLoaderTest { + + private static final String TEST_CLASSIFIER = "linux-x64"; + private static final String OTHER_CLASSIFIER = "darwin-arm64"; + private static final String TEST_VERSION = "1.2.3-test"; + private static final String TEST_NATIVE_VERSION = "0.0.1-test"; + private static final byte[] FAKE_BINARY_CONTENT = "fake runtime.node binary content".getBytes(); + private static final byte[] FAKE_CLI_CONTENT = "fake copilot CLI content".getBytes(); + private static final byte[] OTHER_BINARY_CONTENT = "other runtime.node binary content".getBytes(); + private static final byte[] OTHER_CLI_CONTENT = "other copilot CLI content".getBytes(); + + // ------------------------------------------------------------------------- + // Version properties resource reading + // ------------------------------------------------------------------------- + + @Test + void readVersionReturnsVersionFromPropertiesResource(@TempDir Path tempDir) throws Exception { + ClassLoader loader = classLoaderWithVersionResource(tempDir, "1.0.5-preview"); + assertEquals("1.0.5-preview", NativeRuntimeLoader.readVersion(loader)); + } + + @Test + void readVersionThrowsWhenResourceMissing() { + ClassLoader emptyLoader = new URLClassLoader(new URL[0], null); + IllegalStateException ex = assertThrows(IllegalStateException.class, + () -> NativeRuntimeLoader.readVersion(emptyLoader)); + assertTrue(ex.getMessage().contains(NativeRuntimeLoader.VERSION_RESOURCE)); + } + + @Test + void readVersionThrowsWhenVersionPropertyIsBlank(@TempDir Path tempDir) throws Exception { + ClassLoader loader = classLoaderWithVersionResource(tempDir, " "); + IllegalStateException ex = assertThrows(IllegalStateException.class, + () -> NativeRuntimeLoader.readVersion(loader)); + assertTrue(ex.getMessage().contains("version")); + } + + // ------------------------------------------------------------------------- + // COPILOT_CLI_PATH override + // ------------------------------------------------------------------------- + + @Test + void resolveFromCliPathReturnsSiblingWhenRuntimeNodeExists(@TempDir Path tempDir) throws Exception { + Path fakeCliPath = tempDir.resolve("copilot"); + Files.createFile(fakeCliPath); + Path runtimeNode = tempDir.resolve(NativeRuntimeLoader.RUNTIME_FILENAME); + Files.write(runtimeNode, FAKE_BINARY_CONTENT); + + Path result = NativeRuntimeLoader.resolveFromCliPath(fakeCliPath.toString()); + + assertEquals(runtimeNode, result); + } + + @Test + void resolveFromCliPathReturnsNullWhenRuntimeNodeMissing(@TempDir Path tempDir) throws Exception { + Path fakeCliPath = tempDir.resolve("copilot"); + Files.createFile(fakeCliPath); + + assertNull(NativeRuntimeLoader.resolveFromCliPath(fakeCliPath.toString())); + } + + @Test + void resolveFromCliPathReturnsNullWhenEnvIsNull() throws Exception { + assertNull(NativeRuntimeLoader.resolveFromCliPath(null)); + } + + @Test + void resolveFromCliPathReturnsNullWhenEnvIsBlank() throws Exception { + assertNull(NativeRuntimeLoader.resolveFromCliPath(" ")); + } + + @Test + void resolveFromCliPathReturnsNullWhenRuntimeNodeIsEmpty(@TempDir Path tempDir) throws Exception { + Path fakeCliPath = tempDir.resolve("copilot"); + Files.createFile(fakeCliPath); + Path runtimeNode = tempDir.resolve(NativeRuntimeLoader.RUNTIME_FILENAME); + Files.createFile(runtimeNode); // empty file + + assertNull(NativeRuntimeLoader.resolveFromCliPath(fakeCliPath.toString())); + } + + @Test + void resolveFromCliPathReturnsPrebuildsPathWhenFlatRuntimeNodeIsMissing(@TempDir Path tempDir) throws Exception { + Path fakeCliPath = tempDir.resolve("copilot"); + Files.createFile(fakeCliPath); + Path prebuiltDir = tempDir.resolve("prebuilds").resolve(PlatformDetector.detectClassifier()); + Files.createDirectories(prebuiltDir); + Path runtimeNode = prebuiltDir.resolve(NativeRuntimeLoader.RUNTIME_FILENAME); + Files.write(runtimeNode, FAKE_BINARY_CONTENT); + + Path result = NativeRuntimeLoader.resolveFromCliPath(fakeCliPath.toString()); + + assertEquals(runtimeNode, result); + } + + @Test + void resolveFromCliPathPrefersFlatRuntimeNodeOverPrebuildsPath(@TempDir Path tempDir) throws Exception { + Path fakeCliPath = tempDir.resolve("copilot"); + Files.createFile(fakeCliPath); + Path flatRuntimeNode = tempDir.resolve(NativeRuntimeLoader.RUNTIME_FILENAME); + Files.write(flatRuntimeNode, FAKE_BINARY_CONTENT); + Path prebuiltDir = tempDir.resolve("prebuilds").resolve(PlatformDetector.detectClassifier()); + Files.createDirectories(prebuiltDir); + Files.write(prebuiltDir.resolve(NativeRuntimeLoader.RUNTIME_FILENAME), OTHER_BINARY_CONTENT); + + Path result = NativeRuntimeLoader.resolveFromCliPath(fakeCliPath.toString()); + + assertEquals(flatRuntimeNode, result); + } + + @Test + void resolveEntrypointUsesConfiguredCliWhenRuntimeIsInPrebuilds(@TempDir Path tempDir) throws Exception { + Path cli = Files.writeString(tempDir.resolve("copilot"), "fake cli"); + Path runtimeDir = tempDir.resolve("prebuilds").resolve(PlatformDetector.detectClassifier()); + Files.createDirectories(runtimeDir); + Path runtime = Files.write(runtimeDir.resolve(NativeRuntimeLoader.RUNTIME_FILENAME), FAKE_BINARY_CONTENT); + + assertEquals(cli, NativeRuntimeLoader.resolveEntrypoint(cli.toString(), runtime)); + } + + @Test + void resolveFromCliPathReturnsAbsolutePathForRelativeCliPath(@TempDir Path tempDir) throws Exception { + Path workingDirectory = Path.of("").toAbsolutePath(); + Path fakeCliDir = tempDir.resolve("cli-dir"); + Files.createDirectories(fakeCliDir); + Path fakeCliPath = fakeCliDir.resolve("copilot"); + Files.createFile(fakeCliPath); + Path runtimeNode = fakeCliDir.resolve(NativeRuntimeLoader.RUNTIME_FILENAME); + Files.write(runtimeNode, FAKE_BINARY_CONTENT); + + Path relativeCliPath = workingDirectory.relativize(fakeCliPath); + + assertEquals(runtimeNode, NativeRuntimeLoader.resolveFromCliPath(relativeCliPath.toString())); + } + + @Test + void cliPathOverrideTakesPriorityOverClasspathExtraction(@TempDir Path tempDir) throws Exception { + assumeLinuxX64(); + // Create a valid runtime.node alongside the fake CLI path + Path fakeCliDir = tempDir.resolve("cli-dir"); + Files.createDirectories(fakeCliDir); + Path fakeCliPath = fakeCliDir.resolve("copilot"); + Files.createFile(fakeCliPath); + Path runtimeNode = fakeCliDir.resolve(NativeRuntimeLoader.RUNTIME_FILENAME); + Files.write(runtimeNode, FAKE_BINARY_CONTENT); + + // Source 2 is also available (should be ignored) + Path cacheBase = tempDir.resolve("cache"); + ClassLoader loader = classLoaderWithRuntimeResource(tempDir, TEST_CLASSIFIER); + + Path result = NativeRuntimeLoader.resolve(fakeCliPath.toString(), cacheBase, loader, TEST_CLASSIFIER, + TEST_VERSION); + + assertEquals(runtimeNode, result, "Source 1 (COPILOT_CLI_PATH) must take priority over classpath extraction"); + } + + // ------------------------------------------------------------------------- + // Source 2: classpath extraction to cache + // ------------------------------------------------------------------------- + + @Test + void extractToCacheCopiesResourceToVersionedCacheDirectory(@TempDir Path tempDir) throws Exception { + assumeLinuxX64(); + Path cacheBase = tempDir.resolve("cache"); + ClassLoader loader = classLoaderWithRuntimeResource(tempDir, TEST_CLASSIFIER); + + Path result = NativeRuntimeLoader.extractToCache(cacheBase, loader, TEST_CLASSIFIER, TEST_VERSION); + + Path expected = cacheBase.resolve(TEST_VERSION).resolve(TEST_NATIVE_VERSION).resolve(TEST_CLASSIFIER) + .resolve(NativeRuntimeLoader.RUNTIME_FILENAME); + assertEquals(expected, result); + assertTrue(Files.isRegularFile(result)); + assertTrue(Files.size(result) > 0); + } + + @Test + void extractToCacheReturnsCachedFileOnSecondCall(@TempDir Path tempDir) throws Exception { + assumeLinuxX64(); + Path cacheBase = tempDir.resolve("cache"); + ClassLoader loader = classLoaderWithRuntimeResource(tempDir, TEST_CLASSIFIER); + + Path first = NativeRuntimeLoader.extractToCache(cacheBase, loader, TEST_CLASSIFIER, TEST_VERSION); + long modifiedAfterFirstExtraction = Files.getLastModifiedTime(first).toMillis(); + + // Small delay so modification time would differ if the file were rewritten + Thread.sleep(50); + + Path second = NativeRuntimeLoader.extractToCache(cacheBase, loader, TEST_CLASSIFIER, TEST_VERSION); + long modifiedAfterSecondCall = Files.getLastModifiedTime(second).toMillis(); + + assertEquals(first, second); + assertEquals(modifiedAfterFirstExtraction, modifiedAfterSecondCall, + "Cached file must not be overwritten on cache hit"); + } + + @Test + void changedNativeVersionDoesNotReuseCachedArtifactsForSameSdkVersion(@TempDir Path tempDir) throws Exception { + assumeLinuxX64(); + Path cacheBase = tempDir.resolve("cache"); + ClassLoader firstLoader = classLoaderWithNativeArtifacts(tempDir.resolve("native-v1"), TEST_CLASSIFIER, "1.0.0", + FAKE_BINARY_CONTENT, FAKE_CLI_CONTENT); + ClassLoader secondLoader = classLoaderWithNativeArtifacts(tempDir.resolve("native-v2"), TEST_CLASSIFIER, + "1.1.0", OTHER_BINARY_CONTENT, OTHER_CLI_CONTENT); + + Path firstRuntime = NativeRuntimeLoader.extractToCache(cacheBase, firstLoader, TEST_CLASSIFIER, TEST_VERSION); + Path secondRuntime = NativeRuntimeLoader.extractToCache(cacheBase, secondLoader, TEST_CLASSIFIER, TEST_VERSION); + + assertNotEquals(firstRuntime, secondRuntime, "Different native versions must use different cache entries"); + assertBytesEqual(FAKE_BINARY_CONTENT, Files.readAllBytes(firstRuntime)); + assertBytesEqual(FAKE_CLI_CONTENT, + Files.readAllBytes(firstRuntime.getParent().resolve(NativeRuntimeLoader.CLI_FILENAME))); + assertBytesEqual(OTHER_BINARY_CONTENT, Files.readAllBytes(secondRuntime)); + assertBytesEqual(OTHER_CLI_CONTENT, + Files.readAllBytes(secondRuntime.getParent().resolve(NativeRuntimeLoader.CLI_FILENAME))); + } + + @Test + void extractToCacheThrowsWhenClasspathResourceMissing(@TempDir Path tempDir) { + assumeLinuxX64(); + Path cacheBase = tempDir.resolve("cache"); + ClassLoader emptyLoader = new URLClassLoader(new URL[0], null); + + assertThrows(IOException.class, + () -> NativeRuntimeLoader.extractToCache(cacheBase, emptyLoader, TEST_CLASSIFIER, TEST_VERSION)); + } + + @Test + void extractToCacheThrowsWhenNativeMetadataMissing(@TempDir Path tempDir) throws Exception { + assumeLinuxX64(); + Path resourceDir = tempDir.resolve("native").resolve(TEST_CLASSIFIER); + Files.createDirectories(resourceDir); + Files.write(resourceDir.resolve(NativeRuntimeLoader.RUNTIME_FILENAME), FAKE_BINARY_CONTENT); + ClassLoader loader = new URLClassLoader(new URL[]{tempDir.toUri().toURL()}, null); + + IOException ex = assertThrows(IOException.class, () -> NativeRuntimeLoader + .extractToCache(tempDir.resolve("cache"), loader, TEST_CLASSIFIER, TEST_VERSION)); + + assertTrue(ex.getMessage().contains("platform.properties")); + } + + @Test + void extractedBinaryContentsMatchClasspathResource(@TempDir Path tempDir) throws Exception { + assumeLinuxX64(); + Path cacheBase = tempDir.resolve("cache"); + ClassLoader loader = classLoaderWithRuntimeResource(tempDir, TEST_CLASSIFIER); + + Path result = NativeRuntimeLoader.extractToCache(cacheBase, loader, TEST_CLASSIFIER, TEST_VERSION); + + byte[] extracted = Files.readAllBytes(result); + assertBytesEqual(FAKE_BINARY_CONTENT, extracted); + } + + @Test + void extractToCacheFiltersClasspathByClassifier(@TempDir Path tempDir) throws Exception { + assumeLinuxX64(); + Path cacheBase = tempDir.resolve("cache"); + writeRuntimeResource(tempDir, TEST_CLASSIFIER, FAKE_BINARY_CONTENT); + writeRuntimeResource(tempDir, OTHER_CLASSIFIER, OTHER_BINARY_CONTENT); + ClassLoader loader = new URLClassLoader(new URL[]{tempDir.toUri().toURL()}, null); + + Path result = NativeRuntimeLoader.extractToCache(cacheBase, loader, TEST_CLASSIFIER, TEST_VERSION); + + assertTrue(result.toString().contains(TEST_CLASSIFIER), "Cache path must include the classifier: " + result); + assertBytesEqual(FAKE_BINARY_CONTENT, Files.readAllBytes(result)); + } + + @Test + void extractToCacheRepairsInvalidCacheEntry(@TempDir Path tempDir) throws Exception { + assumeLinuxX64(); + Path cacheBase = tempDir.resolve("cache"); + Path cached = cacheBase.resolve(TEST_VERSION).resolve(TEST_NATIVE_VERSION).resolve(TEST_CLASSIFIER) + .resolve(NativeRuntimeLoader.RUNTIME_FILENAME); + Files.createDirectories(cached.getParent()); + Files.createFile(cached); + ClassLoader loader = classLoaderWithRuntimeResource(tempDir, TEST_CLASSIFIER); + + Path result = NativeRuntimeLoader.extractToCache(cacheBase, loader, TEST_CLASSIFIER, TEST_VERSION); + + assertEquals(cached, result); + assertBytesEqual(FAKE_BINARY_CONTENT, Files.readAllBytes(result)); + } + + @Test + void nonExecutableCachedCliIsNotAcceptedAsValid(@TempDir Path tempDir) throws Exception { + assumeLinuxX64(); + assumeTrue(Files.getFileStore(tempDir).supportsFileAttributeView("posix")); + Path cacheBase = tempDir.resolve("cache"); + Path cacheDir = cacheBase.resolve(TEST_VERSION).resolve(TEST_NATIVE_VERSION).resolve(TEST_CLASSIFIER); + Files.createDirectories(cacheDir); + Files.write(cacheDir.resolve(NativeRuntimeLoader.RUNTIME_FILENAME), FAKE_BINARY_CONTENT); + Path cachedCli = Files.write(cacheDir.resolve(NativeRuntimeLoader.CLI_FILENAME), FAKE_CLI_CONTENT); + Files.setPosixFilePermissions(cachedCli, PosixFilePermissions.fromString("rw-------")); + ClassLoader loader = classLoaderWithRuntimeAndCliResources(tempDir, TEST_CLASSIFIER); + + NativeRuntimeLoader.extractToCache(cacheBase, loader, TEST_CLASSIFIER, TEST_VERSION); + + assertTrue(Files.isExecutable(cachedCli), "A non-executable cached CLI must be repaired or replaced"); + } + + // ------------------------------------------------------------------------- + // Source 3: bundled-CLI sibling + // ------------------------------------------------------------------------- + + @Test + void bundledCliSiblingIsUsedWhenClasspathResourceAbsent(@TempDir Path tempDir) throws Exception { + assumeLinuxX64(); + Path bundledCliDir = tempDir.resolve("bundled-cli"); + Files.createDirectories(bundledCliDir); + Path runtimeNode = bundledCliDir.resolve(NativeRuntimeLoader.RUNTIME_FILENAME); + Files.write(runtimeNode, FAKE_BINARY_CONTENT); + + Path cacheBase = tempDir.resolve("cache"); + ClassLoader emptyLoader = new URLClassLoader(new URL[0], null); // no classpath resource + + Path result = NativeRuntimeLoader.resolve(null, cacheBase, emptyLoader, TEST_CLASSIFIER, TEST_VERSION, + bundledCliDir); + + assertEquals(runtimeNode, result, + "Source 3 (bundled-CLI sibling) must be used when classpath resource is absent"); + } + + @Test + void classpathResourceWinsOverBundledCliSibling(@TempDir Path tempDir) throws Exception { + assumeLinuxX64(); + // Source 3: bundled CLI dir with runtime.node (should NOT win) + Path bundledCliDir = tempDir.resolve("bundled-cli"); + Files.createDirectories(bundledCliDir); + Files.write(bundledCliDir.resolve(NativeRuntimeLoader.RUNTIME_FILENAME), "bundled".getBytes()); + + // Source 2: classpath resource (should win) + Path cacheBase = tempDir.resolve("cache"); + ClassLoader loader = classLoaderWithRuntimeResource(tempDir, TEST_CLASSIFIER); + + Path result = NativeRuntimeLoader.resolve(null, cacheBase, loader, TEST_CLASSIFIER, TEST_VERSION, + bundledCliDir); + + Path expectedFromClasspath = cacheBase.resolve(TEST_VERSION).resolve(TEST_NATIVE_VERSION) + .resolve(TEST_CLASSIFIER).resolve(NativeRuntimeLoader.RUNTIME_FILENAME); + assertEquals(expectedFromClasspath, result, + "Source 2 (classpath) must win over source 3 (bundled-CLI sibling)"); + assertNotEquals(bundledCliDir.resolve(NativeRuntimeLoader.RUNTIME_FILENAME), result); + } + + @Test + void bundledCliSiblingIsIgnoredWhenRuntimeNodeMissing(@TempDir Path tempDir) { + assumeLinuxX64(); + Path bundledCliDir = tempDir.resolve("bundled-cli-no-runtime"); + // bundledCliDir doesn't even exist — no runtime.node present + + Path cacheBase = tempDir.resolve("cache"); + ClassLoader emptyLoader = new URLClassLoader(new URL[0], null); + + // Both source 2 and source 3 absent: must throw (the classpath error) + IOException ex = assertThrows(IOException.class, () -> NativeRuntimeLoader.resolve(null, cacheBase, emptyLoader, + TEST_CLASSIFIER, TEST_VERSION, bundledCliDir)); + assertTrue(ex.getMessage().contains("classpath"), "Error should mention classpath: " + ex.getMessage()); + } + + // ------------------------------------------------------------------------- + // Atomic publication test seam + // ------------------------------------------------------------------------- + + @Test + void defaultPublisherMovesSourceToTarget(@TempDir Path tempDir) throws Exception { + Path temp = Files.createTempFile(tempDir, "runtime-tmp-", ".node"); + Files.write(temp, FAKE_BINARY_CONTENT); + Path target = tempDir.resolve(NativeRuntimeLoader.RUNTIME_FILENAME); + + NativeRuntimeLoader.DEFAULT_PUBLISHER.publish(temp, target); + + assertTrue(Files.isRegularFile(target), "Target must exist after publication"); + assertTrue(Files.size(target) > 0, "Target must be non-empty"); + assertFalse(Files.exists(temp), "Source temp file must be absent after atomic move"); + } + + @Test + void cliIsExecutableBeforeAtomicPublication(@TempDir Path tempDir) throws Exception { + assumeLinuxX64(); + assumeTrue(Files.getFileStore(tempDir).supportsFileAttributeView("posix")); + Path cacheBase = tempDir.resolve("cache"); + ClassLoader loader = classLoaderWithRuntimeAndCliResources(tempDir, TEST_CLASSIFIER); + NativeRuntimeLoader.AtomicPublisher publisher = (temp, cached) -> { + if (cached.getFileName().toString().equals(NativeRuntimeLoader.CLI_FILENAME)) { + assertTrue(Files.isExecutable(temp), "CLI temp file must be executable before atomic publication"); + } + Files.move(temp, cached, StandardCopyOption.REPLACE_EXISTING); + }; + + NativeRuntimeLoader.extractToCache(cacheBase, loader, TEST_CLASSIFIER, TEST_VERSION, publisher); + } + + @Test + void extractionCleansUpTempFileWhenPublicationFails(@TempDir Path tempDir) throws Exception { + assumeLinuxX64(); + Path cacheBase = tempDir.resolve("cache"); + ClassLoader loader = classLoaderWithRuntimeResource(tempDir, TEST_CLASSIFIER); + + // Capture the temp path so we can verify it was deleted + Path[] capturedTemp = {null}; + NativeRuntimeLoader.AtomicPublisher failingPublisher = (temp, cached) -> { + capturedTemp[0] = temp; + throw new AtomicMoveNotSupportedException(temp.toString(), cached.toString(), + "filesystem does not support atomic moves — test"); + }; + + assertThrows(AtomicMoveNotSupportedException.class, () -> NativeRuntimeLoader.extractToCache(cacheBase, loader, + TEST_CLASSIFIER, TEST_VERSION, failingPublisher)); + + assertNotNull(capturedTemp[0], "Publisher must have been invoked"); + assertFalse(Files.exists(capturedTemp[0]), "Temp file must be deleted after failed publication"); + } + + @Test + void extractionCleansUpTempFileWhenPublisherThrowsIllegalStateException(@TempDir Path tempDir) throws Exception { + assumeLinuxX64(); + Path cacheBase = tempDir.resolve("cache"); + ClassLoader loader = classLoaderWithRuntimeResource(tempDir, TEST_CLASSIFIER); + + Path[] capturedTemp = {null}; + NativeRuntimeLoader.AtomicPublisher unsupportedPublisher = (temp, cached) -> { + capturedTemp[0] = temp; + // Simulate the wrapping that DEFAULT_PUBLISHER performs for + // AtomicMoveNotSupportedException + throw new IllegalStateException("Filesystem does not support atomic moves; cannot safely publish " + + NativeRuntimeLoader.RUNTIME_FILENAME + " to " + cached); + }; + + IllegalStateException ex = assertThrows(IllegalStateException.class, () -> NativeRuntimeLoader + .extractToCache(cacheBase, loader, TEST_CLASSIFIER, TEST_VERSION, unsupportedPublisher)); + + assertTrue(ex.getMessage().contains("atomic moves"), + "Error message should describe the atomic-move failure: " + ex.getMessage()); + assertNotNull(capturedTemp[0], "Publisher must have been invoked"); + assertFalse(Files.exists(capturedTemp[0]), "Temp file must be deleted after failed atomic publication"); + } + + // ------------------------------------------------------------------------- + // Concurrent extraction safety + // ------------------------------------------------------------------------- + + @Test + void concurrentExtractionByMultipleThreadsBothSucceed(@TempDir Path tempDir) throws Exception { + assumeLinuxX64(); + Path cacheBase = tempDir.resolve("cache"); + ClassLoader loader = classLoaderWithRuntimeResource(tempDir, TEST_CLASSIFIER); + int threadCount = 8; + CountDownLatch startGate = new CountDownLatch(1); + ExecutorService pool = Executors.newFixedThreadPool(threadCount); + List> futures = new ArrayList<>(); + + for (int i = 0; i < threadCount; i++) { + futures.add(pool.submit(() -> { + startGate.await(); + return NativeRuntimeLoader.extractToCache(cacheBase, loader, TEST_CLASSIFIER, TEST_VERSION); + })); + } + + startGate.countDown(); + pool.shutdown(); + assertTrue(pool.awaitTermination(10, TimeUnit.SECONDS)); + + Path expected = cacheBase.resolve(TEST_VERSION).resolve(TEST_NATIVE_VERSION).resolve(TEST_CLASSIFIER) + .resolve(NativeRuntimeLoader.RUNTIME_FILENAME); + for (Future future : futures) { + Path result = future.get(); + assertEquals(expected, result); + assertTrue(Files.isRegularFile(result)); + assertTrue(Files.size(result) > 0); + } + try (var files = Files.list(expected.getParent())) { + assertEquals(List.of(expected), files.toList(), "Concurrent extraction must clean up temporary files"); + } + } + + // ------------------------------------------------------------------------- + // resolve() -- full three-source resolution chain + // ------------------------------------------------------------------------- + + @Test + void resolveWithNullCliEnvExtractsFromClasspath(@TempDir Path tempDir) throws Exception { + assumeLinuxX64(); + Path cacheBase = tempDir.resolve("cache"); + ClassLoader loader = classLoaderWithRuntimeResource(tempDir, TEST_CLASSIFIER); + + Path result = NativeRuntimeLoader.resolve(null, cacheBase, loader, TEST_CLASSIFIER, TEST_VERSION); + + assertNotNull(result); + assertTrue(Files.isRegularFile(result)); + assertTrue(Files.size(result) > 0); + } + + @Test + void resolveThrowsWhenNoSourceIsAvailable(@TempDir Path tempDir) { + assumeLinuxX64(); + Path cacheBase = tempDir.resolve("cache"); + ClassLoader emptyLoader = new URLClassLoader(new URL[0], null); + + // No CLI env, no classpath resource, no bundled-CLI dir → throw + assertThrows(IOException.class, + () -> NativeRuntimeLoader.resolve(null, cacheBase, emptyLoader, TEST_CLASSIFIER, TEST_VERSION)); + } + + @Test + void resolveFallsBackToRuntimeAlongsideBundledCli(@TempDir Path tempDir) throws Exception { + assumeLinuxX64(); + Path cacheBase = tempDir.resolve("cache"); + ClassLoader emptyLoader = new URLClassLoader(new URL[0], null); + Path bundledCli = tempDir.resolve("copilot"); + Files.createFile(bundledCli); + Path runtimeNode = tempDir.resolve(NativeRuntimeLoader.RUNTIME_FILENAME); + Files.write(runtimeNode, FAKE_BINARY_CONTENT); + + Path result = NativeRuntimeLoader.resolve(null, bundledCli.toString(), cacheBase, emptyLoader, TEST_CLASSIFIER, + TEST_VERSION); + + assertEquals(runtimeNode, result); + } + + // ------------------------------------------------------------------------- + // Helpers + // ------------------------------------------------------------------------- + + private static void assumeLinuxX64() { + String actualClassifier; + try { + actualClassifier = PlatformDetector.detectClassifier(); + } catch (IllegalStateException ex) { + actualClassifier = "unsupported"; + } + assumeTrue(TEST_CLASSIFIER.equals(actualClassifier), + "Requires linux-x64; detected " + actualClassifier + "; see #2323"); + } + + private static ClassLoader classLoaderWithVersionResource(Path tempDir, String version) throws IOException { + Path propsFile = tempDir.resolve(NativeRuntimeLoader.VERSION_RESOURCE); + Files.writeString(propsFile, "version=" + version + "\n"); + return new URLClassLoader(new URL[]{tempDir.toUri().toURL()}, null); + } + + private static ClassLoader classLoaderWithRuntimeResource(Path tempDir, String classifier) throws IOException { + writeRuntimeResource(tempDir, classifier, FAKE_BINARY_CONTENT); + return new URLClassLoader(new URL[]{tempDir.toUri().toURL()}, null); + } + + private static ClassLoader classLoaderWithRuntimeAndCliResources(Path tempDir, String classifier) + throws IOException { + writeRuntimeResource(tempDir, classifier, FAKE_BINARY_CONTENT); + Path resourceDir = tempDir.resolve("native").resolve(classifier); + Files.write(resourceDir.resolve(NativeRuntimeLoader.CLI_FILENAME), FAKE_CLI_CONTENT); + return new URLClassLoader(new URL[]{tempDir.toUri().toURL()}, null); + } + + private static ClassLoader classLoaderWithNativeArtifacts(Path tempDir, String classifier, String nativeVersion, + byte[] runtimeContent, byte[] cliContent) throws IOException { + writeRuntimeResource(tempDir, classifier, runtimeContent); + Path resourceDir = tempDir.resolve("native").resolve(classifier); + Files.write(resourceDir.resolve(NativeRuntimeLoader.CLI_FILENAME), cliContent); + Files.writeString(resourceDir.resolve("platform.properties"), + "classifier=" + classifier + "\nversion=" + nativeVersion + "\n"); + return new URLClassLoader(new URL[]{tempDir.toUri().toURL()}, null); + } + + private static void writeRuntimeResource(Path tempDir, String classifier, byte[] content) throws IOException { + Path resourceDir = tempDir.resolve("native").resolve(classifier); + Files.createDirectories(resourceDir); + Files.write(resourceDir.resolve(NativeRuntimeLoader.RUNTIME_FILENAME), content); + Files.writeString(resourceDir.resolve("platform.properties"), + "classifier=" + classifier + "\nversion=" + TEST_NATIVE_VERSION + "\n"); + } + + private static void assertBytesEqual(byte[] expected, byte[] actual) { + assertEquals(expected.length, actual.length, "Array lengths differ"); + for (int i = 0; i < expected.length; i++) { + assertEquals(expected[i], actual[i], "Byte differs at index " + i); + } + } +} diff --git a/java/sdk/src/test/java/com/github/copilot/ffi/PlatformDetectorTest.java b/java/sdk/src/test/java/com/github/copilot/ffi/PlatformDetectorTest.java new file mode 100644 index 000000000..82049ea6a --- /dev/null +++ b/java/sdk/src/test/java/com/github/copilot/ffi/PlatformDetectorTest.java @@ -0,0 +1,217 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.ffi; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class PlatformDetectorTest { + + @Test + void detectOsMapsSupportedNames() { + withSystemProperty("os.name", "Mac OS X", () -> assertEquals("darwin", PlatformDetector.detectOs())); + withSystemProperty("os.name", "Darwin", () -> assertEquals("darwin", PlatformDetector.detectOs())); + withSystemProperty("os.name", "Windows 11", () -> assertEquals("win32", PlatformDetector.detectOs())); + withSystemProperty("os.name", "Linux", () -> assertEquals("linux", PlatformDetector.detectOs())); + } + + @Test + void detectOsThrowsForUnsupportedSystem() { + withSystemProperty("os.name", "Solaris", + () -> assertThrows(IllegalStateException.class, PlatformDetector::detectOs)); + } + + @Test + void detectArchMapsSupportedAliases() { + withSystemProperty("os.arch", "amd64", () -> assertEquals("x64", PlatformDetector.detectArch())); + withSystemProperty("os.arch", "x86_64", () -> assertEquals("x64", PlatformDetector.detectArch())); + withSystemProperty("os.arch", "x64", () -> assertEquals("x64", PlatformDetector.detectArch())); + withSystemProperty("os.arch", "aarch64", () -> assertEquals("arm64", PlatformDetector.detectArch())); + withSystemProperty("os.arch", "arm64", () -> assertEquals("arm64", PlatformDetector.detectArch())); + } + + @Test + void detectArchThrowsForUnsupportedArchitecture() { + withSystemProperty("os.arch", "ppc64", + () -> assertThrows(IllegalStateException.class, PlatformDetector::detectArch)); + } + + @Test + void detectLinuxLibcParsesGlibcInterpPath() throws Exception { + byte[] glibcProbe = buildElf64ProbeWithInterp("/lib64/ld-linux-x86-64.so.2"); + assertEquals(PlatformDetector.LinuxLibc.GLIBC, PlatformDetector.detectLinuxLibc(glibcProbe)); + } + + @Test + void detectLinuxLibcParsesMuslInterpPath() throws Exception { + byte[] muslProbe = buildElf64ProbeWithInterp("/lib/ld-musl-x86_64.so.1"); + assertEquals(PlatformDetector.LinuxLibc.MUSL, PlatformDetector.detectLinuxLibc(muslProbe)); + } + + @Test + void detectLinuxLibcOnLinuxReturnsRecognizedValue() { + withSystemProperty("os.name", "Linux", () -> { + PlatformDetector.LinuxLibc libc = PlatformDetector.detectLinuxLibc(); + assertTrue(libc == PlatformDetector.LinuxLibc.GLIBC || libc == PlatformDetector.LinuxLibc.MUSL + || libc == PlatformDetector.LinuxLibc.UNKNOWN); + }); + } + + @Test + void detectLinuxLibcReturnsUnknownOutsideLinux() { + withSystemProperty("os.name", "Windows 11", + () -> assertEquals(PlatformDetector.LinuxLibc.UNKNOWN, PlatformDetector.detectLinuxLibc())); + } + + @Test + void detectClassifierReturnsClassifierForCurrentLinuxLibc() { + PlatformDetector.LinuxLibc libc = PlatformDetector.detectLinuxLibc(); + String expected = libc == PlatformDetector.LinuxLibc.MUSL ? "linuxmusl-x64" : "linux-x64"; + + withSystemProperties("Linux", "amd64", () -> assertEquals(expected, PlatformDetector.detectClassifier())); + } + + @Test + void detectClassifierAllowListCoversAllSupportedValues() { + Set expected = Set.of("linux-x64", "linux-arm64", "linuxmusl-x64", "linuxmusl-arm64", "darwin-x64", + "darwin-arm64", "win32-x64", "win32-arm64"); + assertEquals(expected, PlatformDetector.supportedClassifiers()); + + Set resolved = new LinkedHashSet<>(); + resolved.add(PlatformDetector.detectClassifier("linux", "x64", PlatformDetector.LinuxLibc.GLIBC)); + resolved.add(PlatformDetector.detectClassifier("linux", "arm64", PlatformDetector.LinuxLibc.GLIBC)); + resolved.add(PlatformDetector.detectClassifier("linux", "x64", PlatformDetector.LinuxLibc.MUSL)); + resolved.add(PlatformDetector.detectClassifier("linux", "arm64", PlatformDetector.LinuxLibc.MUSL)); + resolved.add(PlatformDetector.detectClassifier("darwin", "x64", PlatformDetector.LinuxLibc.UNKNOWN)); + resolved.add(PlatformDetector.detectClassifier("darwin", "arm64", PlatformDetector.LinuxLibc.UNKNOWN)); + resolved.add(PlatformDetector.detectClassifier("win32", "x64", PlatformDetector.LinuxLibc.UNKNOWN)); + resolved.add(PlatformDetector.detectClassifier("win32", "arm64", PlatformDetector.LinuxLibc.UNKNOWN)); + + assertEquals(expected, resolved); + } + + @Test + void detectClassifierFailsFastForUnsupportedTuple() { + assertThrows(IllegalStateException.class, + () -> PlatformDetector.detectClassifier("darwin", "mips64", PlatformDetector.LinuxLibc.UNKNOWN)); + } + + @Test + void detectClassifierFailsForUnsupportedCurrentPlatform() { + withSystemProperties("Solaris", "amd64", + () -> assertThrows(IllegalStateException.class, PlatformDetector::detectClassifier)); + } + + @Test + void detectLinuxLibcReturnsUnknownWhenElfParsingFails() { + byte[] invalidProbe = new byte[64]; + Arrays.fill(invalidProbe, (byte) 1); + + assertThrows(IOException.class, () -> PlatformDetector.detectLinuxLibc(invalidProbe)); + } + + @Test + void detectLinuxLibcReturnsUnknownForTruncatedProgramHeader(@TempDir Path tempDir) throws IOException { + byte[] malformedProbe = buildElf64ProbeWithInterp("/lib64/ld-linux-x86-64.so.2"); + writeLe64(malformedProbe, 32, malformedProbe.length - 1); + writeLe16(malformedProbe, 54, 1); + Path executable = tempDir.resolve("malformed-elf"); + Files.write(executable, malformedProbe); + + assertEquals(PlatformDetector.LinuxLibc.UNKNOWN, PlatformDetector.detectLinuxLibc(executable)); + } + + private static void withSystemProperties(String osName, String osArch, Runnable action) { + String previousOsName = System.getProperty("os.name"); + String previousOsArch = System.getProperty("os.arch"); + try { + System.setProperty("os.name", osName); + System.setProperty("os.arch", osArch); + action.run(); + } finally { + restoreProperty("os.name", previousOsName); + restoreProperty("os.arch", previousOsArch); + } + } + + private static void withSystemProperty(String key, String value, Runnable action) { + String previousValue = System.getProperty(key); + try { + System.setProperty(key, value); + action.run(); + } finally { + restoreProperty(key, previousValue); + } + } + + private static void restoreProperty(String key, String value) { + if (value == null) { + System.clearProperty(key); + } else { + System.setProperty(key, value); + } + } + + private static byte[] buildElf64ProbeWithInterp(String interpreterPath) { + byte[] interpBytes = interpreterPath.getBytes(StandardCharsets.UTF_8); + byte[] probe = new byte[512]; + + probe[0] = 0x7F; + probe[1] = 'E'; + probe[2] = 'L'; + probe[3] = 'F'; + probe[4] = 2; + probe[5] = 1; + + int phoff = 64; + int phentsize = 56; + int phnum = 1; + int interpOffset = 256; + int interpSize = interpBytes.length + 1; + + writeLe64(probe, 32, phoff); + writeLe16(probe, 54, phentsize); + writeLe16(probe, 56, phnum); + + int pHeader = phoff; + writeLe32(probe, pHeader, 3); + writeLe64(probe, pHeader + 8, interpOffset); + writeLe64(probe, pHeader + 32, interpSize); + + System.arraycopy(interpBytes, 0, probe, interpOffset, interpBytes.length); + probe[interpOffset + interpBytes.length] = 0; + return probe; + } + + private static void writeLe16(byte[] buffer, int offset, int value) { + buffer[offset] = (byte) (value & 0xFF); + buffer[offset + 1] = (byte) ((value >>> 8) & 0xFF); + } + + private static void writeLe32(byte[] buffer, int offset, int value) { + buffer[offset] = (byte) (value & 0xFF); + buffer[offset + 1] = (byte) ((value >>> 8) & 0xFF); + buffer[offset + 2] = (byte) ((value >>> 16) & 0xFF); + buffer[offset + 3] = (byte) ((value >>> 24) & 0xFF); + } + + private static void writeLe64(byte[] buffer, int offset, long value) { + for (int i = 0; i < 8; i++) { + buffer[offset + i] = (byte) ((value >>> (8 * i)) & 0xFF); + } + } +} diff --git a/java/sdk/src/test/java/com/github/copilot/ffi/QueueInputStreamTest.java b/java/sdk/src/test/java/com/github/copilot/ffi/QueueInputStreamTest.java new file mode 100644 index 000000000..6fd961670 --- /dev/null +++ b/java/sdk/src/test/java/com/github/copilot/ffi/QueueInputStreamTest.java @@ -0,0 +1,81 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.ffi; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Test; + +class QueueInputStreamTest { + + @Test + void readReturnsEnqueuedBytesAcrossMultipleChunks() throws Exception { + QueueInputStream stream = new QueueInputStream(); + stream.enqueue("hello ".getBytes(StandardCharsets.UTF_8)); + stream.enqueue("world".getBytes(StandardCharsets.UTF_8)); + + byte[] buffer = new byte[11]; + int first = stream.read(buffer, 0, 6); + int second = stream.read(buffer, 6, 5); + + assertEquals(6, first); + assertEquals(5, second); + assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8), buffer); + } + + @Test + void readBlocksUntilDataArrives() throws Exception { + QueueInputStream stream = new QueueInputStream(); + + CompletableFuture readFuture = CompletableFuture.supplyAsync(() -> { + try { + return stream.read(); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + + Thread.sleep(100); + stream.enqueue(new byte[]{(byte) 'A'}); + + assertEquals((int) 'A', readFuture.get(2, TimeUnit.SECONDS)); + } + + @Test + void closeSignalsEndOfStream() throws Exception { + QueueInputStream stream = new QueueInputStream(); + stream.enqueue("x".getBytes(StandardCharsets.UTF_8)); + + assertEquals('x', stream.read()); + stream.close(); + assertEquals(-1, stream.read()); + } + + @Test + void closeUnblocksPendingReadWithEof() throws Exception { + QueueInputStream stream = new QueueInputStream(); + + CompletableFuture readFuture = CompletableFuture.supplyAsync(() -> { + try { + return stream.read(); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + + Thread.sleep(100); + stream.close(); + + assertEquals(-1, readFuture.get(2, TimeUnit.SECONDS)); + assertTrue(readFuture.isDone()); + } +} diff --git a/java/src/test/java/com/github/copilot/generated/GeneratedEventTypesCoverageTest.java b/java/sdk/src/test/java/com/github/copilot/generated/GeneratedEventTypesCoverageTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/generated/GeneratedEventTypesCoverageTest.java rename to java/sdk/src/test/java/com/github/copilot/generated/GeneratedEventTypesCoverageTest.java diff --git a/java/src/test/java/com/github/copilot/generated/GeneratedTypesJacksonRoundTripTest.java b/java/sdk/src/test/java/com/github/copilot/generated/GeneratedTypesJacksonRoundTripTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/generated/GeneratedTypesJacksonRoundTripTest.java rename to java/sdk/src/test/java/com/github/copilot/generated/GeneratedTypesJacksonRoundTripTest.java diff --git a/java/src/test/java/com/github/copilot/generated/rpc/GeneratedRpcApiCoverageTest.java b/java/sdk/src/test/java/com/github/copilot/generated/rpc/GeneratedRpcApiCoverageTest.java similarity index 99% rename from java/src/test/java/com/github/copilot/generated/rpc/GeneratedRpcApiCoverageTest.java rename to java/sdk/src/test/java/com/github/copilot/generated/rpc/GeneratedRpcApiCoverageTest.java index a4c934463..e92b0f968 100644 --- a/java/src/test/java/com/github/copilot/generated/rpc/GeneratedRpcApiCoverageTest.java +++ b/java/sdk/src/test/java/com/github/copilot/generated/rpc/GeneratedRpcApiCoverageTest.java @@ -498,7 +498,7 @@ void sessionRpc_permissions_handlePendingPermissionRequest_merges_sessionId() { var stub = new StubCaller(); var session = new SessionRpc(stub, "sess-perm"); - var permParams = new SessionPermissionsHandlePendingPermissionRequestParams(null, "req-perm-1", "allow"); + var permParams = new SessionPermissionsHandlePendingPermissionRequestParams(null, "req-perm-1", "allow", null); session.permissions.handlePendingPermissionRequest(permParams); assertEquals(1, stub.calls.size()); @@ -551,8 +551,8 @@ void sessionRpc_history_compact_injects_sessionId() { assertEquals(1, stub.calls.size()); assertEquals("session.history.compact", stub.calls.get(0).method()); - var params = (Map) stub.calls.get(0).params(); - assertEquals("sess-hist", params.get("sessionId")); + var params = (com.fasterxml.jackson.databind.node.ObjectNode) stub.calls.get(0).params(); + assertEquals("sess-hist", params.get("sessionId").asText()); } @Test diff --git a/java/src/test/java/com/github/copilot/generated/rpc/GeneratedRpcRecordsCoverageTest.java b/java/sdk/src/test/java/com/github/copilot/generated/rpc/GeneratedRpcRecordsCoverageTest.java similarity index 95% rename from java/src/test/java/com/github/copilot/generated/rpc/GeneratedRpcRecordsCoverageTest.java rename to java/sdk/src/test/java/com/github/copilot/generated/rpc/GeneratedRpcRecordsCoverageTest.java index 2b6b0164d..80d224bbc 100644 --- a/java/src/test/java/com/github/copilot/generated/rpc/GeneratedRpcRecordsCoverageTest.java +++ b/java/sdk/src/test/java/com/github/copilot/generated/rpc/GeneratedRpcRecordsCoverageTest.java @@ -87,7 +87,7 @@ void sessionAgentGetCurrentParams_record() { @Test void sessionAgentListParams_record() { - var params = new SessionAgentListParams("sess-3"); + var params = new SessionAgentListParams("sess-3", null, null); assertEquals("sess-3", params.sessionId()); } @@ -234,8 +234,12 @@ void sessionFsWriteFileParams_record() { @Test void sessionHistoryCompactParams_record() { - var params = new SessionHistoryCompactParams("sess-22"); + var params = new SessionHistoryCompactParams("sess-22", "focus on the API surface", + SessionHistoryCompactParams.SessionHistoryCompactParamsTrigger.MANUAL, 4096L); assertEquals("sess-22", params.sessionId()); + assertEquals("focus on the API surface", params.customInstructions()); + assertEquals(SessionHistoryCompactParams.SessionHistoryCompactParamsTrigger.MANUAL, params.trigger()); + assertEquals(4096L, params.tokenLimit()); } @Test @@ -321,21 +325,24 @@ void sessionModelGetCurrentParams_record() { @Test void sessionModelSwitchToParams_record() { - var params = new SessionModelSwitchToParams("sess-32", "claude-sonnet-4.5", "high", null, null, null, null); + var params = new SessionModelSwitchToParams("sess-32", "claude-sonnet-4.5", "high", null, null, null, null, + null); assertEquals("sess-32", params.sessionId()); assertEquals("claude-sonnet-4.5", params.modelId()); assertEquals("high", params.reasoningEffort()); assertNull(params.reasoningSummary()); assertNull(params.verbosity()); assertNull(params.modelCapabilities()); + assertNull(params.deferIfModelChangeQueued()); } @Test void sessionPermissionsHandlePendingPermissionRequestParams_record() { - var params = new SessionPermissionsHandlePendingPermissionRequestParams("sess-33", "req-1", "allow"); + var params = new SessionPermissionsHandlePendingPermissionRequestParams("sess-33", "req-1", "allow", null); assertEquals("sess-33", params.sessionId()); assertEquals("req-1", params.requestId()); assertEquals("allow", params.result()); + assertNull(params.decisionContext()); } @Test @@ -462,7 +469,7 @@ void pingResult_fields() { @Test void sessionAgentListResult_with_items() { var item = new AgentInfo("name1", "Name One", "Desc 1", "/path/to/agent1", null, null, null, null, null, null, - null); + null, null); var result = new SessionAgentListResult(List.of(item)); assertEquals(1, result.agents().size()); assertEquals("name1", result.agents().get(0).name()); @@ -473,7 +480,7 @@ void sessionAgentListResult_with_items() { @Test void sessionAgentGetCurrentResult_nested() { - var agent = new AgentInfo("agent-1", "Agent One", "Does things", null, null, null, null, null, null, null, + var agent = new AgentInfo("agent-1", "Agent One", "Does things", null, null, null, null, null, null, null, null, null); var result = new SessionAgentGetCurrentResult(agent); assertEquals("agent-1", result.agent().name()); @@ -490,7 +497,7 @@ void sessionAgentGetCurrentResult_null_agent() { @Test void sessionAgentReloadResult_with_items() { - var item = new AgentInfo("a", "A", "Desc", "/path/to/a", null, null, null, null, null, null, null); + var item = new AgentInfo("a", "A", "Desc", "/path/to/a", null, null, null, null, null, null, null, null); var result = new SessionAgentReloadResult(List.of(item)); assertEquals(1, result.agents().size()); assertEquals("a", result.agents().get(0).name()); @@ -499,7 +506,7 @@ void sessionAgentReloadResult_with_items() { @Test void sessionAgentSelectResult_nested() { var agent = new AgentInfo("selected", "Selected", "The selected agent", "/path/to/selected", null, null, null, - null, null, null, null); + null, null, null, null, null); var result = new SessionAgentSelectResult(agent); assertEquals("selected", result.agent().name()); } @@ -614,8 +621,10 @@ void sessionHistoryCompactResult_nested() { @Test void sessionHistoryTruncateResult_record() { - var result = new SessionHistoryTruncateResult(3L); + var result = new SessionHistoryTruncateResult(3L, false, null); assertEquals(3L, result.eventsRemoved()); + assertEquals(false, result.checkpointCleanupFailed()); + assertNull(result.checkpointCleanupError()); } @Test @@ -652,8 +661,9 @@ void sessionModelGetCurrentResult_record() { @Test void sessionModelSwitchToResult_record() { - var result = new SessionModelSwitchToResult("gpt-5"); + var result = new SessionModelSwitchToResult("gpt-5", true); assertEquals("gpt-5", result.modelId()); + assertEquals(true, result.deferred()); } @Test @@ -697,11 +707,12 @@ void sessionShellKillResult_record() { @Test void sessionSkillsListResult_nested() { - var item = new Skill("deploy", "Deploy the app", SkillSource.PROJECT, true, true, "/skills/deploy.md", null, - null); + var item = new Skill("deploy", "deploy", "Deploy the app", SkillSource.PROJECT, true, true, "/skills/deploy.md", + null, null); var result = new SessionSkillsListResult(List.of(item)); assertEquals(1, result.skills().size()); assertEquals("deploy", result.skills().get(0).name()); + assertEquals("deploy", result.skills().get(0).commandName()); assertEquals(SkillSource.PROJECT, result.skills().get(0).source()); assertTrue(result.skills().get(0).enabled()); } @@ -806,7 +817,7 @@ void modelsListResult_nested() { var policy = new ModelPolicy(ModelPolicyState.ENABLED, null); var promo = new ModelBillingPromo("summer-2026", 25.0, "2026-08-01T00:00:00Z", "Summer discount"); var billing = new ModelBilling(1.0, null, null, promo); - var modelItem = new Model("gpt-5", "GPT-5", capabilities, policy, billing, null, null, null, null); + var modelItem = new Model("gpt-5", "GPT-5", capabilities, policy, billing, null, null, null); var result = new ModelsListResult(List.of(modelItem)); assertEquals(1, result.models().size()); @@ -842,7 +853,7 @@ void sessionModelSwitchToParams_nested_records() { var limits = new ModelCapabilitiesOverrideLimits(100000L, 8192L, 128000L, limitsVision); var supports = new ModelCapabilitiesOverrideSupports(true, true, null); var capabilities = new ModelCapabilitiesOverride(supports, limits); - var params = new SessionModelSwitchToParams("sess-m", "gpt-5", null, null, null, capabilities, null); + var params = new SessionModelSwitchToParams("sess-m", "gpt-5", null, null, null, capabilities, null, null); assertEquals("gpt-5", params.modelId()); assertNotNull(params.modelCapabilities()); diff --git a/java/src/test/java/com/github/copilot/rpc/ParamCoercionTest.java b/java/sdk/src/test/java/com/github/copilot/rpc/ParamCoercionTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/rpc/ParamCoercionTest.java rename to java/sdk/src/test/java/com/github/copilot/rpc/ParamCoercionTest.java diff --git a/java/src/test/java/com/github/copilot/rpc/ParamSchemaTest.java b/java/sdk/src/test/java/com/github/copilot/rpc/ParamSchemaTest.java similarity index 80% rename from java/src/test/java/com/github/copilot/rpc/ParamSchemaTest.java rename to java/sdk/src/test/java/com/github/copilot/rpc/ParamSchemaTest.java index 27d76a91a..5aea4471d 100644 --- a/java/src/test/java/com/github/copilot/rpc/ParamSchemaTest.java +++ b/java/sdk/src/test/java/com/github/copilot/rpc/ParamSchemaTest.java @@ -5,10 +5,12 @@ package com.github.copilot.rpc; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.math.BigDecimal; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; @@ -167,6 +169,89 @@ void buildSchema_multipleParams_orderPreservedInProperties() { assertEquals(List.of("alpha", "beta", "gamma"), keys); } + // ── buildSchema: schema override ─────────────────────────────────────────── + + @Test + void buildSchema_withSchemaOverride_usesExplicitSchema() { + Param p = Param.of(String.class, "when", "Meeting time") + .schema("{\"type\":\"string\",\"format\":\"date-time\"}"); + Map schema = ParamSchema.buildSchema("schedule", MAPPER, p); + @SuppressWarnings("unchecked") + Map props = (Map) schema.get("properties"); + @SuppressWarnings("unchecked") + Map whenSchema = (Map) props.get("when"); + assertEquals("string", whenSchema.get("type")); + assertEquals("date-time", whenSchema.get("format")); + } + + @Test + void buildSchema_withSchemaOverride_preservesDescription() { + Param p = Param.of(String.class, "when", "Meeting time") + .schema("{\"type\":\"string\",\"format\":\"date-time\"}"); + Map schema = ParamSchema.buildSchema("schedule", MAPPER, p); + @SuppressWarnings("unchecked") + Map props = (Map) schema.get("properties"); + @SuppressWarnings("unchecked") + Map whenSchema = (Map) props.get("when"); + assertEquals("Meeting time", whenSchema.get("description")); + } + + @Test + void buildSchema_withSchemaOverride_respectsRequired() { + Param p = Param.of(String.class, "when", "Meeting time") + .schema("{\"type\":\"string\",\"format\":\"date-time\"}"); + Map schema = ParamSchema.buildSchema("schedule", MAPPER, p); + @SuppressWarnings("unchecked") + List required = (List) schema.get("required"); + assertTrue(required.contains("when")); + } + + @Test + void buildSchema_mixedParams_overrideAndAuto() { + Param pOverride = Param.of(String.class, "when", "Meeting time") + .schema("{\"type\":\"string\",\"format\":\"date-time\"}"); + Param pAuto = Param.of(String.class, "title", "Meeting title"); + Map schema = ParamSchema.buildSchema("schedule", MAPPER, pOverride, pAuto); + @SuppressWarnings("unchecked") + Map props = (Map) schema.get("properties"); + + @SuppressWarnings("unchecked") + Map whenSchema = (Map) props.get("when"); + assertEquals("date-time", whenSchema.get("format")); + + @SuppressWarnings("unchecked") + Map titleSchema = (Map) props.get("title"); + assertEquals("string", titleSchema.get("type")); + // Auto-generated should NOT have format + assertFalse(titleSchema.containsKey("format")); + } + + @Test + void buildSchema_withSchemaOverride_rejectsTrailingJson() { + Param param = Param.of(String.class, "when", "Meeting time") + .schema("{\"type\":\"string\"} {\"type\":\"integer\"}"); + + IllegalArgumentException error = assertThrows(IllegalArgumentException.class, + () -> ParamSchema.buildSchema("schedule", MAPPER, param)); + + assertTrue(error.getMessage().contains("Invalid schema JSON")); + } + + @Test + void buildSchema_withSchemaOverride_preservesDecimalPrecision() { + Param param = Param.of(String.class, "value", "Precise value") + .schema("{\"type\":\"number\",\"maximum\":1e400,\"multipleOf\":0.12345678901234567890}"); + + Map schema = ParamSchema.buildSchema("calculate", MAPPER, param); + @SuppressWarnings("unchecked") + Map properties = (Map) schema.get("properties"); + @SuppressWarnings("unchecked") + Map valueSchema = (Map) properties.get("value"); + + assertEquals(new BigDecimal("1e400"), valueSchema.get("maximum")); + assertEquals(new BigDecimal("0.12345678901234567890"), valueSchema.get("multipleOf")); + } + // ── forType: primitive and boxed integer types ─────────────────────────────── @Test diff --git a/java/sdk/src/test/java/com/github/copilot/rpc/PermissionRequestResultDecisionContextTest.java b/java/sdk/src/test/java/com/github/copilot/rpc/PermissionRequestResultDecisionContextTest.java new file mode 100644 index 000000000..395ad50ad --- /dev/null +++ b/java/sdk/src/test/java/com/github/copilot/rpc/PermissionRequestResultDecisionContextTest.java @@ -0,0 +1,97 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package com.github.copilot.rpc; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.copilot.generated.rpc.PermissionDecisionContext; +import com.github.copilot.generated.rpc.PermissionDecisionOutcome; +import com.github.copilot.generated.rpc.PermissionDecisionSource; +import com.github.copilot.generated.rpc.PermissionDecisionSurface; +import com.github.copilot.generated.rpc.SessionPermissionsHandlePendingPermissionRequestParams; +import org.junit.jupiter.api.Test; + +/** + * Verifies that {@link PermissionRequestResult} carries an optional + * {@link PermissionDecisionContext} as a sibling of {@code result} — never + * nested inside the serialized result — when the SDK forwards a permission + * response to the runtime. + */ +class PermissionRequestResultDecisionContextTest { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + private static PermissionDecisionContext sampleContext() { + return new PermissionDecisionContext(PermissionDecisionOutcome.AUTO_APPROVED, + PermissionDecisionSource.HOST_POLICY, PermissionDecisionSurface.SDK); + } + + @Test + void setDecisionContextForwardsContextAsSiblingOfResult() throws Exception { + var result = PermissionRequestResult.approveOnce().setDecisionContext(sampleContext()); + var params = new SessionPermissionsHandlePendingPermissionRequestParams("session-1", "req-1", result, + result.getDecisionContext()); + + JsonNode json = MAPPER.valueToTree(params); + + assertTrue(json.has("decisionContext"), "decisionContext must be a top-level sibling of result"); + assertEquals("host_policy", json.get("decisionContext").get("source").asText()); + assertEquals("auto_approved", json.get("decisionContext").get("outcome").asText()); + assertEquals("sdk", json.get("decisionContext").get("surface").asText()); + assertFalse(json.get("result").has("decisionContext"), "decisionContext must NOT be nested inside result"); + } + + @Test + void withoutContextOmitsDecisionContextKey() throws Exception { + var result = PermissionRequestResult.approveOnce(); + assertNull(result.getDecisionContext()); + + var params = new SessionPermissionsHandlePendingPermissionRequestParams("session-1", "req-1", result, + result.getDecisionContext()); + + JsonNode json = MAPPER.valueToTree(params); + + // Generated params record is @JsonInclude(NON_NULL), so a null + // decisionContext is omitted entirely — byte-identical to legacy behavior. + assertFalse(json.has("decisionContext"), "decisionContext key must be absent when no context is supplied"); + } + + @Test + void setDecisionContextTwiceReplacesRatherThanNests() { + var first = sampleContext(); + var second = new PermissionDecisionContext(PermissionDecisionOutcome.PROMPTED_USER, + PermissionDecisionSource.HUMAN_RESPONSE, PermissionDecisionSurface.TUI); + + var result = PermissionRequestResult.approveOnce().setDecisionContext(first).setDecisionContext(second); + + assertSame(second, result.getDecisionContext(), "second setDecisionContext must replace the first, not nest"); + } + + @Test + void serializingResultWithContextDoesNotEmitContextInsideResult() throws Exception { + var result = PermissionRequestResult.approveOnce().setDecisionContext(sampleContext()); + + JsonNode resultJson = MAPPER.valueToTree(result); + + assertFalse(resultJson.has("decisionContext"), + "@JsonIgnore must keep decisionContext out of the serialized result"); + assertEquals(PermissionRequestResultKind.APPROVED.getValue(), resultJson.get("kind").asText()); + } + + @Test + void setDecisionContextAcceptsNullAsNoContext() { + var result = PermissionRequestResult.approveOnce().setDecisionContext(sampleContext()); + + result.setDecisionContext(null); + + assertNull(result.getDecisionContext(), "null must clear the context rather than throwing"); + } +} diff --git a/java/src/test/java/com/github/copilot/rpc/RecordInvocationArgs.java b/java/sdk/src/test/java/com/github/copilot/rpc/RecordInvocationArgs.java similarity index 100% rename from java/src/test/java/com/github/copilot/rpc/RecordInvocationArgs.java rename to java/sdk/src/test/java/com/github/copilot/rpc/RecordInvocationArgs.java diff --git a/java/src/test/java/com/github/copilot/rpc/ToolDefinitionFromObjectTest.java b/java/sdk/src/test/java/com/github/copilot/rpc/ToolDefinitionFromObjectTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/rpc/ToolDefinitionFromObjectTest.java rename to java/sdk/src/test/java/com/github/copilot/rpc/ToolDefinitionFromObjectTest.java diff --git a/java/sdk/src/test/java/com/github/copilot/rpc/ToolDefinitionIsTerminalTest.java b/java/sdk/src/test/java/com/github/copilot/rpc/ToolDefinitionIsTerminalTest.java new file mode 100644 index 000000000..850dfa251 --- /dev/null +++ b/java/sdk/src/test/java/com/github/copilot/rpc/ToolDefinitionIsTerminalTest.java @@ -0,0 +1,51 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ +package com.github.copilot.rpc; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.Map; +import org.junit.jupiter.api.Test; + +/** Wire-level coverage for {@link ToolDefinition#isTerminal()}. */ +class ToolDefinitionIsTerminalTest { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + @Test + void isTerminalSerializesAsCamelCaseWhenSet() throws Exception { + ToolDefinition definition = new ToolDefinition("clear_context", "Clear the conversation", + Map.of("type", "object"), null, null, null, null, null, true); + + JsonNode node = MAPPER.valueToTree(definition); + + assertTrue(node.has("isTerminal"), "isTerminal should be serialized"); + assertTrue(node.get("isTerminal").asBoolean(), "isTerminal should be true"); + } + + @Test + void isTerminalIsOmittedWhenNull() throws Exception { + ToolDefinition definition = new ToolDefinition("plain", "A plain tool", Map.of("type", "object"), null, null, + null, null, null, null); + + JsonNode node = MAPPER.valueToTree(definition); + + assertFalse(node.has("isTerminal"), "isTerminal should be omitted when null"); + } + + @Test + void sevenArgumentConstructorStillCompilesAndLeavesTerminalityUnset() throws Exception { + // Guards source compatibility for call sites written before isTerminal + // was added as a record component. + ToolDefinition definition = new ToolDefinition("legacy", "Legacy call site", Map.of("type", "object"), null, + null, null, null); + + assertEquals(null, definition.isTerminal()); + assertFalse(MAPPER.valueToTree(definition).has("isTerminal")); + } +} diff --git a/java/src/test/java/com/github/copilot/rpc/ToolDefinitionLambdaTest.java b/java/sdk/src/test/java/com/github/copilot/rpc/ToolDefinitionLambdaTest.java similarity index 96% rename from java/src/test/java/com/github/copilot/rpc/ToolDefinitionLambdaTest.java rename to java/sdk/src/test/java/com/github/copilot/rpc/ToolDefinitionLambdaTest.java index 7f9ccaaba..75752c67a 100644 --- a/java/src/test/java/com/github/copilot/rpc/ToolDefinitionLambdaTest.java +++ b/java/sdk/src/test/java/com/github/copilot/rpc/ToolDefinitionLambdaTest.java @@ -48,6 +48,9 @@ @AllowCopilotExperimental class ToolDefinitionLambdaTest { + private record CustomDateTime(String value) { + } + // ── Helpers ────────────────────────────────────────────────────────────────── private static ToolInvocation invocationOf(Map args) { @@ -485,6 +488,23 @@ void schema_oneArg_hasCorrectTypeForString() { assertEquals("Search query", querySchema.get("description")); } + @Test + void schema_oneArg_customTypeUsesExplicitSchemaAndCoercion() throws Exception { + Param p = Param.of(CustomDateTime.class, "when", "Meeting time") + .schema("{\"type\":\"object\",\"properties\":{\"value\":{\"type\":\"string\"}}}"); + ToolDefinition tool = ToolDefinition.from("schedule", "Schedules a meeting", p, + when -> "scheduled " + when.value()); + @SuppressWarnings("unchecked") + Map whenSchema = (Map) propertiesOf(tool).get("when"); + assertNotNull(whenSchema); + assertEquals("object", whenSchema.get("type")); + assertEquals("Meeting time", whenSchema.get("description")); + ObjectNode arguments = JsonNodeFactory.instance.objectNode(); + arguments.putObject("when").put("value", "2026-07-23T21:00:00Z"); + Object result = tool.handler().invoke(new ToolInvocation().setArguments(arguments)).get(); + assertEquals("scheduled 2026-07-23T21:00:00Z", result); + } + @Test void schema_oneArg_hasCorrectTypeForInteger() { Param p = Param.of(Integer.class, "count", "Item count"); diff --git a/java/src/test/java/com/github/copilot/rpc/fixtures/ArgCoercionTools$$CopilotToolMeta.java b/java/sdk/src/test/java/com/github/copilot/rpc/fixtures/ArgCoercionTools$$CopilotToolMeta.java similarity index 100% rename from java/src/test/java/com/github/copilot/rpc/fixtures/ArgCoercionTools$$CopilotToolMeta.java rename to java/sdk/src/test/java/com/github/copilot/rpc/fixtures/ArgCoercionTools$$CopilotToolMeta.java diff --git a/java/src/test/java/com/github/copilot/rpc/fixtures/ArgCoercionTools.java b/java/sdk/src/test/java/com/github/copilot/rpc/fixtures/ArgCoercionTools.java similarity index 100% rename from java/src/test/java/com/github/copilot/rpc/fixtures/ArgCoercionTools.java rename to java/sdk/src/test/java/com/github/copilot/rpc/fixtures/ArgCoercionTools.java diff --git a/java/src/test/java/com/github/copilot/rpc/fixtures/DateTimeTools$$CopilotToolMeta.java b/java/sdk/src/test/java/com/github/copilot/rpc/fixtures/DateTimeTools$$CopilotToolMeta.java similarity index 100% rename from java/src/test/java/com/github/copilot/rpc/fixtures/DateTimeTools$$CopilotToolMeta.java rename to java/sdk/src/test/java/com/github/copilot/rpc/fixtures/DateTimeTools$$CopilotToolMeta.java diff --git a/java/src/test/java/com/github/copilot/rpc/fixtures/DateTimeTools.java b/java/sdk/src/test/java/com/github/copilot/rpc/fixtures/DateTimeTools.java similarity index 100% rename from java/src/test/java/com/github/copilot/rpc/fixtures/DateTimeTools.java rename to java/sdk/src/test/java/com/github/copilot/rpc/fixtures/DateTimeTools.java diff --git a/java/src/test/java/com/github/copilot/rpc/fixtures/DefaultValueTools$$CopilotToolMeta.java b/java/sdk/src/test/java/com/github/copilot/rpc/fixtures/DefaultValueTools$$CopilotToolMeta.java similarity index 100% rename from java/src/test/java/com/github/copilot/rpc/fixtures/DefaultValueTools$$CopilotToolMeta.java rename to java/sdk/src/test/java/com/github/copilot/rpc/fixtures/DefaultValueTools$$CopilotToolMeta.java diff --git a/java/src/test/java/com/github/copilot/rpc/fixtures/DefaultValueTools.java b/java/sdk/src/test/java/com/github/copilot/rpc/fixtures/DefaultValueTools.java similarity index 100% rename from java/src/test/java/com/github/copilot/rpc/fixtures/DefaultValueTools.java rename to java/sdk/src/test/java/com/github/copilot/rpc/fixtures/DefaultValueTools.java diff --git a/java/src/test/java/com/github/copilot/rpc/fixtures/InvocationAwareTools$$CopilotToolMeta.java b/java/sdk/src/test/java/com/github/copilot/rpc/fixtures/InvocationAwareTools$$CopilotToolMeta.java similarity index 100% rename from java/src/test/java/com/github/copilot/rpc/fixtures/InvocationAwareTools$$CopilotToolMeta.java rename to java/sdk/src/test/java/com/github/copilot/rpc/fixtures/InvocationAwareTools$$CopilotToolMeta.java diff --git a/java/src/test/java/com/github/copilot/rpc/fixtures/InvocationAwareTools.java b/java/sdk/src/test/java/com/github/copilot/rpc/fixtures/InvocationAwareTools.java similarity index 100% rename from java/src/test/java/com/github/copilot/rpc/fixtures/InvocationAwareTools.java rename to java/sdk/src/test/java/com/github/copilot/rpc/fixtures/InvocationAwareTools.java diff --git a/java/src/test/java/com/github/copilot/rpc/fixtures/MultiReturnTools$$CopilotToolMeta.java b/java/sdk/src/test/java/com/github/copilot/rpc/fixtures/MultiReturnTools$$CopilotToolMeta.java similarity index 100% rename from java/src/test/java/com/github/copilot/rpc/fixtures/MultiReturnTools$$CopilotToolMeta.java rename to java/sdk/src/test/java/com/github/copilot/rpc/fixtures/MultiReturnTools$$CopilotToolMeta.java diff --git a/java/src/test/java/com/github/copilot/rpc/fixtures/MultiReturnTools.java b/java/sdk/src/test/java/com/github/copilot/rpc/fixtures/MultiReturnTools.java similarity index 100% rename from java/src/test/java/com/github/copilot/rpc/fixtures/MultiReturnTools.java rename to java/sdk/src/test/java/com/github/copilot/rpc/fixtures/MultiReturnTools.java diff --git a/java/src/test/java/com/github/copilot/rpc/fixtures/OptionalParamTools$$CopilotToolMeta.java b/java/sdk/src/test/java/com/github/copilot/rpc/fixtures/OptionalParamTools$$CopilotToolMeta.java similarity index 100% rename from java/src/test/java/com/github/copilot/rpc/fixtures/OptionalParamTools$$CopilotToolMeta.java rename to java/sdk/src/test/java/com/github/copilot/rpc/fixtures/OptionalParamTools$$CopilotToolMeta.java diff --git a/java/src/test/java/com/github/copilot/rpc/fixtures/OptionalParamTools.java b/java/sdk/src/test/java/com/github/copilot/rpc/fixtures/OptionalParamTools.java similarity index 100% rename from java/src/test/java/com/github/copilot/rpc/fixtures/OptionalParamTools.java rename to java/sdk/src/test/java/com/github/copilot/rpc/fixtures/OptionalParamTools.java diff --git a/java/src/test/java/com/github/copilot/rpc/fixtures/OverrideTools$$CopilotToolMeta.java b/java/sdk/src/test/java/com/github/copilot/rpc/fixtures/OverrideTools$$CopilotToolMeta.java similarity index 100% rename from java/src/test/java/com/github/copilot/rpc/fixtures/OverrideTools$$CopilotToolMeta.java rename to java/sdk/src/test/java/com/github/copilot/rpc/fixtures/OverrideTools$$CopilotToolMeta.java diff --git a/java/src/test/java/com/github/copilot/rpc/fixtures/OverrideTools.java b/java/sdk/src/test/java/com/github/copilot/rpc/fixtures/OverrideTools.java similarity index 100% rename from java/src/test/java/com/github/copilot/rpc/fixtures/OverrideTools.java rename to java/sdk/src/test/java/com/github/copilot/rpc/fixtures/OverrideTools.java diff --git a/java/src/test/java/com/github/copilot/rpc/fixtures/SimpleTools$$CopilotToolMeta.java b/java/sdk/src/test/java/com/github/copilot/rpc/fixtures/SimpleTools$$CopilotToolMeta.java similarity index 100% rename from java/src/test/java/com/github/copilot/rpc/fixtures/SimpleTools$$CopilotToolMeta.java rename to java/sdk/src/test/java/com/github/copilot/rpc/fixtures/SimpleTools$$CopilotToolMeta.java diff --git a/java/src/test/java/com/github/copilot/rpc/fixtures/SimpleTools.java b/java/sdk/src/test/java/com/github/copilot/rpc/fixtures/SimpleTools.java similarity index 100% rename from java/src/test/java/com/github/copilot/rpc/fixtures/SimpleTools.java rename to java/sdk/src/test/java/com/github/copilot/rpc/fixtures/SimpleTools.java diff --git a/java/src/test/java/com/github/copilot/rpc/fixtures/StaticInvocationTools$$CopilotToolMeta.java b/java/sdk/src/test/java/com/github/copilot/rpc/fixtures/StaticInvocationTools$$CopilotToolMeta.java similarity index 100% rename from java/src/test/java/com/github/copilot/rpc/fixtures/StaticInvocationTools$$CopilotToolMeta.java rename to java/sdk/src/test/java/com/github/copilot/rpc/fixtures/StaticInvocationTools$$CopilotToolMeta.java diff --git a/java/src/test/java/com/github/copilot/rpc/fixtures/StaticInvocationTools.java b/java/sdk/src/test/java/com/github/copilot/rpc/fixtures/StaticInvocationTools.java similarity index 100% rename from java/src/test/java/com/github/copilot/rpc/fixtures/StaticInvocationTools.java rename to java/sdk/src/test/java/com/github/copilot/rpc/fixtures/StaticInvocationTools.java diff --git a/java/src/test/java/com/github/copilot/rpc/fixtures/StaticTools$$CopilotToolMeta.java b/java/sdk/src/test/java/com/github/copilot/rpc/fixtures/StaticTools$$CopilotToolMeta.java similarity index 100% rename from java/src/test/java/com/github/copilot/rpc/fixtures/StaticTools$$CopilotToolMeta.java rename to java/sdk/src/test/java/com/github/copilot/rpc/fixtures/StaticTools$$CopilotToolMeta.java diff --git a/java/src/test/java/com/github/copilot/rpc/fixtures/StaticTools.java b/java/sdk/src/test/java/com/github/copilot/rpc/fixtures/StaticTools.java similarity index 100% rename from java/src/test/java/com/github/copilot/rpc/fixtures/StaticTools.java rename to java/sdk/src/test/java/com/github/copilot/rpc/fixtures/StaticTools.java diff --git a/java/src/test/java/com/github/copilot/tool/CopilotToolAnnotationTest.java b/java/sdk/src/test/java/com/github/copilot/tool/CopilotToolAnnotationTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/tool/CopilotToolAnnotationTest.java rename to java/sdk/src/test/java/com/github/copilot/tool/CopilotToolAnnotationTest.java diff --git a/java/src/test/java/com/github/copilot/tool/CopilotToolProcessorTest.java b/java/sdk/src/test/java/com/github/copilot/tool/CopilotToolProcessorTest.java similarity index 76% rename from java/src/test/java/com/github/copilot/tool/CopilotToolProcessorTest.java rename to java/sdk/src/test/java/com/github/copilot/tool/CopilotToolProcessorTest.java index 574d3acaa..e7012c644 100644 --- a/java/src/test/java/com/github/copilot/tool/CopilotToolProcessorTest.java +++ b/java/sdk/src/test/java/com/github/copilot/tool/CopilotToolProcessorTest.java @@ -7,6 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; @@ -14,6 +15,7 @@ import java.io.IOException; import java.io.Writer; import java.net.URI; +import java.net.URLClassLoader; import java.nio.file.Path; import java.security.CodeSource; import java.util.ArrayList; @@ -38,6 +40,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import com.github.copilot.rpc.ToolDefinition; +import com.github.copilot.rpc.ToolInvocation; + /** * Tests that {@link CopilotToolProcessor} correctly generates * {@code $$CopilotToolMeta} companion classes and emits compile errors for @@ -168,6 +173,30 @@ public String search(@CopilotToolParam(defaultValue = "fallback") SearchArgs req "Expected compile error for single-record wrapper defaultValue, got: " + result.diagnostics); } + @Test + void emitsError_forSingleRecordWrapperSchemaWithoutUnsupportedGuidance() { + String source = """ + package test; + import com.github.copilot.tool.CopilotTool; + import com.github.copilot.tool.CopilotToolParam; + public class SingleRecordSchemaTools { + public record SearchArgs(String query, int limit) {} + @CopilotTool("Single record") + public String search(@CopilotToolParam(schema = "{\\"type\\":\\"object\\"}") SearchArgs req) { + return req.query(); + } + } + """; + + CompilationResult result = compileWithProcessor( + List.of(inMemorySource("test.SingleRecordSchemaTools", source))); + + assertTrue(hasErrorContaining(result, "schema=...) is not supported on single-record tool parameters"), + "Expected unsupported schema diagnostic, got: " + result.diagnostics); + assertFalse(hasErrorContaining(result, "annotate record components"), + "Diagnostic must not recommend unsupported record-component annotations: " + result.diagnostics); + } + @Test void emitsError_forSingleRecordWrapperMetadataOverrides() { String source = """ @@ -189,6 +218,335 @@ public String search(@CopilotToolParam(value = "Search input", required = false, "Expected compile error for single-record wrapper metadata overrides, got: " + result.diagnostics); } + // ── Test: @CopilotToolParam schema override ───────────────────────────────── + + @Test + void generatesCorrectSchema_forExplicitSchemaOverride() { + String source = """ + package test; + import com.github.copilot.tool.CopilotTool; + import com.github.copilot.tool.CopilotToolParam; + public class SchemaOverrideTools { + @CopilotTool("Schedule meeting") + public String schedule( + @CopilotToolParam(value = "When to meet", + schema = "{\\"type\\":\\"string\\",\\"format\\":\\"date-time\\"}") String when) { + return "scheduled " + when; + } + } + """; + + CompilationResult result = compileWithProcessor(List.of(inMemorySource("test.SchemaOverrideTools", source))); + + assertNoErrors(result); + assertTrue(result.generatedSources.stream().anyMatch(s -> s.contains("date-time")), + "Expected generated code to contain the custom schema format, got: " + result.generatedSources); + } + + @Test + void generatedSchemaOverride_supportsCustomTypeHandlerInvocation() throws Exception { + String source = """ + package test; + import com.github.copilot.tool.CopilotTool; + import com.github.copilot.tool.CopilotToolParam; + public class AnnotationSchemaTools { + public static class CustomDateTime { + public String value; + } + @CopilotTool("Schedule meeting") + public String schedule(@CopilotToolParam(value = "Meeting time", + schema = "{\\"type\\":\\"object\\",\\"properties\\":{\\"value\\":{\\"type\\":\\"string\\"}}}") CustomDateTime when) { + return "scheduled " + when.value; + } + } + """; + + CompilationResult compilation = compileWithProcessor( + List.of(inMemorySource("test.AnnotationSchemaTools", source))); + assertNoErrors(compilation); + + try (URLClassLoader loader = new URLClassLoader(new java.net.URL[]{compilation.outputDir.toUri().toURL()}, + getClass().getClassLoader())) { + Class toolsClass = loader.loadClass("test.AnnotationSchemaTools"); + Object tools = toolsClass.getConstructor().newInstance(); + Class providerClass = loader.loadClass("test.AnnotationSchemaTools$$CopilotToolMeta"); + @SuppressWarnings("unchecked") + CopilotToolMetadataProvider provider = (CopilotToolMetadataProvider) providerClass + .getConstructor().newInstance(); + ToolDefinition tool = provider.definitions(tools, new com.fasterxml.jackson.databind.ObjectMapper()).get(0); + + @SuppressWarnings("unchecked") + Map schema = (Map) tool.parameters(); + @SuppressWarnings("unchecked") + Map properties = (Map) schema.get("properties"); + @SuppressWarnings("unchecked") + Map whenSchema = (Map) properties.get("when"); + assertEquals("object", whenSchema.get("type")); + + var arguments = com.fasterxml.jackson.databind.node.JsonNodeFactory.instance.objectNode(); + arguments.putObject("when").put("value", "2026-07-23T22:00:00Z"); + Object result = tool.handler().invoke(new ToolInvocation().setArguments(arguments)).get(); + assertEquals("scheduled 2026-07-23T22:00:00Z", result); + } + } + + @Test + void emitsError_forSchemaWithDefaultValue() { + String source = """ + package test; + import com.github.copilot.tool.CopilotTool; + import com.github.copilot.tool.CopilotToolParam; + public class SchemaDefaultConflict { + @CopilotTool("Do something") + public String doIt( + @CopilotToolParam(value = "Input", + schema = "{\\"type\\":\\"string\\"}", + defaultValue = "hello") String input) { + return input; + } + } + """; + + CompilationResult result = compileWithProcessor(List.of(inMemorySource("test.SchemaDefaultConflict", source))); + + assertTrue(hasErrorContaining(result, "schema and defaultValue"), + "Expected compile error for schema + defaultValue conflict, got: " + result.diagnostics); + } + + @Test + void emitsError_forInvalidSchemaJson() { + String source = """ + package test; + import com.github.copilot.tool.CopilotTool; + import com.github.copilot.tool.CopilotToolParam; + public class InvalidSchemaTools { + @CopilotTool("Do something") + public String doIt( + @CopilotToolParam(value = "Input", schema = "not json") String input) { + return input; + } + } + """; + + CompilationResult result = compileWithProcessor(List.of(inMemorySource("test.InvalidSchemaTools", source))); + + assertTrue(hasErrorContaining(result, "valid JSON object string"), + "Expected compile error for invalid schema JSON, got: " + result.diagnostics); + } + + @Test + void emitsError_forUnrepresentableSchemaNumber() { + String source = """ + package test; + import com.github.copilot.tool.CopilotTool; + import com.github.copilot.tool.CopilotToolParam; + public class UnrepresentableSchemaNumberTools { + @CopilotTool("Do something") + public String doIt( + @CopilotToolParam(value = "Input", schema = "{\\"maximum\\":1e9999999999}") String input) { + return input; + } + } + """; + + CompilationResult result = compileWithProcessor( + List.of(inMemorySource("test.UnrepresentableSchemaNumberTools", source))); + + assertTrue(hasErrorContaining(result, "Number cannot be represented"), + "Expected compile error for unrepresentable schema number, got: " + result.diagnostics); + } + + @Test + void compilesSuccessfully_forEmptySchemaFallsThrough() { + String source = """ + package test; + import com.github.copilot.tool.CopilotTool; + import com.github.copilot.tool.CopilotToolParam; + public class EmptySchemaTools { + @CopilotTool("Search") + public String search(@CopilotToolParam(value = "Query", schema = "") String query) { + return query; + } + } + """; + + CompilationResult result = compileWithProcessor(List.of(inMemorySource("test.EmptySchemaTools", source))); + + assertNoErrors(result); + } + + @Test + void generatesSchemaOverride_withLargeObjectsNullAndNumbers() { + String source = """ + package test; + import com.github.copilot.tool.CopilotTool; + import com.github.copilot.tool.CopilotToolParam; + public class ComplexSchemaTools { + @CopilotTool("Complex schema") + public String useSchema(@CopilotToolParam(value = "Input", + schema = "{\\"type\\":\\"object\\",\\"const\\":null,\\"enum\\":[\\"x\\",null],\\"minimum\\":2147483648,\\"k1\\":true,\\"k2\\":true,\\"k3\\":true,\\"k4\\":true,\\"k5\\":true,\\"k6\\":true,\\"k7\\":true}") String input) { + return input; + } + } + """; + + CompilationResult result = compileWithProcessor(List.of(inMemorySource("test.ComplexSchemaTools", source))); + + assertNoErrors(result); + String generated = result.getGeneratedSource("test.ComplexSchemaTools$$CopilotToolMeta"); + assertTrue(generated.contains("mapOfNullable("), "Expected arity-independent map helper, got:\n" + generated); + assertTrue(generated.contains("new java.math.BigDecimal(\"2147483648\")"), + "Expected safe numeric source, got:\n" + generated); + assertTrue(generated.contains("\"const\", (Object) null"), "Expected null schema value, got:\n" + generated); + assertTrue(generated.contains("listOfNullable(\"x\", (Object) null)"), + "Expected null-tolerant list helper, got:\n" + generated); + } + + @Test + void jsonToMapOfSource_decodesEscapesAndRejectsMalformedJson() { + String generated = CopilotToolProcessor.jsonToMapOfSource("{\"title\":\"line\\n\\u0061\"}"); + + assertTrue(generated.contains("\"title\", \"line\\na\""), "Expected decoded JSON escapes, got: " + generated); + IllegalArgumentException escapeError = assertThrows(IllegalArgumentException.class, + () -> CopilotToolProcessor.jsonToMapOfSource("{\"title\":\"\\q\"}")); + assertTrue(escapeError.getMessage().contains("Invalid escape sequence")); + IllegalArgumentException numberError = assertThrows(IllegalArgumentException.class, + () -> CopilotToolProcessor.jsonToMapOfSource("{\"minimum\":1.}")); + assertTrue(numberError.getMessage().contains("Expected digit in number fraction")); + assertThrows(IllegalArgumentException.class, + () -> CopilotToolProcessor.jsonToMapOfSource("{\"minimum\":1\u0662}")); + assertThrows(IllegalArgumentException.class, + () -> CopilotToolProcessor.jsonToMapOfSource("{\f\"type\":\"string\"}")); + assertEquals("mapOfNullable(\"enum\", listOfNullable((Object) null))", + CopilotToolProcessor.jsonToMapOfSource("{\"enum\":[null]}")); + assertThrows(IllegalArgumentException.class, + () -> CopilotToolProcessor.jsonToMapOfSource("{\"title\":\"\\" + "u١٢٣٤\"}")); + } + + @Test + void generatesSchemaOverride_withEscapedControlCharacters() { + String source = """ + package test; + import com.github.copilot.tool.CopilotTool; + import com.github.copilot.tool.CopilotToolParam; + public class EscapedControlSchemaTools { + @CopilotTool("Escaped control schema") + public String useSchema(@CopilotToolParam(value = "Input", + schema = "{\\"title\\":\\"\\\\b\\\\fUNICODE_ESCAPE\\"}") String input) { + return input; + } + } + """.replace("UNICODE_ESCAPE", "\\\\" + "u0000"); + + CompilationResult result = compileWithProcessor( + List.of(inMemorySource("test.EscapedControlSchemaTools", source))); + + assertNoErrors(result); + String generated = result.getGeneratedSource("test.EscapedControlSchemaTools$$CopilotToolMeta"); + assertTrue(generated.contains("\\b\\f\\000"), + "Expected Java-safe control escapes in generated source, got:\n" + generated); + } + + // ── Test: Blank @CopilotToolParam description validation ──────────────────── + + @Test + void emitsError_forBlankParamDescription() { + String source = """ + package test; + import com.github.copilot.tool.CopilotTool; + import com.github.copilot.tool.CopilotToolParam; + public class BlankDescTools { + @CopilotTool("Search for items") + public String searchItems(@CopilotToolParam("") String query) { + return "results for " + query; + } + } + """; + + CompilationResult result = compileWithProcessor(List.of(inMemorySource("test.BlankDescTools", source))); + + assertTrue(hasErrorContaining(result, "blank value (description)"), + "Expected compile error for blank @CopilotToolParam description, got: " + result.diagnostics); + } + + @Test + void emitsError_forWhitespaceOnlyParamDescription() { + String source = """ + package test; + import com.github.copilot.tool.CopilotTool; + import com.github.copilot.tool.CopilotToolParam; + public class WhitespaceDescTools { + @CopilotTool("Search for items") + public String searchItems(@CopilotToolParam(" ") String query) { + return "results for " + query; + } + } + """; + + CompilationResult result = compileWithProcessor(List.of(inMemorySource("test.WhitespaceDescTools", source))); + + assertTrue(hasErrorContaining(result, "blank value (description)"), + "Expected compile error for whitespace-only @CopilotToolParam description, got: " + result.diagnostics); + } + + @Test + void compilesSuccessfully_forValidParamDescription() { + String source = """ + package test; + import com.github.copilot.tool.CopilotTool; + import com.github.copilot.tool.CopilotToolParam; + public class ValidDescTools { + @CopilotTool("Search for items") + public String searchItems(@CopilotToolParam("Search query") String query) { + return "results for " + query; + } + } + """; + + CompilationResult result = compileWithProcessor(List.of(inMemorySource("test.ValidDescTools", source))); + + assertNoErrors(result); + } + + @Test + void compilesSuccessfully_forParamWithoutAnnotation() { + String source = """ + package test; + import com.github.copilot.tool.CopilotTool; + public class NoAnnotationTools { + @CopilotTool("Search for items") + public String searchItems(String query) { + return "results for " + query; + } + } + """; + + CompilationResult result = compileWithProcessor(List.of(inMemorySource("test.NoAnnotationTools", source))); + + assertNoErrors(result); + } + + @Test + void doesNotEmitBlankError_forSingleRecordWrapperWithDefaultAnnotation() { + String source = """ + package test; + import com.github.copilot.tool.CopilotTool; + import com.github.copilot.tool.CopilotToolParam; + public class RecordWrapperTools { + public record SearchArgs(String query, int limit) {} + @CopilotTool("Search for items") + public String search(@CopilotToolParam SearchArgs args) { + return args.query(); + } + } + """; + + CompilationResult result = compileWithProcessor(List.of(inMemorySource("test.RecordWrapperTools", source))); + + assertFalse(hasErrorContaining(result, "blank value (description)"), + "Single-record wrapper should be exempt from blank description check, got: " + result.diagnostics); + } + // ── Test: Return type handling ────────────────────────────────────────────── @Test @@ -261,8 +619,9 @@ public String doSomething(@CopilotToolParam("Input") String input) { String generated = result.getGeneratedSource("test.PlainTools$$CopilotToolMeta"); assertFalse(generated.contains("Map.of("), "Expected no metadata map for a tool without metadata, got:\n" + generated); - assertTrue(generated.contains(" null\n )"), - "Expected metadata constructor argument to be null when metadata is absent, got:\n" + generated); + String normalizedGenerated = generated.replace("\r\n", "\n").replace('\r', '\n'); + assertTrue(normalizedGenerated.contains(" null,\n null\n )"), + "Expected metadata and isTerminal constructor arguments to be null when absent, got:\n" + generated); } @Test @@ -884,6 +1243,28 @@ public String grep(@CopilotToolParam("Query") String query) { "Expected Boolean.TRUE for overridesBuiltInTool, got:\n" + generated); } + @Test + void generatesTerminalTool_whenIsTerminal() { + String source = """ + package test; + import com.github.copilot.tool.CopilotTool; + public class TerminalTools { + @CopilotTool(value = "Ends the turn", isTerminal = true) + public String finish() { + return "done"; + } + } + """; + + CompilationResult result = compileWithProcessor(List.of(inMemorySource("test.TerminalTools", source))); + assertNoErrors(result); + String generated = result.getGeneratedSource("test.TerminalTools$$CopilotToolMeta"); + String normalizedGenerated = generated.replace("\r\n", "\n").replace('\r', '\n'); + assertTrue(normalizedGenerated.contains( + " null,\n null,\n null,\n null,\n Boolean.TRUE\n )"), + "Expected Boolean.TRUE for isTerminal in the final constructor position, got:\n" + generated); + } + // ── Test: Combined flags all apply independently ──────────────────────────── @Test @@ -893,7 +1274,8 @@ void generatesCombinedFlags() { import com.github.copilot.tool.CopilotTool; import com.github.copilot.rpc.ToolDefer; public class CombinedTools { - @CopilotTool(value = "Combined", overridesBuiltInTool = true, skipPermission = true, defer = ToolDefer.AUTO) + @CopilotTool(value = "Combined", overridesBuiltInTool = true, skipPermission = true, + isTerminal = true, defer = ToolDefer.AUTO) public String doAll() { return "done"; } @@ -909,11 +1291,10 @@ public String doAll() { assertTrue(generated.contains("Boolean.TRUE"), "Expected Boolean.TRUE for override/skipPermission, got:\n" + generated); assertTrue(generated.contains("ToolDefer.AUTO"), "Expected ToolDefer.AUTO, got:\n" + generated); - // Count Boolean.TRUE occurrences — should be 2 (overridesBuiltInTool + - // skipPermission) + // Count Boolean.TRUE occurrences — override, skipPermission, and isTerminal. long boolCount = generated.lines().filter(l -> l.contains("Boolean.TRUE")).count(); - assertEquals(2, boolCount, - "Expected 2 Boolean.TRUE lines (overridesBuiltInTool + skipPermission), got:\n" + generated); + assertEquals(3, boolCount, + "Expected 3 Boolean.TRUE lines (override + skipPermission + isTerminal), got:\n" + generated); } // ── Test: ToolDefer.NONE results in regular create ────────────────────────── diff --git a/java/src/test/java/com/github/copilot/tool/ParamTest.java b/java/sdk/src/test/java/com/github/copilot/tool/ParamTest.java similarity index 83% rename from java/src/test/java/com/github/copilot/tool/ParamTest.java rename to java/sdk/src/test/java/com/github/copilot/tool/ParamTest.java index 75f6e4422..c2b38a4ce 100644 --- a/java/src/test/java/com/github/copilot/tool/ParamTest.java +++ b/java/sdk/src/test/java/com/github/copilot/tool/ParamTest.java @@ -243,6 +243,52 @@ void toStringContainsName() { assertTrue(p.toString().contains("String")); } + // ------------------------------------------------------------------ + // Schema override validation + // ------------------------------------------------------------------ + + @Test + void rejectsSchemaWithDefaultValue() { + var ex = assertThrows(IllegalArgumentException.class, + () -> Param.of(String.class, "x", "desc").schema("{\"type\":\"string\"}").defaultValue("hello")); + assertTrue(ex.getMessage().contains("schema")); + assertTrue(ex.getMessage().contains("defaultValue")); + } + + @Test + void rejectsSchemaNotStartingWithBrace() { + var ex = assertThrows(IllegalArgumentException.class, + () -> Param.of(String.class, "x", "desc").schema("not json")); + assertTrue(ex.getMessage().contains("schema")); + } + + @Test + void rejectsSchemaNotEndingWithBrace() { + var ex = assertThrows(IllegalArgumentException.class, + () -> Param.of(String.class, "x", "desc").schema("{\"type\":\"string\"")); + assertTrue(ex.getMessage().contains("schema")); + } + + @Test + void acceptsValidSchemaJson() { + Param p = Param.of(String.class, "x", "desc").schema("{\"type\":\"string\",\"format\":\"date-time\"}"); + assertEquals("{\"type\":\"string\",\"format\":\"date-time\"}", p.schema()); + } + + @Test + void acceptsEmptySchema() { + Param p = Param.of(String.class, "x", "desc"); + assertEquals("", p.schema()); + } + + @Test + void schemaPreservedAcrossFluentCopies() { + Param base = Param.of(String.class, "x", "desc").schema("{\"type\":\"string\"}"); + assertEquals("{\"type\":\"string\"}", base.name("y").schema()); + assertEquals("{\"type\":\"string\"}", base.description("other").schema()); + assertEquals("{\"type\":\"string\"}", base.required(false).schema()); + } + // ------------------------------------------------------------------ // Null type rejected // ------------------------------------------------------------------ diff --git a/java/src/test/java/com/github/copilot/tool/SchemaGeneratorTest.java b/java/sdk/src/test/java/com/github/copilot/tool/SchemaGeneratorTest.java similarity index 100% rename from java/src/test/java/com/github/copilot/tool/SchemaGeneratorTest.java rename to java/sdk/src/test/java/com/github/copilot/tool/SchemaGeneratorTest.java diff --git a/java/src/test/prompts/PROMPT-smoke-test.md b/java/sdk/src/test/prompts/PROMPT-smoke-test.md similarity index 100% rename from java/src/test/prompts/PROMPT-smoke-test.md rename to java/sdk/src/test/prompts/PROMPT-smoke-test.md diff --git a/java/src/test/resources/logging-debug.properties b/java/sdk/src/test/resources/logging-debug.properties similarity index 100% rename from java/src/test/resources/logging-debug.properties rename to java/sdk/src/test/resources/logging-debug.properties diff --git a/java/src/test/resources/logging.properties b/java/sdk/src/test/resources/logging.properties similarity index 100% rename from java/src/test/resources/logging.properties rename to java/sdk/src/test/resources/logging.properties diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogReadResult.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogReadResult.java deleted file mode 100644 index 7383e0500..000000000 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionEventLogReadResult.java +++ /dev/null @@ -1,37 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - *--------------------------------------------------------------------------------------------*/ - -// AUTO-GENERATED FILE - DO NOT EDIT -// Generated from: api.schema.json - -package com.github.copilot.generated.rpc; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.github.copilot.CopilotExperimental; -import java.util.List; -import javax.annotation.processing.Generated; - -/** - * Batch of session events returned by a read, with cursor and continuation metadata. - * - * @apiNote This method is experimental and may change in a future version. - * @since 1.0.0 - */ -@CopilotExperimental -@javax.annotation.processing.Generated("copilot-sdk-codegen") -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonIgnoreProperties(ignoreUnknown = true) -public record SessionEventLogReadResult( - /** Events are delivered in two batches per read: persisted events first (in append order), then ephemeral events (in seq order). When `waitMs > 0` and the catch-up batches were empty, post-wait events follow the same two-batch ordering. Persisted and ephemeral events do not interleave within a single read. */ - @JsonProperty("events") List events, - /** Opaque cursor for the next read. Pass back unchanged in the next read.cursor to continue from where this read left off. Always present, even when no events were returned. */ - @JsonProperty("cursor") String cursor, - /** True when the read returned `max` events and more events are available immediately. When false, the next read with a non-zero `waitMs` will block until a new event arrives or the wait expires. */ - @JsonProperty("hasMore") Boolean hasMore, - /** Cursor status: 'ok' means the cursor was applied successfully; 'expired' means the cursor referred to an event that no longer exists in history (e.g. truncated or compacted away) and the read started from the beginning of the remaining history. */ - @JsonProperty("cursorStatus") EventsCursorStatus cursorStatus -) { -} diff --git a/java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryApi.java b/java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryApi.java deleted file mode 100644 index fa2f47b38..000000000 --- a/java/src/generated/java/com/github/copilot/generated/rpc/SessionHistoryApi.java +++ /dev/null @@ -1,93 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - *--------------------------------------------------------------------------------------------*/ - -// AUTO-GENERATED FILE - DO NOT EDIT -// Generated from: api.schema.json - -package com.github.copilot.generated.rpc; - -import com.github.copilot.CopilotExperimental; -import java.util.concurrent.CompletableFuture; -import javax.annotation.processing.Generated; - -/** - * API methods for the {@code history} namespace. - * - * @since 1.0.0 - */ -@javax.annotation.processing.Generated("copilot-sdk-codegen") -public final class SessionHistoryApi { - - private static final com.fasterxml.jackson.databind.ObjectMapper MAPPER = RpcMapper.INSTANCE; - - private final RpcCaller caller; - private final String sessionId; - - /** @param caller the RPC transport function */ - SessionHistoryApi(RpcCaller caller, String sessionId) { - this.caller = caller; - this.sessionId = sessionId; - } - - /** - * Optional compaction parameters. - * - * @apiNote This method is experimental and may change in a future version. - * @since 1.0.0 - */ - @CopilotExperimental - public CompletableFuture compact() { - return caller.invoke("session.history.compact", java.util.Map.of("sessionId", this.sessionId), SessionHistoryCompactResult.class); - } - - /** - * Identifier of the event to truncate to; this event and all later events are removed. - *

- * Note: the {@code sessionId} field in the params record is overridden - * by the session-scoped wrapper; any value provided is ignored. - * - * @apiNote This method is experimental and may change in a future version. - * @since 1.0.0 - */ - @CopilotExperimental - public CompletableFuture truncate(SessionHistoryTruncateParams params) { - com.fasterxml.jackson.databind.node.ObjectNode _p = MAPPER.valueToTree(params); - _p.put("sessionId", this.sessionId); - return caller.invoke("session.history.truncate", _p, SessionHistoryTruncateResult.class); - } - - /** - * Identifies the target session. - * - * @apiNote This method is experimental and may change in a future version. - * @since 1.0.0 - */ - @CopilotExperimental - public CompletableFuture cancelBackgroundCompaction() { - return caller.invoke("session.history.cancelBackgroundCompaction", java.util.Map.of("sessionId", this.sessionId), SessionHistoryCancelBackgroundCompactionResult.class); - } - - /** - * Identifies the target session. - * - * @apiNote This method is experimental and may change in a future version. - * @since 1.0.0 - */ - @CopilotExperimental - public CompletableFuture abortManualCompaction() { - return caller.invoke("session.history.abortManualCompaction", java.util.Map.of("sessionId", this.sessionId), SessionHistoryAbortManualCompactionResult.class); - } - - /** - * Identifies the target session. - * - * @apiNote This method is experimental and may change in a future version. - * @since 1.0.0 - */ - @CopilotExperimental - public CompletableFuture summarizeForHandoff() { - return caller.invoke("session.history.summarizeForHandoff", java.util.Map.of("sessionId", this.sessionId), SessionHistorySummarizeForHandoffResult.class); - } - -} diff --git a/java/src/main/java/com/github/copilot/rpc/PermissionRequest.java b/java/src/main/java/com/github/copilot/rpc/PermissionRequest.java deleted file mode 100644 index 51a303feb..000000000 --- a/java/src/main/java/com/github/copilot/rpc/PermissionRequest.java +++ /dev/null @@ -1,89 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - *--------------------------------------------------------------------------------------------*/ - -package com.github.copilot.rpc; - -import java.util.Map; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * Represents a permission request from the AI assistant. - *

- * When the assistant needs permission to perform certain actions, this object - * contains the details of the request, including the kind of permission and any - * associated tool call. - * - * @see PermissionHandler - * @since 1.0.0 - */ -@JsonInclude(JsonInclude.Include.NON_NULL) -public class PermissionRequest { - - @JsonProperty("kind") - private String kind; - - @JsonProperty("toolCallId") - private String toolCallId; - - private Map extensionData; - - /** - * Gets the kind of permission being requested. - * - * @return the permission kind - */ - public String getKind() { - return kind; - } - - /** - * Sets the permission kind. - * - * @param kind - * the permission kind - */ - public void setKind(String kind) { - this.kind = kind; - } - - /** - * Gets the associated tool call ID, if applicable. - * - * @return the tool call ID, or {@code null} if not a tool-related request - */ - public String getToolCallId() { - return toolCallId; - } - - /** - * Sets the tool call ID. - * - * @param toolCallId - * the tool call ID - */ - public void setToolCallId(String toolCallId) { - this.toolCallId = toolCallId; - } - - /** - * Gets additional extension data for the request. - * - * @return the extension data map - */ - public Map getExtensionData() { - return extensionData; - } - - /** - * Sets additional extension data for the request. - * - * @param extensionData - * the extension data map - */ - public void setExtensionData(Map extensionData) { - this.extensionData = extensionData; - } -} diff --git a/java/src/test/java/com/github/copilot/PermissionRequestResultTest.java b/java/src/test/java/com/github/copilot/PermissionRequestResultTest.java deleted file mode 100644 index 4a1ff0313..000000000 --- a/java/src/test/java/com/github/copilot/PermissionRequestResultTest.java +++ /dev/null @@ -1,73 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - *--------------------------------------------------------------------------------------------*/ - -package com.github.copilot; - -import static org.junit.jupiter.api.Assertions.*; - -import org.junit.jupiter.api.Test; - -import com.github.copilot.rpc.PermissionRequestResult; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.json.JsonMapper; -import com.fasterxml.jackson.annotation.JsonInclude; - -/** - * Tests for {@link PermissionRequestResult} factory methods and feedback field. - */ -public class PermissionRequestResultTest { - - private static final ObjectMapper MAPPER = JsonMapper.builder().serializationInclusion(JsonInclude.Include.NON_NULL) - .build(); - - @Test - void testApproveOnce() { - var result = PermissionRequestResult.approveOnce(); - assertEquals("approve-once", result.getKind()); - assertNull(result.getFeedback()); - } - - @Test - void testRejectWithFeedback() { - var result = PermissionRequestResult.reject("Not allowed"); - assertEquals("reject", result.getKind()); - assertEquals("Not allowed", result.getFeedback()); - } - - @Test - void testRejectWithoutFeedback() { - var result = PermissionRequestResult.reject(null); - assertEquals("reject", result.getKind()); - assertNull(result.getFeedback()); - } - - @Test - void testUserNotAvailable() { - var result = PermissionRequestResult.userNotAvailable(); - assertEquals("user-not-available", result.getKind()); - assertNull(result.getFeedback()); - } - - @Test - void testNoResult() { - var result = PermissionRequestResult.noResult(); - assertEquals("no-result", result.getKind()); - assertNull(result.getFeedback()); - } - - @Test - void testFeedbackSerialized() throws Exception { - var result = PermissionRequestResult.reject("Unsafe operation"); - var json = MAPPER.writeValueAsString(result); - assertTrue(json.contains("\"feedback\":\"Unsafe operation\"")); - assertTrue(json.contains("\"kind\":\"reject\"")); - } - - @Test - void testFeedbackNotSerializedWhenNull() throws Exception { - var result = PermissionRequestResult.approveOnce(); - var json = MAPPER.writeValueAsString(result); - assertFalse(json.contains("feedback")); - } -} diff --git a/nodejs/README.md b/nodejs/README.md index 52b3d2805..eec674ce4 100644 --- a/nodejs/README.md +++ b/nodejs/README.md @@ -36,7 +36,7 @@ import { CopilotClient, approveAll } from "@github/copilot-sdk"; const client = new CopilotClient(); await client.start(); -// Create a session (onPermissionRequest is optional; approveAll allows every tool) +// approveAll is only valid when managed settings are disabled. const session = await client.createSession({ model: "gpt-5", onPermissionRequest: approveAll, @@ -71,6 +71,12 @@ await using session = await client.createSession({ // session is automatically disconnected when leaving scope ``` +When targeting MCP tools configured through `mcpServers`, remember the runtime +tool name is `-`. For `availableTools` and +`excludedTools`, prefer `new ToolSet().addMcp("-")` or +the raw `mcp:-` form. For `customAgents[].tools` and +`defaultAgent.excludedTools`, use `-` directly. + ## API Reference ### CopilotClient @@ -125,12 +131,14 @@ Create a new conversation session. - `sessionId?: string` - Custom session ID. - `model?: string` - Model to use ("gpt-5", "claude-sonnet-4.5", etc.). **Required when using custom provider.** -- `reasoningEffort?: "low" | "medium" | "high" | "xhigh"` - Reasoning effort level for models that support it. Use `listModels()` to check which models support this option. +- `reasoningEffort?: "low" | "medium" | "high" | "xhigh" | "max"` - Reasoning effort level for models that support it. Use `listModels()` to check which models support this option. - `tools?: Tool[]` - Custom tools exposed to the CLI. Tools without `handler` are declaration-only and must be resolved via pending tool-call RPCs. - `systemMessage?: SystemMessageConfig` - System message customization (see below) - `infiniteSessions?: InfiniteSessionConfig` - Configure automatic context compaction (see below) +- `workingDirectory?: string` - Working directory for the session (default: runtime process cwd). +- `enableSessionStore?: boolean` - Enables the cross-session store for search and retrieval across sessions. When unset in `"copilot-cli"` mode, the runtime default applies (enabled). In `"empty"` mode, defaults to disabled. - `provider?: ProviderConfig` - Custom API provider configuration (BYOK - Bring Your Own Key). See [Custom Providers](#custom-providers) section. -- `onPermissionRequest?: PermissionHandler` - Optional handler called before each tool execution to approve or deny it. When omitted, permission requests are emitted as events and left pending for manual resolution. Use `approveAll` to allow everything, or provide a custom function for fine-grained control. See [Permission Handling](#permission-handling) section. +- `onPermissionRequest?: PermissionHandler` - Optional handler called before each tool execution to approve or deny it. When omitted, permission requests are emitted as events and left pending for manual resolution. `approveAll` approves requests when managed settings are disabled and throws when `enableManagedSettings` is true. Custom handlers can inspect `managedApprovalRequired` for human-facing confirmation logic. See [Permission Handling](#permission-handling) section. - `onUserInputRequest?: UserInputHandler` - Handler for user input requests from the agent. Enables the `ask_user` tool. See [User Input Requests](#user-input-requests) section. - `onElicitationRequest?: ElicitationHandler` - Handler for elicitation requests dispatched by the server. Enables this client to present form-based UI dialogs on behalf of the agent or other session participants. See [Elicitation Requests](#elicitation-requests) section. - `hooks?: SessionHooks` - Hook handlers for session lifecycle events. See [Session Hooks](#session-hooks) section. @@ -751,7 +759,7 @@ The SDK supports custom OpenAI-compatible API providers (BYOK - Bring Your Own K - `apiKey?: string` - API key (optional for local providers like Ollama) - `bearerToken?: string` - Bearer token for authentication (takes precedence over apiKey) - `wireApi?: "completions" | "responses"` - API format for OpenAI/Azure (default: "completions") -- `azure?.apiVersion?: string` - Azure API version (default: "2024-10-21") +- `azure?.apiVersion?: string` - Azure API version; when omitted, the runtime uses the GA versionless `v1` route **Example with Ollama:** @@ -855,7 +863,7 @@ An `onPermissionRequest` handler is optional when you create or resume a session ### Approve All (simplest) -Use the built-in `approveAll` helper to allow every tool call without any checks: +Use the built-in `approveAll` helper when managed settings are disabled: ```typescript import { CopilotClient, approveAll } from "@github/copilot-sdk"; @@ -866,9 +874,11 @@ const session = await client.createSession({ }); ``` +When `enableManagedSettings` is true for the session, `approveAll` throws. Use a custom handler for managed sessions; request-level `managedApprovalRequired` remains available for human-facing confirmation logic. + ### Custom Permission Handler -Provide your own function to inspect each request and apply custom logic: +Provide your own function to inspect each request and apply custom logic. Check `managedApprovalRequired` before any automatic approval: ```typescript import type { PermissionRequest, PermissionRequestResult } from "@github/copilot-sdk"; @@ -876,6 +886,11 @@ import type { PermissionRequest, PermissionRequestResult } from "@github/copilot const session = await client.createSession({ model: "gpt-5", onPermissionRequest: (request: PermissionRequest, invocation): PermissionRequestResult => { + if ("managedApprovalRequired" in request && request.managedApprovalRequired === true) { + // Leave the request pending for the host's human-facing confirmation flow. + return { kind: "no-result" }; + } + // request.kind — what type of operation is being requested: // "shell" — executing a shell command // "write" — writing or editing a file @@ -913,7 +928,7 @@ The handler must return one of the `PermissionDecision` shapes (or `{ kind: "no- | `"approve-permanently"` | Allow this request and persist the approval across sessions (currently used for URL domains) | `domain` (URL domain to approve) | | `"reject"` | Deny the request | `feedback?` (optional string surfaced to the agent) | | `"user-not-available"` | Deny the request because no user is available to confirm it | — | -| `"no-result"` | Leave the request unanswered (only valid with protocol v1; rejected by protocol v2 servers) | — | +| `"no-result"` | Suppress this SDK client's response so another connected client can answer the pending request | — | ### Resuming Sessions @@ -1058,6 +1073,16 @@ const session = await client.createSession({ errorHandling: "retry", // "retry", "skip", or "abort" }; }, + + // Called when the top-level agent naturally stops + onAgentStop: async (input, invocation) => { + if (!input.stopHookActive && needsMoreWork()) { + return { + decision: "block", + reason: "Run the final validation and fix any failures.", + }; + } + }, }, }); ``` @@ -1071,6 +1096,7 @@ const session = await client.createSession({ - `onSessionStart` - Run logic when a session starts or resumes. - `onSessionEnd` - Cleanup or logging when session ends. - `onErrorOccurred` - Handle errors with retry/skip/abort strategies. +- `onAgentStop` - Observe natural top-level agent completion. Return `{ decision: "block", reason }` to request another turn; use `stopHookActive` to avoid repeated blocks. ## Error Handling @@ -1083,6 +1109,21 @@ try { } ``` +## Development + +From the repository root: + +```bash +cd test/harness +npm ci +``` + +```bash +cd nodejs +npm ci +npm test +``` + ## License MIT diff --git a/nodejs/docs/agent-author.md b/nodejs/docs/agent-author.md index 907181442..6b9366a7e 100644 --- a/nodejs/docs/agent-author.md +++ b/nodejs/docs/agent-author.md @@ -258,7 +258,7 @@ Subscribe to session events. Returns an unsubscribe function. ```js const unsub = session.on("tool.execution_complete", (event) => { - // event.data.toolName, event.data.success, event.data.result + // event.data.success, event.data.result }); ``` @@ -268,9 +268,9 @@ const unsub = session.on("tool.execution_complete", (event) => { | ------------------------- | ------------------------------------------------------ | | `assistant.message` | `content`, `messageId` | | `tool.execution_start` | `toolCallId`, `toolName`, `arguments` | -| `tool.execution_complete` | `toolCallId`, `toolName`, `success`, `result`, `error` | +| `tool.execution_complete` | `toolCallId`, `success`, `result`, `error` | | `user.message` | `content`, `attachments`, `source` | -| `session.idle` | `backgroundTasks` | +| `session.idle` | `aborted` | | `session.error` | `errorType`, `message`, `stack` | | `permission.requested` | `requestId`, `permissionRequest.kind` | | `session.shutdown` | `shutdownType`, `totalPremiumRequests` | diff --git a/nodejs/docs/examples.md b/nodejs/docs/examples.md index 1bac87982..63389c491 100644 --- a/nodejs/docs/examples.md +++ b/nodejs/docs/examples.md @@ -368,7 +368,7 @@ session.on((event) => { ```js const unsubscribe = session.on("tool.execution_complete", (event) => { - // event.data.toolName, event.data.success, event.data.result, event.data.error + // event.data.success, event.data.result, event.data.error }); // Later, stop listening @@ -417,9 +417,9 @@ session.on("assistant.message", (event) => { | `assistant.message` | Agent's final response | `content`, `messageId`, `toolRequests` | | `assistant.message_delta` | Message content chunks (ephemeral) | `deltaContent` | | `tool.execution_start` | A tool is about to run | `toolCallId`, `toolName`, `arguments` | -| `tool.execution_complete` | A tool finished running | `toolCallId`, `toolName`, `success`, `result`, `error` | +| `tool.execution_complete` | A tool finished running | `toolCallId`, `success`, `result`, `error` | | `user.message` | User sent a message | `content`, `attachments`, `source` | -| `session.idle` | Session finished processing a turn | `backgroundTasks` | +| `session.idle` | Session finished processing a turn | `aborted` | | `session.error` | An error occurred | `errorType`, `message`, `stack` | | `permission.requested` | Agent needs permission (shell, file write, etc.) | `requestId`, `permissionRequest.kind` | | `session.shutdown` | Session is ending | `shutdownType`, `totalPremiumRequests`, `codeChanges` | @@ -677,6 +677,6 @@ session.on("assistant.message", (event) => { }); session.on("tool.execution_complete", (event) => { - // event.data.success, event.data.toolName, event.data.result + // event.data.success, event.data.result }); ``` diff --git a/nodejs/docs/extensions.md b/nodejs/docs/extensions.md index 8b36de8a5..d33a73312 100644 --- a/nodejs/docs/extensions.md +++ b/nodejs/docs/extensions.md @@ -56,4 +56,5 @@ The `session` object provides methods for sending messages, logging to the timel ## Further Reading - `examples.md` — Practical code examples for tools, hooks, events, and complete extensions +- `factories.md`: Authoring, running, resuming, and observing Agent Factories - `agent-author.md` — Step-by-step workflow for agents authoring extensions programmatically diff --git a/nodejs/docs/factories.md b/nodejs/docs/factories.md new file mode 100644 index 000000000..e6d9ce2bc --- /dev/null +++ b/nodejs/docs/factories.md @@ -0,0 +1,257 @@ +# Agent Factories + +Agent Factories are extension-authored, session-scoped workflows that coordinate subagents and durable steps. The API is experimental. + +## Define and register a factory + +Use `defineFactory` and pass the returned handle to `joinSession`: + +```js +import { defineFactory, joinSession } from "@github/copilot-sdk/extension"; + +const reviewChanged = defineFactory({ + meta: { + name: "review-changed", + description: + "Review changed files and verify the findings. " + + "args: { files: string[] } — the paths to review.", + phases: [{ title: "Review" }, { title: "Verify" }], + argsSchema: { + type: "object", + required: ["files"], + properties: { + files: { type: "array", items: { type: "string" } }, + }, + }, + limits: { + maxConcurrentSubagents: 3, + maxTotalSubagents: 10, + timeoutSeconds: 90.5, + maxAiCredits: 5, + }, + }, + run: async (ctx) => { + ctx.phase("Review"); + const reviews = await ctx.parallel( + ctx.args.files.map( + (file) => () => ctx.agent(`Review ${file}`, { label: `Review ${file}` }) + ) + ); + + ctx.phase("Verify"); + const report = await ctx.step("report", () => ({ reviews })); + ctx.log(`Completed factory run ${ctx.runId}`); + return report; + }, +}); + +const session = await joinSession({ factories: [reviewChanged] }); +``` + +Factory metadata contains a stable `name`, a human-readable `description`, declared `phases`, an optional `argsSchema`, and optional `limits`. Phase entries contain a `title` and optional `detail`. + +## Declaring an argument shape + +A factory that reads `ctx.args` should declare `meta.argsSchema`, as the example above does. When the model invokes the factory through the `run_factory` tool, the CLI validates `args` against the declaration **before** the run starts. + +Declaring one turns an expensive failure into a cheap one. With a schema, a malformed call is rejected up front — the model gets a correction hint and retries, and no run row, permission prompt, or credit spend happens. Without one, nothing validates: the run starts, takes a user approval, spends credits, and then dies inside the factory body with a confusing error. Agents can read the declared shape with `factories_manage` using `operation: "inspect"`. + +Enforcement covers structure — types, required properties, and enum or const values. Finer constraints such as `minLength`, `pattern`, or `additionalProperties` are recorded in the declaration but not enforced. The accepted vocabulary is the `FactoryJsonSchema` subset also used for subagent structured output: `type`, `required`, `enum`, `const`, recursive `properties`/`items`, and `anyOf`/`oneOf`/`allOf`. A `type` is one of `null`, `boolean`, `integer`, `number`, `string`, `array`, or `object`, or a non-empty array of those such as `["object", "null"]`. A declaration outside that subset is rejected at registration. + +`argsSchema` is optional and backward compatible. A factory that omits it behaves exactly as before, so **the `description` is then the only thing telling an agent what arguments to supply** — state the expected shape there. + +Validation covers the model's `run_factory` path only. An extension calling `session.factory.run(...)` directly is not validated against `argsSchema`; those arguments are typed through `defineFactory` instead, and that typing does not reach the model. So a factory that reads `ctx.args` should still validate it rather than assume a shape — the declared subset does not enforce every constraint, and it does not run at all on the SDK path. + +`defineFactory` accepts a `run(context)` function returning `Promise`, where `TResult` is `JsonValue | void`. Objects, arrays, strings, numbers, booleans, and `null` are valid results. Returning `undefined` completes the factory with no result. Other non-JSON values are rejected. + +## Factory context + +The `run()` context provides: + +- `ctx.runId`: Stable ID reused across resumed attempts. +- `ctx.args`: Invocation arguments, forwarded verbatim. When the caller omits `args`, this is `{}` rather than `undefined`. +- `ctx.agent(prompt, options?)`: Runs one factory-owned subagent. Options are exactly `label`, `schema`, `model`, `agent`, `reasoningEffort`, and `contextTier`. See [Subagent calls](#subagent-calls). +- `ctx.parallel(thunks)`: Runs thunks concurrently and awaits all of them (a barrier). A thunk that throws becomes `null` in the result array, so one failed item does not lose the rest. Cancellation and hard runtime failures (`ResponseError`, `ConnectionError`) are the exception — those propagate and reject the whole call, because they mean the run itself is in trouble rather than one item having failed. Handle them at run level; do not assume every failure arrives as a `null`. Rejects above 4096 items. +- `ctx.pipeline(items, ...stages)`: Flows each item through every stage without a barrier between stages, so one item can be in a later stage while another is still in an earlier one. Each stage is called as `(previous, item, index)`, where `previous` is the prior stage's result and `item` is the original input. A stage that throws drops that item to `null` and skips its remaining stages, with the same exception for cancellation and hard runtime failures. Rejects above 4096 items. +- `ctx.phase(title)`: Starts a named progress phase. This sets a single run-global value, so calling it from inside concurrent `parallel`/`pipeline` stages races. Call it at run-level transitions and distinguish concurrent work by `label` instead. +- `ctx.log(message)`: Appends a progress line. When a factory bounds its own coverage (top-N, sampling), log what was dropped. +- `ctx.step(key, producer, options?)`: Journals the producer's JSON result under a stable key so a resume replays it without re-running the producer. A journaled (default) producer must return a JSON-serializable value; `undefined` or a non-JSON value is rejected. Pass `{ volatile: true }` to bypass the journal and run the producer every time. + + The key is the *sole* identity: neither the producer body nor its inputs contribute to it. A resume replays the cached value for a matching key even if the producer has since changed, so version the key (`"scan-v2"`) whenever its inputs or meaning change. Journaled producers are best-effort at-least-once and may run again across crashes or concurrent same-key callers, so keep side effects idempotent. +- `ctx.session`: The session returned by `joinSession`. It refuses calls that start or resume a factory run. Call `extensions_manage` with `operation: "guide"` to read more about the session APIs. +- `ctx.signal`: Cooperative cancellation signal for extension work and subprocesses. +- `ctx.factory(...)`: Always rejects because nested factories are not supported. + +Factory-owned subagents are intentionally hidden from `read_agent` and `write_agent`. Use the factory observability APIs instead. + +### Subagent calls + +`ctx.agent(prompt, options?)` spawns one factory-scoped subagent and awaits it. Without a schema it resolves to the subagent's final text. With `options.schema` it resolves to the parsed JSON value. + +**Identical calls are memoized into one subagent.** Each call is journaled by its canonical prompt and options, including `label`. Two calls with the same prompt and the same options return one shared result — even when issued concurrently. To spawn N *independent* subagents, give each a unique `label` or vary the prompt: + +```js +// One subagent, awaited five times — almost certainly not what you want. +await ctx.parallel([1, 2, 3, 4, 5].map(() => () => ctx.agent("Find a bug"))); + +// Five independent subagents. +await ctx.parallel( + [1, 2, 3, 4, 5].map((i) => () => ctx.agent("Find a bug", { label: `finder:${i}` })) +); +``` + +**An ordinary failure resolves to `null` — it does not throw.** A subagent that errors, returns nothing, or (with a schema) produces output that still fails to parse or match after its one retry resolves `null`. Always guard the result before using it, including a bare `await ctx.agent(...)`: + +```js +const finding = await ctx.agent(prompt, { label: "inspector" }); +if (!finding) return { finding: null }; +``` + +Cancellation and hard runtime failures — a reached limit, a durable-state failure — reject instead, aborting the run. When filtering results, prefer `v => v !== null` over `Boolean`, which also discards a valid `false`, `0`, or `""`. + +**`schema` is a structural subset of JSON Schema, not a validator.** Honored: `type`, `required`, `enum`, `const`, recursive `properties`/`items`, and `anyOf`/`oneOf`/`allOf` — where `oneOf` is treated as `anyOf`, meaning at least one branch matches rather than exactly one. Ignored and *not* enforced: `additionalProperties`, `pattern`, `minLength`/`maxLength`, `format`, numeric ranges, and boolean schemas. Do not rely on an ignored keyword to constrain a result. A schema call retries once on a parse or match failure, so it may spawn twice, and both spawns count toward `maxTotalSubagents`. + +### Choosing between pipeline and parallel + +Prefer `pipeline` for multi-stage work. It has no barrier between stages, so each item advances as soon as its own prior stage finishes. + +Reach for a barrier — `parallel` between stages — only when a stage genuinely needs every prior result at once: deduplicating or merging across the full set, an early exit based on the total, or a prompt that compares one result against the others. Needing to map, filter, or flatten is not a reason to use a barrier; do that inside a pipeline stage. Barrier latency is real: if the slowest of N subagents takes three times the fastest, a barrier wastes the rest of the pool's time. + +See [factory-patterns.md](./factory-patterns.md) for composable orchestration patterns built on these primitives. + +## Resource limits + +Limits may be declared in `meta.limits` and overridden per invocation. All limits must be positive when present. + +- `maxConcurrentSubagents`: Positive integer concurrent-subagent cap. Additional subagents wait in a queue. Queueing applies backpressure and does not fail the run. +- `maxTotalSubagents`: Positive integer cumulative admission cap. An attempted subagent beyond the cap ends the attempt with failure kind `maxTotalSubagents`. +- `timeoutSeconds`: Positive finite number of seconds, including positive fractions, capped at `2_147_483.647`. It measures accumulated active-execution time across attempts, including the extension body, subprocess waits, queued-agent waits, and sleeps. Time between attempts is excluded. The timeout is soft because already-running work may take time to stop. Its failure kind is `timeoutSeconds`. +- `maxAiCredits`: Positive finite AI-credit budget for the whole run's factory subagent subtree, including descendants. AI credits are GitHub Copilot's universal usage metric. This is a soft, post-paid ceiling, so completed or parallel turns can settle above it before the run stops. Accounting is fail-closed: an accounting failure stops a budgeted run rather than allowing untracked use. Its failure kind is `maxAiCredits`. + +`maxTotalSubagents`, `timeoutSeconds`, and `maxAiCredits` use reject-and-retry semantics. A rejected attempt ends with run status `error` and `failure.type` set to `factory_limit_reached`. The failed run keeps its ID, arguments, journal, and accounting. Resume the run with a raised limit when additional work is approved. Previously consumed resources still count. + +## Run and resume + +Run by registered name or handle: + +```ts +const run = await session.factory.run("review-changed", { + args: { files: ["src/a.ts"] }, + limits: { maxAiCredits: 3 }, +}); + +if (run.status === "completed") { + console.log(run.result); +} else { + console.error(`run ${run.runId} ended as ${run.status}`, run.failure ?? run.error); +} +``` + +The name overload is: + +```ts +session.factory.run( + name: string, + options?: { args?: JsonValue; limits?: FactoryLimits }, +): Promise; +``` + +Resume by run ID without resending the name or arguments: + +```ts +const run = await session.factory.resume(runId, { + limits: { maxAiCredits: 6 }, +}); +``` + +The signature is: + +```ts +session.factory.resume( + runId: string, + options?: { limits?: FactoryLimits }, +): Promise; +``` + +Both resolve with the run envelope (`FactoryRunResult`) for **every** outcome — `completed`, `error`, `halted`, and `cancelled` alike. Inspect `status` and read `result` only when the run completed; a limit breach carries a typed `failure`. SDK-initiated `run` and `resume` do not request permission, so they have no declined outcome. The model's `run_factory` tool requests permission before the durable row exists; declining it creates no run row. An SDK-initiated run is refused only when the session already has its maximum number of active top-level runs. Pre-execution resume failures throw `FactoryResumeError`, whose `code` is one of `not_found`, `non_resumable`, `already_active`, `factory_already_running`, `factory_limits_invalid`, `factory_session_disposed`, `factory_storage_unavailable`, or `factory_storage_corrupt`. + +An agent that no longer has a prior run's ID in context can recover it with `factories_manage` and `operation: "runs"`, which lists the session's factory runs with their IDs and statuses. This matters for resume: a run that reached a limit keeps its journal, so resuming it replays completed work for free, while restarting it from scratch pays for that work twice. + +The agent-facing `run_factory` tool has exactly two input branches: + +```ts +{ name: string; args?: JsonValue; limits?: FactoryLimits } +{ resumeFromRunId: string; limits?: FactoryLimits } +``` + +## Authoring a factory from inside a session + +The agent-facing `factories_manage` tool writes a factory into a session-scoped extension at runtime with `operation: "author"`. The rules above all apply, plus one constraint that does not affect an extension author. + +**The `run` body is self-contained.** It is emitted verbatim into a generated module as a single async function expression. It closes over nothing: not the conversation that authored it, and not any authoring-time binding. Only its own locals, its `ctx` parameter, and standard Node and JavaScript globals are in scope, so every schema, constant, and helper must be defined *inside* the function. The generated module imports the SDK itself; the expression cannot add static `import` statements or use `require`. Load anything else with a dynamic `await import("...")` in the body. + +```js +async ({ args, agent, phase }) => { + // Defined inside — there is no outer scope to close over. + const VERDICT = { type: "object", properties: { real: { type: "boolean" } }, required: ["real"] }; + + phase("Inspect"); + const finding = await agent(`Name one likely bug in ${args.file ?? "the code"}.`, { + label: "inspector", + }); + if (!finding) return { finding: null, real: false }; + + phase("Verify"); + const verdict = await agent(`Is this a real bug? Claim: ${finding}`, { + label: "verifier", + schema: VERDICT, + }); + return { finding, real: verdict?.real === true }; +}; +``` + +Authoring registers the factory but does not run it. Invoke it afterwards with `run_factory`. Use `factories_manage` with `operation: "list"` to see the factories already registered in the session and `operation: "inspect"` to read one factory's description, phases, declared argument shape, and limits before running it. + +## Observe a run + +The calling session can inspect its own factory runs: + +```ts +const runs = await session.factory.listRuns(); +const detail = await session.factory.getRunDetail(runId); +const page = await session.factory.getRunProgress(runId, { + phaseId, + afterSeq, + beforeSeq, + limit, +}); +``` + +- `listRuns()` returns the newest default page of this session's durable factory runs. +- `getRunDetail(runId)` returns phases, prompt-safe agent summaries, and the latest progress page. +- `getRunProgress(runId, options?)` pages progress forward, backward, by phase, or from the latest tail. + +`getRun(runId)` reads the latest run envelope, and `cancel(runId)` cancels a run and returns its terminal envelope. + +`waitForRun(runId, options?)` resolves with the terminal envelope once the run settles into `completed`, `error`, `halted`, or `cancelled`, and resolves immediately when it has already settled: + +```ts +const settled = await session.factory.waitForRun(runId); +if (settled.status === "completed") { + console.log(settled.result); +} +``` + +It watches `factory.run_updated` and re-reads the durable envelope on each invalidation, collapsing a burst of events into a single in-flight read. A low-frequency periodic re-read runs alongside the subscription, so a dropped or missing invalidation degrades into a slightly late resolution rather than an unbounded wait. Pass a `signal` to stop waiting: + +```ts +const controller = new AbortController(); +setTimeout(() => controller.abort(), 30_000); +const settled = await session.factory.waitForRun(runId, { signal: controller.signal }); +``` + +Aborting rejects the wait and has no effect on the run, which keeps executing — use `cancel(runId)` to actually stop it. Because a terminal envelope is final, the resolved value never changes afterwards. `isFactoryRunTerminal(status)` exposes the same terminal-status test for callers driving their own loop. + +Listen for the ephemeral `factory.run_updated` event. Its `{ runId, revision }` payload is an invalidation signal. Re-read the desired API when a newer monotonic revision arrives. + +Revisions cover durable lifecycle, accounting, phase, agent, and progress changes. Continuous read-time fields can change without a new revision. These include `observedAt`, active-time calculations, live counts, and a live agent's status or prompt-safe activity text. Factory prompts are never exposed by these APIs. A run is visible only through the session that owns it. diff --git a/nodejs/docs/factory-patterns.md b/nodejs/docs/factory-patterns.md new file mode 100644 index 000000000..66c6d13b1 --- /dev/null +++ b/nodejs/docs/factory-patterns.md @@ -0,0 +1,194 @@ +# Agent Factory patterns + +Composable orchestration patterns built on the factory context. Read [factories.md](./factories.md) first for the API and its semantics. The API is experimental. + +Every snippet below assumes the surrounding `async (ctx) => { ... }` run body and destructures the hooks it uses. Three rules apply throughout, because breaking them fails silently: + +- **Give every independent subagent a unique `label`.** Identical prompt-and-options pairs memoize into a single shared subagent. +- **Guard every `agent()` result.** An ordinary failure resolves to `null` rather than throwing. +- **Filter with `v => v !== null`,** not `Boolean`, which also discards a valid `false`, `0`, or `""`. + +## Multi-stage review + +The default shape: fan out across dimensions, and let each dimension verify as soon as its own review lands. No barrier, so a slow dimension never holds up a fast one. + +```js +async ({ pipeline, parallel, agent, phase, log }) => { + const FINDINGS = { + type: "object", + properties: { + findings: { + type: "array", + items: { + type: "object", + properties: { title: { type: "string" } }, + required: ["title"], + }, + }, + }, + required: ["findings"], + }; + const VERDICT = { + type: "object", + properties: { isReal: { type: "boolean" } }, + required: ["isReal"], + }; + const DIMENSIONS = [ + { key: "bugs", prompt: "Review the diff for correctness bugs. Return JSON {findings:[{title}]}." }, + { key: "perf", prompt: "Review the diff for performance issues. Return JSON {findings:[{title}]}." }, + ]; + + phase("Review"); // Run-global: set it before the fan-out, never inside a stage. + const perDimension = await pipeline( + DIMENSIONS, + (d) => agent(d.prompt, { label: `review:${d.key}`, schema: FINDINGS }), + (review, d) => { + if (!review) { + log(`review:${d.key} produced nothing`); + return []; + } + return parallel( + (review.findings ?? []).map((f, i) => () => + agent(`Adversarially verify this finding is real: ${f.title}`, { + label: `verify:${d.key}:${i}`, + schema: VERDICT, + }).then((v) => (v && v.isReal ? f : null)) + ) + ); + } + ); + + return { confirmed: perDimension.flat().filter((v) => v !== null) }; +}; +``` + +## When a barrier is correct + +Deduplicating across every finding needs the whole set in hand, so the barrier earns its cost here. Dedup itself is plain JavaScript, done in the body between the two fan-outs. This excerpt reuses `FINDINGS`, `VERDICT`, and `DIMENSIONS` from the previous example — define them inside your own function. + +```js +const all = await parallel( + DIMENSIONS.map((d) => () => agent(d.prompt, { label: `find:${d.key}`, schema: FINDINGS })) +); +const findings = all.filter((v) => v !== null).flatMap((r) => r.findings ?? []); +const deduped = [...new Map(findings.map((f) => [f.title, f])).values()]; // Needs all of them. +const verified = await parallel( + deduped.map((f, i) => () => agent(`Verify: ${f.title}`, { label: `verify:${i}`, schema: VERDICT })) +); +``` + +## Loop until count + +Accumulate toward a target. Each iteration needs a unique identity — a unique label plus a prompt that excludes what has already been found — a bounded attempt count, and a null guard. + +```js +const BUG = { + type: "object", + properties: { title: { type: "string" } }, + required: ["title"], +}; + +const bugs = []; +let attempt = 0; +while (bugs.length < 10 && attempt < 30) { + const r = await agent( + `Find ONE distinct bug NOT already listed: ${JSON.stringify(bugs.map((b) => b.title))}. Return JSON {title}.`, + { label: `finder:${attempt}`, schema: BUG } + ); + attempt++; + if (r && r.title) bugs.push(r); + log(`${bugs.length}/10 found`); +} +``` + +## Loop until dry + +Keep spawning finders until some number of consecutive rounds surface nothing new. Deduplicate against everything *seen*, not just what was kept, or discarded findings resurface every round. + +```js +const BUGS = { + type: "object", + properties: { + bugs: { + type: "array", + items: { type: "object", properties: { title: { type: "string" } }, required: ["title"] }, + }, + }, + required: ["bugs"], +}; +const VERDICT = { + type: "object", + properties: { real: { type: "boolean" } }, + required: ["real"], +}; + +const seen = new Set(); +const confirmed = []; +const keyOf = (b) => b.title.toLowerCase(); +let dry = 0; +let round = 0; + +while (dry < 2 && round < 20) { + const found = ( + await parallel( + [0, 1, 2].map((i) => () => + agent(`Find bugs (finder ${i}, round ${round}). Return JSON {bugs:[{title}]}.`, { + label: `find:${round}:${i}`, + schema: BUGS, + }) + ) + ) + ) + .filter((v) => v !== null) + .flatMap((r) => r.bugs ?? []); + + const fresh = found.filter((b) => { + const k = keyOf(b); + if (seen.has(k)) return false; + seen.add(k); + return true; + }); + + if (!fresh.length) { + dry++; + round++; + continue; + } + dry = 0; + + const judged = await parallel( + fresh.map((b, i) => () => + parallel( + ["correctness", "security", "repro"].map((lens) => () => + agent(`Judge via ${lens}: is "${b.title}" real? Return JSON {real}.`, { + label: `judge:${round}:${i}:${lens}`, + schema: VERDICT, + }) + ) + ).then((vs) => ({ b, real: vs.filter((v) => v !== null).filter((v) => v.real).length >= 2 })) + ) + ); + + confirmed.push(...judged.filter((v) => v !== null && v.real).map((v) => v.b)); + round++; +} +``` + +## Quality patterns + +Compose these freely. + +- **Adversarial verify.** Spawn several independent skeptics per finding, each prompted to *refute* it and to default to refuted when uncertain. Keep only what a majority fails to refute. +- **Perspective-diverse verify.** Give each verifier a distinct lens — correctness, security, performance, does-it-reproduce — instead of several identical skeptics. The distinct prompts also stop them memoizing into one subagent. +- **Judge panel.** Generate several independent attempts from different angles, score them with parallel judges, then synthesize from the winner while grafting the best ideas from the runners-up. +- **Multi-modal sweep.** Run parallel searchers that each look a different way: by container, by content, by entity, by time. +- **Completeness critic.** End with an agent asking what is missing — an angle not run, a claim unverified, a source unread — and use its answer to seed the next round. +- **No silent caps.** When the factory bounds its own coverage with a top-N, a sampling step, or a no-retry rule, `log()` what was dropped. + +## Scaling + +Match the orchestration to what was asked. A quick check wants a couple of subagents and single-vote verification; a request to be thorough or comprehensive wants a larger finder pool, a three-to-five vote adversarial pass, and a synthesis stage. + +There is no in-script budget object. Scale with your own counters, as in the loop patterns above, and treat the declared limits as the safety ceiling rather than the control mechanism. Only `agent()` spawns are throttled, by `maxConcurrentSubagents` falling back to `maxTotalSubagents`; with neither declared there is no built-in concurrency cap, so declare one before fanning out widely. `parallel` itself is `Promise.all`, so non-agent work in a thunk runs fully concurrently regardless. + +These patterns are not exhaustive. Compose novel harnesses — tournament brackets, self-repair loops, staged escalation — when the task calls for it. diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json index 65fed3fbf..0fe7660f8 100644 --- a/nodejs/package-lock.json +++ b/nodejs/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0-dev", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.73", + "@github/copilot": "^1.0.79", "koffi": "^3.1.0", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" @@ -700,9 +700,9 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.73", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.73.tgz", - "integrity": "sha512-8I2Ejg2CX/PQA3c2H8W1zuqhniCeR1q1/bD8CrV53/ZLw8GF7DAV0xQpwa8ELYvFgjXb6AADojafCKwdbVef+A==", + "version": "1.0.79", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.79.tgz", + "integrity": "sha512-uHBm2BYbKJgyfiKp1WokX7QUNHGvzEX0zaGeb3qM3CybP06rsJrX3JgQe95qwwma6vQz0ah9gV68ERW2JqaKRA==", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { "detect-libc": "^2.1.2" @@ -711,20 +711,20 @@ "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.73", - "@github/copilot-darwin-x64": "1.0.73", - "@github/copilot-linux-arm64": "1.0.73", - "@github/copilot-linux-x64": "1.0.73", - "@github/copilot-linuxmusl-arm64": "1.0.73", - "@github/copilot-linuxmusl-x64": "1.0.73", - "@github/copilot-win32-arm64": "1.0.73", - "@github/copilot-win32-x64": "1.0.73" + "@github/copilot-darwin-arm64": "1.0.79", + "@github/copilot-darwin-x64": "1.0.79", + "@github/copilot-linux-arm64": "1.0.79", + "@github/copilot-linux-x64": "1.0.79", + "@github/copilot-linuxmusl-arm64": "1.0.79", + "@github/copilot-linuxmusl-x64": "1.0.79", + "@github/copilot-win32-arm64": "1.0.79", + "@github/copilot-win32-x64": "1.0.79" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.73", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.73.tgz", - "integrity": "sha512-5jv7t2sw35/zI0cPze38hG6239NT5/q/Emjx6gLibYkolDqMDJjpm17Ps7tc8oafUEOiMQMb+ar7+qi6rSiGJA==", + "version": "1.0.79", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.79.tgz", + "integrity": "sha512-rsw7JoMvlcxXb0yx08oIeEc0x2hUEwKSfhX9ESKfdMVt0Ckrzm4OEvNUyzOpOnLJ9+l3h/aI+u1w5g2ZU2K7UA==", "cpu": [ "arm64" ], @@ -738,9 +738,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.73", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.73.tgz", - "integrity": "sha512-l794k6Ahb11AG2FQT/P4TEWxWblzM1h8aQQCzG8jBWp8dfwjhyYjJ+d+0CWQzM3Fc1ddNUZRjKXCUsfvFjiZhQ==", + "version": "1.0.79", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.79.tgz", + "integrity": "sha512-D983e2lXYnq+KhjA8mTZXonY1+LGfJN9BM195J73shUvx49nRJmibDHWLvVtGeYc+43evGUOAQrOqOspAhhWPQ==", "cpu": [ "x64" ], @@ -754,9 +754,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.73", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.73.tgz", - "integrity": "sha512-Zu0W5nupJjNeem0brqU/pG+VY0IWr6EWr/FsC90g5SEDiaM4VhVNVWcz8t0E3DQCSYetV6IBaNMtjs/3uIIiDQ==", + "version": "1.0.79", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.79.tgz", + "integrity": "sha512-qqaNkvi92Wg+4OZk/kTWC2nUG72G0vV6eRAo5+PnKaPmjdX1GsI0a+lPxXPEbzX0zYLi/8yrUyANwyyNEsGgXA==", "cpu": [ "arm64" ], @@ -770,9 +770,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.73", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.73.tgz", - "integrity": "sha512-k33XIr6/PVp+K+5F/zv3No4PPaNImvHz73mcbIw63oxh5iiacXjgr0WqbBIS5s/rkhOWjNPIkbof/TTPZ7mQjA==", + "version": "1.0.79", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.79.tgz", + "integrity": "sha512-wzotZfvHkItutciLFMXZT2k9Qiii4Ta8tsVDCMQ7CP8hPxV91FyJ1yf3+FFSSfPvWrfYM6BOAiqIuX+LjgRuiw==", "cpu": [ "x64" ], @@ -786,9 +786,9 @@ } }, "node_modules/@github/copilot-linuxmusl-arm64": { - "version": "1.0.73", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.73.tgz", - "integrity": "sha512-HJWzhfD3oaiIgfRAHkNWzp17fELtshqM9HVN5n+lFEmSO2EETCEh0P1lhJc4m+FYfXSJnL0raAqVuyaNMuPoPw==", + "version": "1.0.79", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.79.tgz", + "integrity": "sha512-INtRSARl7DdNm2MXnn4GJuK+Y7QD24ANox02uH8htNQwRlNvdvg+YGS1V/mYgLDXFepeUjMjzTNC+i70+kh5uw==", "cpu": [ "arm64" ], @@ -802,9 +802,9 @@ } }, "node_modules/@github/copilot-linuxmusl-x64": { - "version": "1.0.73", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.73.tgz", - "integrity": "sha512-/BpOXSb16wHEu8I1SaKiLszQ4Kvu4+Z4uCn7W0bv4xI4fPZwTEG0u3zgaI2W9Ao3+aBl0XRpPmpWzE9ziYEq+w==", + "version": "1.0.79", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.79.tgz", + "integrity": "sha512-LxJAIfPP6Ok/9qpXGZuhnAft3W9JVcK9tbO3jWXcGDJT3v+2NtutyjmP/A7/cDXdTruXVQ4MybwAgacN8Gj/sg==", "cpu": [ "x64" ], @@ -818,9 +818,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.73", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.73.tgz", - "integrity": "sha512-DbPeXiYzQjpOy9oboaBvuCzjRwfcL987c3bG09cK1crdCDrKfkTJ7NXpcp1KWRPIRFO1FQm1qToNE89J+L3uvg==", + "version": "1.0.79", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.79.tgz", + "integrity": "sha512-5wg/ayCBTVy4g4FdO/9BJZRVARY0sgjAn9rBkw5BSJMv4u7Mvxg5Sftlift+V5UWxTyCSHAELZ5IHKvox4Yi8w==", "cpu": [ "arm64" ], @@ -834,9 +834,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.73", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.73.tgz", - "integrity": "sha512-8D3E1l5i+N5Eq8HIOQpx+Zbcb3MXdFxszksM2gqq175Z1S7Zna67oY4GoR3psxlbIpSyHKiLEBWYiaps6ayHWw==", + "version": "1.0.79", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.79.tgz", + "integrity": "sha512-FTpThWwwCDYnLdE0pfdo5zpAQLLVg36kmC2IKyVMuCYv9iPe7rE1mz7ng/UITN9M3TAMBrwHSvCV3pITvw4W8Q==", "cpu": [ "x64" ], @@ -1971,9 +1971,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", "dev": true, "license": "MIT", "dependencies": { @@ -3195,16 +3195,16 @@ } }, "node_modules/minimatch/node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz", + "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" } }, "node_modules/minimist": { @@ -3235,9 +3235,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "version": "3.3.17", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.17.tgz", + "integrity": "sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==", "dev": true, "funding": [ { @@ -3444,9 +3444,9 @@ } }, "node_modules/postcss": { - "version": "8.5.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", - "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "version": "8.5.25", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz", + "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==", "dev": true, "funding": [ { @@ -3464,7 +3464,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.12", + "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, diff --git a/nodejs/package.json b/nodejs/package.json index aadba0ece..437a77a01 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -56,7 +56,7 @@ "author": "GitHub", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.73", + "@github/copilot": "^1.0.79", "koffi": "^3.1.0", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" diff --git a/nodejs/samples/package-lock.json b/nodejs/samples/package-lock.json index a12f7eedd..66b4df470 100644 --- a/nodejs/samples/package-lock.json +++ b/nodejs/samples/package-lock.json @@ -18,7 +18,7 @@ "version": "0.0.0-dev", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.73", + "@github/copilot": "^1.0.79", "koffi": "^3.1.0", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts index 61f4a9941..30095186e 100644 --- a/nodejs/src/client.ts +++ b/nodejs/src/client.ts @@ -16,7 +16,7 @@ import { randomUUID } from "node:crypto"; import { existsSync } from "node:fs"; import { createRequire } from "node:module"; import { Socket } from "node:net"; -import { dirname, join } from "node:path"; +import { dirname, isAbsolute, join } from "node:path"; import { fileURLToPath } from "node:url"; import { createMessageConnection, @@ -85,6 +85,7 @@ import type { TypedSessionLifecycleHandler, } from "./types.js"; import { defaultJoinSessionPermissionHandler } from "./types.js"; +import type { FactoryHandle } from "./factory.js"; /** * Minimum protocol version this SDK can communicate with. @@ -521,6 +522,7 @@ export class CopilotClient { /** Connection-level session filesystem config, set via constructor option. */ private sessionFsConfig: SessionFsConfig | null = null; private requestHandler: CopilotRequestHandler | null = null; + private builtinPluginDirectories: string[] = []; private onGitHubTelemetry?: (notification: GitHubTelemetryNotification) => void | Promise; private clientGlobalHandlers: import("./generated/rpc.js").ClientGlobalApiHandlers = {}; @@ -681,6 +683,16 @@ export class CopilotClient { if (options.sessionFs) { this.validateSessionFsConfig(options.sessionFs); } + if (options.builtinPluginDirectories) { + for (const path of options.builtinPluginDirectories) { + if (!isAbsolute(path)) { + throw new Error( + `builtinPluginDirectories must contain only absolute paths: ${path}` + ); + } + } + this.builtinPluginDirectories = [...options.builtinPluginDirectories]; + } // Pre-parse the URI host/port and mark as external if applicable. if (conn.kind === "uri") { @@ -830,11 +842,6 @@ export class CopilotClient { private setupClientGlobalHandlers(): void { const handlers: import("./generated/rpc.js").ClientGlobalApiHandlers = {}; - // `hooks.invoke` is a client-global RPC method whose payload carries a - // `sessionId`; route each invocation to the matching session's dispatcher. - handlers.hooks = { - invoke: async (params) => await this.handleHooksInvoke(params), - }; if (this.requestHandler) { handlers.llmInference = createCopilotRequestAdapter(this.requestHandler, () => { if (!this.connection) { @@ -898,6 +905,17 @@ export class CopilotClient { // Verify protocol version compatibility await this.verifyProtocolVersion(); + if (this.builtinPluginDirectories.length > 0) { + try { + await this.connection!.sendRequest("plugins.builtin.set", { + paths: this.builtinPluginDirectories, + }); + } catch (error) { + await this.forceStop(); + throw error; + } + } + // If a session filesystem provider was configured, register it if (this.sessionFsConfig) { await this.connection!.sendRequest("sessionFs.setProvider", { @@ -1323,11 +1341,17 @@ export class CopilotClient { enableSessionStore: false, enableSkills: false, memory: { enabled: false }, + customAgentsLocalOnly: true, }; } return {}; } + /** Mode-specific default for enableExperimentalMode. */ + private experimentalModeForMode(supplied: boolean | undefined): boolean | undefined { + return this.options.mode === "empty" ? (supplied ?? false) : supplied; + } + /** * Returns the systemMessage config to use, adjusted for the current mode. * In empty mode we ensure the environment_context section is removed @@ -1425,7 +1449,9 @@ export class CopilotClient { await this.start(); } - config = { ...this.configDefaultsForMode(), ...config }; + const modeDefaults = this.configDefaultsForMode(); + config = { ...modeDefaults, ...config }; + config.customAgentsLocalOnly ??= modeDefaults.customAgentsLocalOnly; config.systemMessage = this.getSystemMessageConfigForMode(config.systemMessage); // For cloud sessions, let the CLI/server assign the session id and @@ -1461,7 +1487,12 @@ export class CopilotClient { this.connection!, undefined, this.onGetTraceContext, - { mcpAuthHandler: config.onMcpAuthRequest } + { + mcpAuthHandler: config.onMcpAuthRequest, + managedSettingsEnabled: + config.enableManagedSettings === true || + config.managedSettings !== undefined, + } ); s.registerTools(config.tools); s.registerCanvases(config.canvases); @@ -1518,6 +1549,7 @@ export class CopilotClient { clientName: config.clientName, reasoningEffort: config.reasoningEffort, reasoningSummary: config.reasoningSummary, + isExperimentalMode: this.experimentalModeForMode(config.enableExperimentalMode), contextTier: config.contextTier, tools: config.tools?.map((tool) => ({ name: tool.name, @@ -1527,6 +1559,7 @@ export class CopilotClient { skipPermission: tool.skipPermission, defer: tool.defer, metadata: tool.metadata, + isTerminal: tool.isTerminal, })), toolSearch: config.toolSearch, canvases: config.canvases?.map((canvas) => canvas.declaration), @@ -1550,6 +1583,7 @@ export class CopilotClient { models: config.models, enableSessionTelemetry: config.enableSessionTelemetry, enableCitations: config.enableCitations, + enableFileChangeTracking: config.enableFileChangeTracking, sessionLimits: config.sessionLimits, modelCapabilities: config.modelCapabilities, largeOutput: toWireLargeOutput(config.largeOutput), @@ -1557,10 +1591,14 @@ export class CopilotClient { requestUserInput: !!config.onUserInputRequest, requestElicitation: !!config.onElicitationRequest, ...(config.enableMcpApps ? { requestMcpApps: true } : {}), + ...(config.githubMcpToolConfig != null + ? { githubMcpToolConfig: config.githubMcpToolConfig } + : {}), requestExitPlanMode: !!config.onExitPlanModeRequest, requestAutoModeSwitch: !!config.onAutoModeSwitchRequest, hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)), workingDirectory: config.workingDirectory, + additionalDirectories: config.additionalDirectories, streaming: config.streaming, includeSubAgentStreamingEvents: config.includeSubAgentStreamingEvents ?? true, ...(this.onGitHubTelemetry != null @@ -1570,6 +1608,7 @@ export class CopilotClient { mcpOAuthTokenStorage: config.mcpOAuthTokenStorage, envValueMode: "direct", customAgents: toWireCustomAgents(config.customAgents), + customAgentsLocalOnly: config.customAgentsLocalOnly, defaultAgent: config.defaultAgent, agent: config.agent, configDir: config.configDirectory, @@ -1586,6 +1625,7 @@ export class CopilotClient { pluginDirectories: config.pluginDirectories, instructionDirectories: config.instructionDirectories, disabledSkills: config.disabledSkills, + disabledMcpServers: config.disabledMcpServers, infiniteSessions: config.infiniteSessions, memory: config.memory, gitHubToken: config.gitHubToken, @@ -1593,6 +1633,7 @@ export class CopilotClient { cloud: config.cloud, expAssignments: config.expAssignments, enableManagedSettings: config.enableManagedSettings, + managedSettings: config.managedSettings, }); const { @@ -1663,6 +1704,23 @@ export class CopilotClient { * ``` */ async resumeSession(sessionId: string, config: ResumeSessionConfig): Promise { + return this.resumeSessionInternal(sessionId, config); + } + + /** @internal */ + async resumeSessionForExtension( + sessionId: string, + config: ResumeSessionConfig, + factories?: FactoryHandle[] + ): Promise { + return this.resumeSessionInternal(sessionId, config, factories); + } + + private async resumeSessionInternal( + sessionId: string, + config: ResumeSessionConfig, + factories?: FactoryHandle[] + ): Promise { if (!this.connection) { await this.start(); } @@ -1674,11 +1732,16 @@ export class CopilotClient { this.connection!, undefined, this.onGetTraceContext, - { mcpAuthHandler: config.onMcpAuthRequest } + { + mcpAuthHandler: config.onMcpAuthRequest, + managedSettingsEnabled: + config.enableManagedSettings === true || config.managedSettings !== undefined, + } ); session.registerTools(config.tools); session.registerCanvases(config.canvases); session.registerCommands(config.commands); + session.registerFactories(factories); const { wireProvider: bearerWireProvider, wireProviders: bearerWireProviders, @@ -1704,7 +1767,9 @@ export class CopilotClient { session.registerHooks(config.hooks); } - config = { ...this.configDefaultsForMode(), ...config }; + const modeDefaults = this.configDefaultsForMode(); + config = { ...modeDefaults, ...config }; + config.customAgentsLocalOnly ??= modeDefaults.customAgentsLocalOnly; config.systemMessage = this.getSystemMessageConfigForMode(config.systemMessage); const { wirePayload: wireSystemMessage, transformCallbacks } = extractTransformCallbacks( @@ -1730,6 +1795,7 @@ export class CopilotClient { model: config.model, reasoningEffort: config.reasoningEffort, reasoningSummary: config.reasoningSummary, + isExperimentalMode: this.experimentalModeForMode(config.enableExperimentalMode), contextTier: config.contextTier, systemMessage: wireSystemMessage, availableTools: toolFilterOptions.availableTools, @@ -1738,6 +1804,7 @@ export class CopilotClient { enableSessionTelemetry: config.enableSessionTelemetry, excludedBuiltinAgents: config.excludedBuiltinAgents, enableCitations: config.enableCitations, + enableFileChangeTracking: config.enableFileChangeTracking, sessionLimits: config.sessionLimits, tools: config.tools?.map((tool) => ({ name: tool.name, @@ -1747,9 +1814,11 @@ export class CopilotClient { skipPermission: tool.skipPermission, defer: tool.defer, metadata: tool.metadata, + isTerminal: tool.isTerminal, })), toolSearch: config.toolSearch, canvases: config.canvases?.map((canvas) => canvas.declaration), + factories: factories?.map((factory) => factory.meta), requestCanvasRenderer: config.requestCanvasRenderer, requestExtensions: config.requestExtensions, extensionSdkPath: config.extensionSdkPath, @@ -1770,10 +1839,14 @@ export class CopilotClient { requestUserInput: !!config.onUserInputRequest, requestElicitation: !!config.onElicitationRequest, ...(config.enableMcpApps ? { requestMcpApps: true } : {}), + ...(config.githubMcpToolConfig != null + ? { githubMcpToolConfig: config.githubMcpToolConfig } + : {}), requestExitPlanMode: !!config.onExitPlanModeRequest, requestAutoModeSwitch: !!config.onAutoModeSwitchRequest, hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)), workingDirectory: config.workingDirectory, + additionalDirectories: config.additionalDirectories, configDir: config.configDirectory, enableConfigDiscovery: config.enableConfigDiscovery, skipEmbeddingRetrieval: config.skipEmbeddingRetrieval, @@ -1793,12 +1866,14 @@ export class CopilotClient { mcpOAuthTokenStorage: config.mcpOAuthTokenStorage, envValueMode: "direct", customAgents: toWireCustomAgents(config.customAgents), + customAgentsLocalOnly: config.customAgentsLocalOnly, defaultAgent: config.defaultAgent, agent: config.agent, skillDirectories: config.skillDirectories, pluginDirectories: config.pluginDirectories, instructionDirectories: config.instructionDirectories, disabledSkills: config.disabledSkills, + disabledMcpServers: config.disabledMcpServers, infiniteSessions: config.infiniteSessions, memory: config.memory, disableResume: config.suppressResumeEvent, @@ -1808,6 +1883,7 @@ export class CopilotClient { openCanvases: config.openCanvases, expAssignments: config.expAssignments, enableManagedSettings: config.enableManagedSettings, + managedSettings: config.managedSettings, }); const { workspacePath, capabilities, openCanvases } = response as { @@ -2823,6 +2899,17 @@ export class CopilotClient { // — the runtime calls into a single handler for the whole connection. registerClientGlobalApiHandlers(this.connection, this.clientGlobalHandlers); + // `hooks.invoke` is an internal RPC method: the runtime calls it to + // invoke a hook callback on the client. Route each call to the matching + // session's dispatcher. Not part of the public ClientGlobalApiHandlers + // interface because HookInvokeRequest/HookType are internal types. + this.connection.onRequest( + "hooks.invoke", + async (params: { sessionId: string; hookType: string; input: unknown }) => { + return await this.handleHooksInvoke(params); + } + ); + this.connection.onClose(() => { this.state = "disconnected"; }); diff --git a/nodejs/src/extension.ts b/nodejs/src/extension.ts index 72bde93bd..c3ae0fd87 100644 --- a/nodejs/src/extension.ts +++ b/nodejs/src/extension.ts @@ -6,10 +6,10 @@ import { CopilotClient } from "./client.js"; import type { CopilotSession } from "./session.js"; import { defaultJoinSessionPermissionHandler, - type ExtensionInfo, type PermissionHandler, type ResumeSessionConfig, } from "./types.js"; +import type { FactoryHandle } from "./factory.js"; export { Canvas, @@ -27,9 +27,42 @@ export type JoinSessionConfig = Omit< "onPermissionRequest" | "extensionSdkPath" > & { onPermissionRequest?: PermissionHandler; + /** + * Factory handles to register when the extension joins the session. + * + * @experimental Part of the experimental Agent Factories surface and may + * change or be removed in future SDK or CLI releases. + */ + factories?: FactoryHandle[]; }; -export type { ExtensionInfo }; +export type { ExtensionInfo, FactoryLimits, FactoryMeta } from "./types.js"; +export { + defineFactory, + FactoryResumeError, + isFactoryRunTerminal, + type RunOptions, + type ResumeOptions, + type FactoryResumeErrorCode, + type SessionFactoryApi, + type FactoryAgentOptions, + type FactoryContext, + type FactoryDefinition, + type FactoryHandle, + type FactoryJsonSchema, + type JsonValue, + type FactoryPipelineStage, + type FactoryStepOptions, + type FactoryRunResult, + type FactoryRunStatus, + type FactoryRunSummary, + type FactoryRunDetail, + type FactoryProgressPage, + type FactoryProgressLine, + type FactoryPhaseObservation, + type FactoryPhaseStatus, + type FactoryAgentSummary, +} from "./factory.js"; /** * Joins the current foreground session. @@ -58,14 +91,22 @@ export async function joinSession(config: JoinSessionConfig = {}): Promise = new Set([ + "completed", + "halted", + "cancelled", + "error", +]); + +/** + * Whether a factory run status is terminal. + * + * @experimental Part of the experimental Agent Factories surface and may + * change or be removed in future SDK or CLI releases. + */ +export function isFactoryRunTerminal(status: FactoryRunStatus): boolean { + return FACTORY_TERMINAL_STATUSES.has(status); +} + +declare const factoryHandleBrand: unique symbol; + +/** A value that can be represented losslessly on the SDK JSON wire. */ +export type JsonValue = + | null + | boolean + | number + | string + | JsonValue[] + | { [key: string]: JsonValue }; + +/** + * Conservative JSON shape language accepted by the Agent Factories surface, for + * both structured factory agent output and a factory's declared `argsSchema`. + * + * This is a best-effort structural guard — used to decide whether a subagent's + * structured output should be accepted or retried, and whether a caller's + * factory `args` match the declared shape — **not** a full JSON Schema + * validator. Only these keywords are honored: `type`, `required`, `enum`, + * `const`, recursive `properties`/`items`, and `anyOf`/`oneOf`/`allOf`. A `type` + * is one of `null`, `boolean`, `integer`, `number`, `string`, `array`, or + * `object`, or a non-empty array of those (for example `["object", "null"]`). + * + * Everything else is **ignored, not enforced**. In particular, string + * constraints (`pattern`, `minLength`, `maxLength`, `format`), numeric ranges + * (`minimum`, `maximum`), `additionalProperties`, and boolean (`true`/`false`) + * schemas do not reject non-conforming output. `oneOf` is treated like `anyOf` + * (at least one branch must match) rather than strict exactly-one. Author + * schemas within this subset; do not rely on unsupported constraints for + * correctness. + * + * @experimental Part of the experimental Agent Factories surface and may + * change or be removed in future SDK or CLI releases. + */ +export type FactoryJsonSchema = { [key: string]: JsonValue }; + +/** + * Options for one factory-scoped subagent call. + * + * @experimental Part of the experimental Agent Factories surface and may + * change or be removed in future SDK or CLI releases. + */ +export interface FactoryAgentOptions { + label?: string; + schema?: FactoryJsonSchema; + model?: string; + reasoningEffort?: string; + contextTier?: ContextTier; + agent?: string; +} + +export const FACTORY_AGENT_OPTION_KEYS = [ + "label", + "schema", + "model", + "reasoningEffort", + "contextTier", + "agent", +] as const; + +/** + * Options for a durable factory step. + * + * @experimental Part of the experimental Agent Factories surface and may + * change or be removed in future SDK or CLI releases. + */ +export interface FactoryStepOptions { + /** Skip the journal and always invoke the producer. */ + volatile?: boolean; +} + +/** + * One stage in a per-item factory pipeline. + * + * @experimental Part of the experimental Agent Factories surface and may + * change or be removed in future SDK or CLI releases. + */ +export type FactoryPipelineStage = ( + previous: TInput, + item: unknown, + index: number +) => Promise | TResult; + +/** + * Context passed to an extension-authored factory body. + * + * @experimental Part of the experimental Agent Factories surface and may + * change or be removed in future SDK or CLI releases. + */ +export interface FactoryContext { + /** Stable identifier for the current factory run. */ + readonly runId: string; + /** Spawn and await one factory-scoped subagent. */ + agent(prompt: string, options?: FactoryAgentOptions): Promise; + /** Memoize an arbitrary producer under a stable author-supplied key. */ + step( + key: string, + producer: () => Promise | JsonValue, + options?: FactoryStepOptions + ): Promise; + /** + * Run thunks concurrently and await all of them. + * + * A thunk that throws becomes `null` in the result array, so one failed + * item does not lose the rest. Cancellation and hard runtime failures + * (`ResponseError`, `ConnectionError`) are the exception: those propagate + * and reject the whole call, because they mean the run itself is in + * trouble rather than one item having failed. + */ + parallel( + thunks: Array<() => Promise | TResult> + ): Promise>; + /** + * Run each item through every stage without barriers between stages. + * + * A stage that throws drops that item to `null` and skips its remaining + * stages. As with {@link FactoryContext.parallel}, cancellation and hard + * runtime failures propagate instead of being recorded per item. + */ + pipeline(items: unknown[], ...stages: FactoryPipelineStage[]): Promise; + /** Start a named factory progress phase. */ + phase(title: string): void; + /** Emit a factory progress line. */ + log(message: string): void; + /** Reject because nested factories are not supported. */ + factory(name: string, args?: JsonValue): Promise; + /** Caller-supplied input, forwarded verbatim. */ + args: TArgs; + /** + * The session instance returned by `joinSession`. It refuses calls that + * start or resume a factory run. + */ + session: CopilotSession; + /** Cooperative cancellation signal for the current factory run. */ + signal: AbortSignal; +} + +/** + * Definition accepted by {@link defineFactory}. + * + * @experimental Part of the experimental Agent Factories surface and may + * change or be removed in future SDK or CLI releases. + */ +export interface FactoryDefinition< + TArgs extends JsonValue = JsonValue, + TResult extends JsonValue | void = JsonValue | void, +> { + meta: FactoryMeta; + run(context: FactoryContext): Promise; +} + +/** + * A deeply immutable view of a value. + * + * `defineFactory` deep-freezes the metadata it stores, so the handle's view of + * it has to be readonly all the way down or `handle.meta.name = "..."` and + * `handle.meta.phases.push(...)` would compile and then throw at runtime. + */ +type DeepReadonly = T extends (infer U)[] + ? readonly DeepReadonly[] + : T extends object + ? { readonly [K in keyof T]: DeepReadonly } + : T; + +/** + * Opaque reusable reference to a defined factory. + * + * @experimental Part of the experimental Agent Factories surface and may + * change or be removed in future SDK or CLI releases. + */ +export interface FactoryHandle< + TArgs extends JsonValue = JsonValue, + TResult extends JsonValue | void = JsonValue | void, +> { + readonly meta: DeepReadonly; + readonly [factoryHandleBrand]: { + readonly args: TArgs; + readonly result: TResult; + }; +} + +/** + * Options for invoking a factory. + * + * @experimental Part of the experimental Agent Factories surface and may + * change or be removed in future SDK or CLI releases. + */ +export interface RunOptions { + /** Input surfaced as `context.args`. */ + args?: TArgs; + /** Optional per-invocation resource ceiling overrides. */ + limits?: FactoryLimits; + /** + * Prior run whose persisted identity, arguments, journal, and accounting should be resumed. + * + * @deprecated Use {@link SessionFactoryApi.resume} instead. + */ + resumeFromRunId?: string; +} + +/** + * Options for resuming a factory run by ID. + * + * @experimental Part of the experimental Agent Factories surface and may + * change or be removed in future SDK or CLI releases. + */ +export interface ResumeOptions { + /** Optional per-invocation resource ceiling overrides. */ + limits?: FactoryLimits; +} + +/** + * Machine-readable pre-execution factory resume failure. + * + * @experimental Part of the experimental Agent Factories surface and may + * change or be removed in future SDK or CLI releases. + */ +export type FactoryResumeErrorCode = + | "not_found" + | "non_resumable" + | "already_active" + | "factory_already_running" + | "factory_limits_invalid" + | "factory_session_disposed" + | "factory_storage_unavailable" + | "factory_storage_corrupt"; + +/** + * Friendly factory API exposed on a session. + * + * @experimental Part of the experimental Agent Factories surface and may + * change or be removed in future SDK or CLI releases. + */ +export interface SessionFactoryApi { + /** + * Run a registered factory and resolve with its run envelope. + * + * The envelope is returned for every outcome, including `error`, `halted`, + * and `cancelled` — inspect `status` and read `result` only when the run + * completed. SDK-initiated runs do not request permission, so they have no + * declined outcome. The model's `run_factory` tool requests permission + * before a durable row exists; declining it creates no run row. Failures + * that occur before a run exists (such as an unknown factory or attempting + * to start a run while the session is at its active top-level run limit) + * still reject. + */ + run(name: string, options?: RunOptions): Promise; + run( + factory: FactoryHandle, + options?: RunOptions + ): Promise; + /** + * Resume a run from its persisted factory name, arguments, journal, and accounting. + * + * Resolves with the run envelope like {@link SessionFactoryApi.run}. + * SDK-initiated resumes do not request permission. A pre-execution failure + * with a documented resume code rejects with {@link FactoryResumeError}. + */ + resume(runId: string, options?: ResumeOptions): Promise; + /** Read the latest durable envelope for a factory run. */ + getRun(runId: string): Promise; + /** + * Wait for a run to settle and resolve with its terminal envelope. + * + * Resolves as soon as the run reaches `completed`, `error`, `halted`, or + * `cancelled`, and resolves immediately when it has already settled. A + * terminal envelope is final, so the resolved value never changes + * afterwards. + * + * This watches the run's `factory.run_updated` invalidation events and + * periodically re-reads the durable envelope so a missed event cannot + * leave the wait hanging. Pass a `signal` to stop waiting; aborting rejects + * and has no effect on the run itself, which keeps executing. Use + * {@link SessionFactoryApi.cancel} to actually stop it. + */ + waitForRun(runId: string, options?: { signal?: AbortSignal }): Promise; + /** + * List the newest default page of this session's durable factory runs. + */ + listRuns(): Promise; + /** Read durable phases, direct agents, and the latest progress tail for a run. */ + getRunDetail(runId: string): Promise; + /** Page durable progress forward, backward, or from the latest tail. */ + getRunProgress( + runId: string, + options?: Omit + ): Promise; + /** Cancel a factory run and return its terminal envelope. */ + cancel(runId: string): Promise; +} + +/** + * Error thrown when a factory cannot be resumed before execution begins. + * + * @experimental Part of the experimental Agent Factories surface and may + * change or be removed in future SDK or CLI releases. + */ +export class FactoryResumeError extends Error { + constructor( + public readonly code: FactoryResumeErrorCode, + message: string + ) { + super(message); + this.name = "FactoryResumeError"; + } +} + +interface StoredFactory { + meta: FactoryMeta; + run(context: FactoryContext): Promise; +} + +const factoryHandles = new WeakMap(); + +/** Maximum accepted factory timeout in seconds, derived from Node's maximum timer delay. */ +const MAX_FACTORY_TIMEOUT_SECONDS = 2_147_483.647; +const NANO_AIU_PER_AIU = 1_000_000_000; + +function deepFreeze(value: T): T { + if (value !== null && typeof value === "object" && !Object.isFrozen(value)) { + Object.freeze(value); + for (const nested of Object.values(value)) { + deepFreeze(nested); + } + } + return value; +} + +function validateLimits(meta: FactoryMeta): void { + const limits = meta.limits; + if (!limits) { + return; + } + + for (const field of ["maxConcurrentSubagents", "maxTotalSubagents"] as const) { + const value = limits[field]; + if (value !== undefined && (!Number.isInteger(value) || value <= 0)) { + throw new Error(`Factory limit "${field}" must be a positive integer`); + } + } + + if ( + limits.timeoutSeconds !== undefined && + (!Number.isFinite(limits.timeoutSeconds) || limits.timeoutSeconds <= 0) + ) { + throw new Error( + 'Factory limit "timeoutSeconds" must be a positive, finite number of seconds' + ); + } + if ( + limits.timeoutSeconds !== undefined && + limits.timeoutSeconds > MAX_FACTORY_TIMEOUT_SECONDS + ) { + throw new Error( + `Factory limit "timeoutSeconds" must not exceed ${MAX_FACTORY_TIMEOUT_SECONDS} seconds` + ); + } + + if (limits.maxAiCredits !== undefined) { + const maxNanoAiu = Math.round(limits.maxAiCredits * NANO_AIU_PER_AIU); + if ( + !Number.isFinite(limits.maxAiCredits) || + limits.maxAiCredits <= 0 || + !Number.isSafeInteger(maxNanoAiu) || + maxNanoAiu < 1 + ) { + throw new Error( + 'Factory limit "maxAiCredits" must be a positive, finite number that rounds to a safe positive integer nano-AIU ceiling' + ); + } + } +} + +function validatePhases(meta: FactoryMeta): void { + const titles = new Set(); + for (const phase of meta.phases) { + if (phase.title.trim().length === 0) { + throw new Error("Factory phase titles must not be empty"); + } + if (titles.has(phase.title)) { + throw new Error(`Factory phase title "${phase.title}" is declared more than once`); + } + titles.add(phase.title); + } +} + +/** + * Defines an extension-authored factory and returns an opaque registration handle. + * + * @experimental Part of the experimental Agent Factories surface and may + * change or be removed in future SDK or CLI releases. + */ +export function defineFactory< + TArgs extends JsonValue = JsonValue, + TResult extends JsonValue | void = JsonValue | void, +>(definition: FactoryDefinition): FactoryHandle { + // Snapshot before validating so post-registration mutation of the caller's + // object cannot slip past the authoring-boundary checks. + const meta = deepFreeze(structuredClone(definition.meta)); + validateLimits(meta); + validatePhases(meta); + + const stored: StoredFactory = { + meta, + run: definition.run, + }; + const handle = Object.freeze({ meta }) as unknown as FactoryHandle; + + factoryHandles.set(handle, stored); + return handle; +} + +/** @internal */ +export function getFactoryDefinition(handle: FactoryHandle): StoredFactory { + const definition = factoryHandles.get(handle); + if (!definition) { + throw new Error("Invalid factory handle"); + } + return definition; +} diff --git a/nodejs/src/generated/rpc.ts b/nodejs/src/generated/rpc.ts index cf009115e..cefc8ef4d 100644 --- a/nodejs/src/generated/rpc.ts +++ b/nodejs/src/generated/rpc.ts @@ -7,6 +7,16 @@ import type { MessageConnection } from "vscode-jsonrpc/node.js"; import type { AbortReason, Attachment, ContextTier, EmbeddedBlobResourceContents, EmbeddedTextResourceContents, McpServerSource, McpServerStatus, PermissionPromptRequest, PermissionRule, ReasoningSummary, SessionEvent, SessionLimitsConfig, SessionMode, ShutdownType, SkillSource, UserToolSessionApproval, Verbosity } from "./session-events.js"; +/** A value that can be represented losslessly on the SDK JSON wire. */ +export type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue }; + +/** + * A value that lives only in this process and never crosses the JSON-RPC + * boundary, such as a callback or a host object handle. + * @internal + */ +export type OpaqueInProcessValue = unknown; + /** * Initial authentication info for the session. * @@ -68,6 +78,27 @@ export type AgentInfoSource = | "plugin" /** Agent built into the Copilot runtime. */ | "builtin"; +/** + * Controls whether built-in agents and authored prompt text are included. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "AgentListRequest". + */ +/** @experimental */ +export type AgentListRequest = + | { + [k: string]: unknown | undefined; + } + | { + /** + * When true, request the session's configured built-in agents alongside custom agents. Listing applies feature, context, inclusion, exclusion, and user-disabled-agent policy, but does not evaluate transient invocation requirements such as model availability. Built-in metadata may be omitted when the session cannot project it, such as a relay session. + */ + includeBuiltInAgents?: boolean; + /** + * When true, request authored base prompt text on each AgentInfo. Prompt text may be omitted when unavailable, such as for agents projected through a relay session. + */ + includePrompt?: boolean; + }; /** * Process kind tag for the registry entry * @@ -238,6 +269,22 @@ export type AuthInfoType = | "token" /** Authentication from a Copilot API token. */ | "copilot-api-token"; +/** + * JSON Schema for canvas open input + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CanvasJsonSchema". + */ +/** @experimental */ +export type CanvasJsonSchema = JsonValue; +/** + * Provider-supplied action result. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CanvasActionInvokeResult". + */ +/** @experimental */ +export type CanvasActionInvokeResult = JsonValue; /** * Coarse command category for grouping and behavior: runtime built-in, skill-backed command, or SDK/client-owned command * @@ -260,6 +307,31 @@ export type SlashCommandKind = */ /** @experimental */ export type SlashCommandInputCompletion = /** Input should complete filesystem directories. */ "directory"; +/** + * Optional filters controlling which command sources to include in the listing. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "CommandsListRequest". + */ +/** @experimental */ +export type CommandsListRequest = + | { + [k: string]: unknown | undefined; + } + | { + /** + * Include runtime built-in commands + */ + includeBuiltins?: boolean; + /** + * Include enabled user-invocable skills and commands + */ + includeSkills?: boolean; + /** + * Include commands registered by protocol clients, including SDK clients and extensions + */ + includeClientCommands?: boolean; + }; /** * Result of the queued command execution. * @@ -372,6 +444,35 @@ export type DebugCollectLogsResultKind = | "archive" /** A directory containing redacted files was written. */ | "directory"; + +/** @experimental */ +export type DisableBypassPermissionsMode = "disable"; +/** + * Persisted extension discovery source + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "DiscoveredExtensionSource". + */ +/** @experimental */ +export type DiscoveredExtensionSource = + /** Extension discovered from the user's extensions directory. */ + | "user" + /** Extension contributed by an installed plugin. */ + | "plugin"; +/** + * Effective extension loading and agent-management mode + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "DiscoveredExtensionMode". + */ +/** @experimental */ +export type DiscoveredExtensionMode = + /** Extensions are not loaded. */ + | "disabled" + /** Extensions are loaded, but the agent cannot create, reload, or manage them. */ + | "load_only" + /** Extensions are loaded and the agent can create, reload, and manage them. */ + | "load_and_augment"; /** * Server transport type: stdio, http, sse (deprecated), or memory * @@ -409,7 +510,19 @@ export type EventsAgentScope = /** Return events from all agents. */ | "all"; /** - * Cursor status: 'ok' means the cursor was applied successfully; 'expired' means the cursor referred to an event that no longer exists in history (e.g. truncated or compacted away) and the read started from the beginning of the remaining history. + * Direction to page through the session's persisted event history. 'forward' pages from the cursor toward newer events; 'backward' returns the newest window first (tail-first) and pages toward older events. Events within a returned batch are always chronological (oldest-to-newest), even for a backward read. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "EventsReadDirection". + */ +/** @experimental */ +export type EventsReadDirection = + /** Page from the cursor toward newer events (default). */ + | "forward" + /** Tail-first: return the newest events and page toward older events. */ + | "backward"; +/** + * Cursor status: 'ok' means the cursor was applied successfully; 'expired' means the cursor referred to an event that no longer exists in history (e.g. truncated or compacted away) and the read fell back to a boundary of the remaining history (the beginning for a forward read, the tail for a backward read). The fallback page is a fresh boundary snapshot, not a continuation of the requested cursor, so it may overlap already-rendered events; on 'expired' a consumer should reset/rebase its pagination state (or deduplicate by event id) before continuing from the returned cursor. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "EventsCursorStatus". @@ -510,17 +623,53 @@ export type ExternalToolTextResultForLlmContentResourceDetails = | EmbeddedTextResourceContents | EmbeddedBlobResourceContents; /** - * Kind of factory progress line. + * Execution-critical factory storage operation. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "FactoryDurableOperation". + */ +/** @experimental */ +export type FactoryDurableOperation = + /** Creating the durable run and declared phases. */ + | "createRun" + /** Persisting the transition to running. */ + | "markRunStarted" + /** Persisting the terminal run envelope. */ + | "finishRun" + /** Persisting subagent admission accounting. */ + | "reserveAgent" + /** Rolling back an uncommitted subagent admission. */ + | "releaseAgent" + /** Persisting an idempotent model-usage charge. */ + | "chargeCredit" + /** Persisting active execution time. */ + | "addElapsed" + /** Reading the authoritative AI-credit total. */ + | "reconcileCreditTotal" + /** Reading a journal entry without treating storage failure as a cache miss. */ + | "journalGet" + /** Persisting a journal entry before reporting success. */ + | "journalPut"; +/** + * Current or terminal state of a factory run. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "FactoryLogLineKind". + * via the `definition` "FactoryRunStatus". */ /** @experimental */ -export type FactoryLogLineKind = - /** A narrator log line. */ - | "log" - /** A named factory phase marker. */ - | "phase"; +export type FactoryRunStatus = + /** The run was minted and is awaiting approval. */ + | "pending" + /** The run is executing. */ + | "running" + /** The run completed successfully. */ + | "completed" + /** The run was interrupted while resource budget remained. */ + | "halted" + /** The run was cancelled before completion. */ + | "cancelled" + /** The factory body failed or reached a cumulative resource ceiling. */ + | "error"; /** * Machine-readable factory run failure. * @@ -551,6 +700,29 @@ export type FactoryRunFailure = */ reason: string; type: "factory_resume_declined"; + } + | { + /** + * Stable failure code. + */ + code: string; + operation: FactoryDurableOperation; + /** + * Factory run identifier. + */ + runId: string; + type: "factory_durable_failure"; + } + | { + /** + * Factory run identifier. + */ + runId: string; + /** + * Confirmed usage in nano-AIU, representing the floor of what the run spent. + */ + drainedNanoAiu: number; + type: "factory_accounting_incomplete"; }; /** * Cumulative resource ceiling that stopped a factory run. @@ -562,28 +734,38 @@ export type FactoryRunFailure = export type FactoryRunFailureKind = /** The run admitted the approved maximum total number of subagents. */ | "maxTotalSubagents" - /** The run reached the approved timeout deadline. */ - | "timeout"; + /** The run reached the approved accumulated active-execution time in seconds. */ + | "timeoutSeconds" + /** The run's settled subagent model usage exceeded the approved AI-credit ceiling, or no headroom remained for another subagent. */ + | "maxAiCredits"; /** - * Current or terminal state of a factory run. + * Kind of factory progress line. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "FactoryRunStatus". + * via the `definition` "FactoryLogLineKind". */ /** @experimental */ -export type FactoryRunStatus = - /** The run was minted and is awaiting approval. */ +export type FactoryLogLineKind = + /** A narrator log line. */ + | "log" + /** A named factory phase marker. */ + | "phase"; +/** + * Derived lifecycle state of a factory phase. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "FactoryPhaseStatus". + */ +/** @experimental */ +export type FactoryPhaseStatus = + /** The phase has not been entered yet. */ | "pending" - /** The run is executing. */ - | "running" - /** The run completed successfully. */ + /** The phase is currently entered and accumulating active time. */ + | "active" + /** The phase was entered and has since been closed. */ | "completed" - /** The run was interrupted while resource budget remained. */ - | "halted" - /** The run was cancelled before completion. */ - | "cancelled" - /** The factory body failed or reached a cumulative resource ceiling. */ - | "error"; + /** The phase was never entered because a later phase was entered or the run reached a terminal state. */ + | "skipped"; /** * Content filtering mode to apply to all tools, or a map of tool name to content filtering mode. * @@ -596,6 +778,112 @@ export type FilterMapping = [k: string]: ContentFilterMode; } | ContentFilterMode; +/** + * Optional compaction parameters. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "HistoryCompactRequest". + */ +/** @experimental */ +export type HistoryCompactRequest = + | { + [k: string]: unknown | undefined; + } + | { + /** + * Optional user-provided instructions to focus the compaction summary + */ + customInstructions?: string; + /** + * What initiated this compaction request, recorded as the `trigger` on the persisted `session.compaction_start` / `session.compaction_complete` events. When absent, the compaction is persisted without trigger attribution (initiator unknown). + */ + trigger?: /** User-requested compaction, e.g. the /compact command or a direct history.compact call. */ + | "manual" + /** Compaction requested while switching to a model with a smaller context window. */ + | "model_switch"; + /** + * Context window token limit this compaction is targeting, recorded as the `tokenLimit` on the persisted `session.compaction_start` / `session.compaction_complete` events. Set it when the compaction targets a window other than the compacting model's own, e.g. switching to a model with a smaller context window: the compaction still runs on the current model, so the limit that motivated it would otherwise be lost. When absent, the events record the compacting model's own resolved limit. Attribution metadata only - it does not change how much the compaction removes. + */ + tokenLimit?: number; + }; +/** + * Reason a captured file was not restored. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "HistoryFileRestoreSkipReason". + */ +/** @experimental */ +export type HistoryFileRestoreSkipReason = + /** The file changed after Copilot's last captured write. */ + | "user-modified" + /** A faithful preimage was not captured. */ + | "skipped-capture"; +/** + * Reason a rewind read (rewind points, file-restore preview, or session diff) could not be answered from the session's file-change captures. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "HistoryRewindUnavailableReason". + */ +/** @experimental */ +export type HistoryRewindUnavailableReason = + /** The session did not opt into file-change tracking before its first turn. */ + | "file-change-tracking-disabled" + /** The session still has work that may mutate files or history. Transient: the same request succeeds once the session settles, so callers should retry rather than treat it as a failure. */ + | "session-busy" + /** Remote-backed rewind routing is not supported. */ + | "unsupported-remote-session"; +/** + * Aggregate file change represented by a rewind preview. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "HistoryRewindChangeType". + */ +/** @experimental */ +export type HistoryRewindChangeType = + /** The discarded turns created the file. */ + | "created" + /** The discarded turns deleted the file. */ + | "deleted" + /** The discarded turns modified the file. */ + | "modified"; +/** + * Scope of a rewind operation. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "HistoryRewindMode". + */ +/** @experimental */ +export type HistoryRewindMode = + /** Discard conversation events while leaving files unchanged. */ + | "conversation" + /** Discard conversation events and restore captured files changed by those turns. */ + | "conversation-and-files"; +/** + * Outcome of a rewind request. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "HistoryRewindOutcome". + */ +/** @experimental */ +export type HistoryRewindOutcome = + /** The requested rewind completed; reachable in either mode. */ + | "success" + /** The session still has work that may mutate files or history; reachable in either mode. */ + | "session-busy" + /** A conversation-and-files rewind was requested for a session that did not enable capture; conversation-only rewinds never produce this. */ + | "file-change-tracking-disabled" + /** Remote-backed rewind routing is not supported; reachable in either mode. */ + | "unsupported-remote-session" + /** File restore failed and all applied file changes were rolled back; only conversation-and-files rewinds produce this. */ + | "files-rolled-back" + /** File restore failed and its rollback could not fully restore the pre-rewind state; only conversation-and-files rewinds produce this. */ + | "rollback-incomplete" + /** Conversation truncation failed. In conversation-and-files mode any files that were restored are left in place because conversation history cannot be un-truncated; in conversation-only mode no files are restored. Consult restoredFiles for what, if anything, was applied. */ + | "truncation-failed" + /** The conversation was rewound (and, in conversation-and-files mode, captured files were restored), but persisted checkpoints could not be cleaned up; reachable in either mode. */ + | "checkpoint-cleanup-failed" + /** Files and conversation were rewound, but obsolete file snapshots could not be removed; only conversation-and-files rewinds produce this. */ + | "snapshot-prune-failed"; /** * Hook event name dispatched through the SDK callback transport. * @@ -1023,6 +1311,63 @@ export type SessionContextAttribution = { * Total token count of the current context window the entries are measured against (system message + conversation messages + tool definitions — the same total reported by /context). Divide an entry's `tokens` by this to derive its share. */ totalTokens: number; + /** + * The concrete model id the entire breakdown was tokenized against (feeds the per-model token multiplier). Under `Auto` (Free/Student) this is the resolved model, not the literal `auto` sentinel, so totals are not undercounted. A single-model approximation of a potentially multi-model Auto session. + */ + modelId: string; + /** + * How `modelId` was chosen. Not a closed set — tolerate unknown values. Known values today: `autoResolved` (the model Auto resolved to), `selected` (the user's explicitly selected model), `default` (a fallback before any model is known). + */ + modelSource: string; + /** + * Maximum prompt tokens the resolved model accepts — the denominator for a `##k/###k` context-usage display. Mirrors `SessionContextInfo.promptTokenLimit`. + */ + promptTokenLimit: number; + /** + * Prompt limit plus the model's output reserve: the full context window `categories.freeSpace` and `categories.buffer` are measured against. Mirrors `SessionContextInfo.limit`. + */ + limit: number; + /** + * Output reserve plus the tokens past the buffer-exhaustion blocking threshold. Mirrors `SessionContextInfo.bufferTokens`. + */ + bufferTokens: number; + /** + * Token count at which background compaction starts. Mirrors `SessionContextInfo.compactionThreshold`. + */ + compactionThreshold: number; + /** + * The six normalized `/context` header buckets, computed from the same tokenization as `entries` so the two never disagree. Convenience rollups: `freeSpace` and `buffer` describe window capacity rather than occupied context, so the values do not sum to `totalTokens`. + */ + categories: { + /** + * System prompt tokens, excluding custom instructions. + */ + systemPrompt: number; + /** + * Custom-instructions tokens (0 when none are configured). + */ + customInstructions: number; + /** + * Non-MCP tool-definition tokens. + */ + systemTools: number; + /** + * MCP tool-definition tokens. + */ + mcpTools: number; + /** + * Conversation (user/assistant/tool) message tokens. + */ + messages: number; + /** + * Remaining unused window capacity (clamped at 0). + */ + freeSpace: number; + /** + * Output reserve plus post-blocking-threshold buffer. + */ + buffer: number; + }; /** * Flat list of per-source attribution entries. Group by `kind` and render unrecognized kinds generically. Nesting and rollups are expressed via `parentId`. */ @@ -1195,6 +1540,23 @@ export type ModelPickerPriceCategory = | "high" /** Highest relative token cost tier. */ | "very_high"; +/** + * Optional listing options. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ModelListRequest". + */ +/** @experimental */ +export type ModelListRequest = + | { + [k: string]: unknown | undefined; + } + | { + /** + * If true, bypasses the per-session model list cache and re-fetches from CAPI. + */ + skipCache?: boolean; + }; /** * Provider type. Defaults to "openai" for generic OpenAI-compatible APIs. * @@ -1334,6 +1696,7 @@ export type PermissionDecisionApproveForSessionApproval = | PermissionDecisionApproveForSessionApprovalMemory | PermissionDecisionApproveForSessionApprovalCustomTool | PermissionDecisionApproveForSessionApprovalExtensionManagement + | PermissionDecisionApproveForSessionApprovalFactory | PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess; /** * Approval to persist for this location @@ -1351,7 +1714,54 @@ export type PermissionDecisionApproveForLocationApproval = | PermissionDecisionApproveForLocationApprovalMemory | PermissionDecisionApproveForLocationApprovalCustomTool | PermissionDecisionApproveForLocationApprovalExtensionManagement + | PermissionDecisionApproveForLocationApprovalFactory | PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess; +/** + * Disposition of a permission request as observed by the responding client. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "PermissionDecisionOutcome". + */ +/** @experimental */ +export type PermissionDecisionOutcome = + /** The request was approved automatically without a new human decision. */ + | "auto_approved" + /** The request was denied without an interactive user decision; source records why. */ + | "autopilot_denied" + /** The response came from an interactive user prompt. */ + | "prompted_user"; +/** + * Controlled reason or actor responsible for a permission response. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "PermissionDecisionSource". + */ +/** @experimental */ +export type PermissionDecisionSource = + /** The response followed the auto-approval judge recommendation. */ + | "judge_recommendation" + /** A human supplied the response through an interactive prompt. */ + | "human_response" + /** The host applied a standing policy or override rather than a judge recommendation or human decision. */ + | "host_policy" + /** The host denied the request because no interactive user response was available. */ + | "unattended_fallback"; +/** + * Client surface that submitted a permission response. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "PermissionDecisionSurface". + */ +/** @experimental */ +export type PermissionDecisionSurface = + /** The interactive Copilot CLI terminal UI. */ + | "tui" + /** The non-interactive Copilot CLI prompt mode. */ + | "prompt_mode" + /** The Copilot App client. */ + | "copilot_app" + /** A generic Copilot SDK client. */ + | "sdk"; /** * Tool approval to persist and apply * @@ -1368,6 +1778,7 @@ export type PermissionsLocationsAddToolApprovalDetails = | PermissionsLocationsAddToolApprovalDetailsMemory | PermissionsLocationsAddToolApprovalDetailsCustomTool | PermissionsLocationsAddToolApprovalDetailsExtensionManagement + | PermissionsLocationsAddToolApprovalDetailsFactory | PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess; /** * Whether the location is a git repo or directory @@ -1438,23 +1849,56 @@ export type PermissionsSetApproveAllSource = /** Allow-all was enabled through an RPC caller. */ | "rpc"; /** - * Provider family. Matches the `type` field of a BYOK provider config. + * Optional flags controlling which side effects the reload performs. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "ProviderEndpointType". + * via the `definition` "PluginsReloadRequest". */ /** @experimental */ -export type ProviderEndpointType = - /** OpenAI-compatible endpoint (use the OpenAI client library). */ - | "openai" - /** Azure OpenAI endpoint (use the OpenAI client library with the Azure base URL). */ - | "azure" - /** Anthropic endpoint (use the Anthropic client library). */ - | "anthropic"; -/** - * Wire API to be used, when required for the provider type. - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema +export type PluginsReloadRequest = + | { + [k: string]: unknown | undefined; + } + | { + /** + * Reload MCP server connections after refreshing plugins. Defaults to true. + */ + reloadMcp?: boolean; + /** + * Re-run custom-agent discovery after refreshing plugins. Defaults to true. + */ + reloadCustomAgents?: boolean; + /** + * Re-load user, plugin, and (subject to `deferRepoHooks`) repo hooks. Defaults to true. Has no effect when the host has not registered a hook reloader (e.g. remote sessions). + */ + reloadHooks?: boolean; + /** + * Re-discover and relaunch subprocess extensions (including plugin-shipped extensions) after refreshing plugins. Defaults to true. Has no effect when the session has no active extension controller (e.g. extensions were not requested for the session). + */ + reloadExtensions?: boolean; + /** + * When true, skip repo-level hooks during the hook reload. Use before folder trust is confirmed; load them post-trust via `sessions.loadDeferredRepoHooks`. + */ + deferRepoHooks?: boolean; + }; +/** + * Provider family. Matches the `type` field of a BYOK provider config. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ProviderEndpointType". + */ +/** @experimental */ +export type ProviderEndpointType = + /** OpenAI-compatible endpoint (use the OpenAI client library). */ + | "openai" + /** Azure OpenAI endpoint (use the OpenAI client library with the Azure base URL). */ + | "azure" + /** Anthropic endpoint (use the Anthropic client library). */ + | "anthropic"; +/** + * Wire API to be used, when required for the provider type. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "ProviderEndpointWireApi". */ /** @experimental */ @@ -1475,6 +1919,23 @@ export type ProviderEndpointTransport = | "http" /** WebSocket transport. */ | "websockets"; +/** + * Optional model identifier to scope the endpoint snapshot to. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ProviderGetEndpointRequest". + */ +/** @experimental */ +export type ProviderGetEndpointRequest = + | { + [k: string]: unknown | undefined; + } + | { + /** + * Model identifier the caller intends to use against the returned endpoint. Used to pick the correct wire shape. Omit to use whichever model the session is currently using. + */ + modelId?: string; + }; /** * Attachment union accepted by push input, covering files, directories, GitHub objects, blobs, snippets, and extension context. * @@ -1512,6 +1973,34 @@ export type PushAttachmentGitHubReferenceType = | "pr" /** GitHub discussion reference. */ | "discussion"; +/** + * The UI mode the agent was in when this message was sent. Defaults to the session's current mode. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SendAgentMode". + */ +/** @experimental */ +export type SendAgentMode = + /** The agent is responding interactively to the user. */ + | "interactive" + /** The agent is preparing a plan before making changes. */ + | "plan" + /** The agent is working autonomously toward task completion. */ + | "autopilot" + /** The agent is in shell-focused UI mode. */ + | "shell"; +/** + * How to deliver the message. `enqueue` (default) appends to the message queue. `immediate` interjects during an in-progress turn. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SendMode". + */ +/** @experimental */ +export type SendMode = + /** Append the message to the normal session queue. */ + | "enqueue" + /** Interject the message during the in-progress turn. */ + | "immediate"; /** * Whether this item is a queued user message or a queued slash command / model change * @@ -1562,34 +2051,6 @@ export type RemoteSessionMetadataTaskType = | "cca" /** CLI remote task. */ | "cli"; -/** - * The UI mode the agent was in when this message was sent. Defaults to the session's current mode. - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "SendAgentMode". - */ -/** @experimental */ -export type SendAgentMode = - /** The agent is responding interactively to the user. */ - | "interactive" - /** The agent is preparing a plan before making changes. */ - | "plan" - /** The agent is working autonomously toward task completion. */ - | "autopilot" - /** The agent is in shell-focused UI mode. */ - | "shell"; -/** - * How to deliver the message. `enqueue` (default) appends to the message queue. `immediate` interjects during an in-progress turn. - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "SendMode". - */ -/** @experimental */ -export type SendMode = - /** Append the message to the normal session queue. */ - | "enqueue" - /** Interject the message during the in-progress turn. */ - | "immediate"; /** * Session capability enabled for this session * @@ -1670,6 +2131,20 @@ export type SessionFsSqliteQueryType = | "query" /** Execute INSERT, UPDATE, or DELETE SQL and return affected-row metadata. */ | "run"; +/** + * SQLite transaction failure classification. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionFsSqliteTransactionErrorClass". + */ +/** @experimental */ +export type SessionFsSqliteTransactionErrorClass = + /** SQLite reported BUSY or LOCKED before commit; the transaction was rolled back and may be retried. */ + | "busyOrLocked" + /** The statement, database, or provider failed definitively and must not be retried automatically. */ + | "fatal" + /** The transport failed after the provider may have committed; retrying could duplicate effects. */ + | "postCommitAmbiguous"; /** * Source descriptor for direct repo installs (when marketplace is empty) * @@ -1682,6 +2157,94 @@ export type SessionInstalledPluginSource = | SessionInstalledPluginSourceGitHub | SessionInstalledPluginSourceUrl | SessionInstalledPluginSourceLocal; +/** + * Client population used for the prediction baseline. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionLimitPredictionClientType". + */ +/** @experimental */ +export type SessionLimitPredictionClientType = + /** Interactive CLI sessions where a user can accept, edit, or top up the limit. */ + | "cli-interactive" + /** Prompt/non-interactive CLI sessions where the initial limit must cover more of the run. */ + | "cli-prompt"; +/** + * Baseline fallback level used to create the prediction. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionLimitPredictionSource". + */ +/** @experimental */ +export type SessionLimitPredictionSource = + /** The prediction used the exact resolved model's baseline cell. */ + | "model" + /** The exact model was unavailable, so the prediction used the model family's baseline cell. */ + | "family" + /** No model or family cell was available, so the prediction used the global client-type baseline cell. */ + | "global"; +/** + * Semantic usage tier used for a recommended cap or additional headroom. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionLimitPredictionTier". + */ +/** @experimental */ +export type SessionLimitPredictionTier = + /** Recommended starting tier. */ + | "recommended" + /** Additional headroom for longer-running sessions. */ + | "additional_headroom" + /** Generous headroom for unusually high usage. */ + | "generous_headroom" + /** Maximum available headroom tier. */ + | "maximum_headroom"; +/** + * Parameters for predicting an AI-credit session limit. Omitting `modelId` uses the session's currently selected model. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionLimitPredictionRequest". + */ +/** @experimental */ +export type SessionLimitPredictionRequest = + | { + [k: string]: unknown | undefined; + } + | { + /** + * Optional model identifier override. If omitted, the session's current model is used. + */ + modelId?: string; + clientType?: SessionLimitPredictionClientType; + }; +/** + * Prediction result. Available results include prediction details; unavailable results include an explicit reason. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionLimitPredictionResult". + */ +/** @experimental */ +export type SessionLimitPredictionResult = + | { + prediction: SessionLimitPredictionDetails; + kind: "available"; + } + | { + reason: SessionLimitPredictionUnavailableReason; + kind: "unavailable"; + }; +/** + * Reason a prediction could not be computed. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionLimitPredictionUnavailableReason". + */ +/** @experimental */ +export type SessionLimitPredictionUnavailableReason = + /** The current model is auto and has not resolved to a concrete model yet. */ + | "auto_unresolved" + /** No model was provided and the session does not currently have a selected model. */ + | "no_model"; /** * Local or remote session metadata entry. Narrow on `isRemote` to access source-specific fields. * @@ -1762,6 +2325,30 @@ export type SessionOpenOptionsReasoningSummary = | "concise" /** Request a detailed summary of model reasoning. */ | "detailed"; +/** + * Controls automatic non-interactive profile loading where supported. Explicit initScripts are unaffected. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ShellInitProfile". + */ +/** @experimental */ +export type ShellInitProfile = + /** Disable automatic non-interactive profile loading. Explicit initScripts still run. */ + | "none" + /** Allow automatic non-interactive profile loading when supported. Explicit initScripts still run. */ + | "non-interactive"; +/** + * Supported built-in shells for initialization scripts. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ShellInitScriptShell". + */ +/** @experimental */ +export type ShellInitScriptShell = + /** Source the script in the built-in Bash shell on macOS and Linux. */ + | "bash" + /** Source the script in the built-in PowerShell shell on Windows. */ + | "powershell"; /** * How MCP server environment values are interpreted. * @@ -2240,6 +2827,14 @@ export type WorkspacesWorkspaceDetailsHostType = */ /** @experimental */ export type AccountGetAllUsersResult = AccountAllUsers[]; +/** + * The number of running background agents (task-registry agents) that were cancelled. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionCancelAllBackgroundAgentsResult". + */ +/** @experimental */ +export type SessionCancelAllBackgroundAgentsResult = number; /** * Parameters for aborting the current turn @@ -2465,6 +3060,7 @@ export interface CopilotUserResponseEndpoints { "origin-tracker"?: string; proxy?: string; telemetry?: string; + exp?: string; } /** * Quota snapshot map from the raw Copilot user-response passthrough, with chat, completions, premium-interactions, and other entries. @@ -2989,7 +3585,7 @@ export interface AgentGetCurrentResult { agent?: AgentInfo | null; } /** - * Custom agent metadata, including identifiers, display details, source, tools, model, MCP servers, skills, and file path. + * Agent metadata, including identifiers, display details, source, tools, model, MCP servers, skills, and file path. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "AgentInfo". @@ -2997,7 +3593,7 @@ export interface AgentGetCurrentResult { /** @experimental */ export interface AgentInfo { /** - * Unique identifier of the custom agent + * Name of the agent. Use `id` as the stable selection identifier. */ name: string; /** @@ -3026,7 +3622,7 @@ export interface AgentInfo { */ tools?: string[]; /** - * Preferred model id for this agent. When omitted, inherits the outer agent's model. + * Authored preferred model id for this agent. Runtime model selection may choose a different model; omitted means no authored preference. */ model?: string; /** @@ -3035,15 +3631,19 @@ export interface AgentInfo { * @experimental */ mcpServers?: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }; /** * Skill names preloaded into this agent's context. Omitted means none. */ skills?: string[]; + /** + * Authored base prompt for the agent. Runtime prompt assembly may add dynamic context at invocation time. Omitted from `session.agent.list` unless `includePrompt` is true. + */ + prompt?: string; } /** - * Custom agents available to the session. + * Agents available to the session. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "AgentList". @@ -3051,7 +3651,7 @@ export interface AgentInfo { /** @experimental */ export interface AgentList { /** - * Available custom agents + * Available agents */ agents: AgentInfo[]; } @@ -3312,6 +3912,23 @@ export interface AgentSelectRequest { export interface AgentSelectResult { agent: AgentInfo; } +/** + * An in-memory authored prompt override for an available agent. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "AgentSetPromptRequest". + */ +/** @experimental */ +export interface AgentSetPromptRequest { + /** + * Stable effective agent id. Plugin namespace separators are normalized. + */ + id: string; + /** + * Replacement authored prompt. Empty text is valid. + */ + prompt: string; +} /** * Optional project paths to include when enumerating agent discovery directories. * @@ -3361,6 +3978,32 @@ export interface AllowAllPermissionState { enabled: boolean; mode?: PermissionsAllowAllMode; } +/** + * The running runtime's complete catalog of well-known built-in model IDs, including supported models and additional IDs with built-in metadata. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "BuiltInModelCatalog". + */ +/** @experimental */ +export interface BuiltInModelCatalog { + /** + * Built-in model entries. + */ + models: BuiltInModelCatalogEntry[]; +} +/** + * A well-known model in the runtime's built-in catalog. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "BuiltInModelCatalogEntry". + */ +/** @experimental */ +export interface BuiltInModelCatalogEntry { + /** + * Well-known runtime model ID suitable for `ProviderConfig.modelId` or `ProviderModelConfig.modelId`. This is not necessarily the provider-facing deployment or model name and does not indicate CAPI entitlement or provider availability. + */ + id: string; +} /** * Cancellation result for a user-requested shell command. * @@ -3392,16 +4035,6 @@ export interface CanvasAction { description?: string; inputSchema?: CanvasJsonSchema; } -/** - * JSON Schema for canvas open input - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "CanvasJsonSchema". - */ -/** @experimental */ -export interface CanvasJsonSchema { - [k: string]: unknown | undefined; -} /** * Canvas action invocation parameters. * @@ -3421,19 +4054,7 @@ export interface CanvasActionInvokeRequest { /** * Action input */ - input?: { - [k: string]: unknown | undefined; - }; -} -/** - * Provider-supplied action result. - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "CanvasActionInvokeResult". - */ -/** @experimental */ -export interface CanvasActionInvokeResult { - [k: string]: unknown | undefined; + input?: JsonValue; } /** * Canvas close parameters. @@ -3578,9 +4199,7 @@ export interface OpenCanvasInstance { /** * Input supplied when the instance was opened */ - input?: { - [k: string]: unknown | undefined; - }; + input?: JsonValue; } /** * Canvas open parameters. @@ -3605,9 +4224,7 @@ export interface CanvasOpenRequest { /** * Canvas open input */ - input?: { - [k: string]: unknown | undefined; - }; + input?: JsonValue; } /** * Canvas close parameters sent to the provider. @@ -3680,9 +4297,7 @@ export interface CanvasProviderInvokeActionRequest { /** * Action input */ - input?: { - [k: string]: unknown | undefined; - }; + input?: JsonValue; host?: CanvasHostContext; session?: CanvasSessionContext; } @@ -3713,9 +4328,7 @@ export interface CanvasProviderOpenRequest { /** * Canvas open input */ - input?: { - [k: string]: unknown | undefined; - }; + input?: JsonValue; host?: CanvasHostContext; session?: CanvasSessionContext; } @@ -3891,27 +4504,6 @@ export interface CommandsInvokeRequest { */ input?: string; } -/** - * Optional filters controlling which command sources to include in the listing. - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "CommandsListRequest". - */ -/** @experimental */ -export interface CommandsListRequest { - /** - * Include runtime built-in commands - */ - includeBuiltins?: boolean; - /** - * Include enabled user-invocable skills and commands - */ - includeSkills?: boolean; - /** - * Include commands registered by protocol clients, including SDK clients and extensions - */ - includeClientCommands?: boolean; -} /** * Queued-command request ID and the result indicating whether the host executed it (and whether to stop processing further queued commands). * @@ -4061,9 +4653,7 @@ export interface ConfigureSessionExtensionsParams { * * @internal */ - controller?: { - [k: string]: unknown | undefined; - }; + controller?: OpaqueInProcessValue; } /** * Metadata for a connected remote session. @@ -4160,7 +4750,7 @@ export interface ConnectRequest { */ token?: string; /** - * Opt this connection in to GitHub telemetry forwarding for its lifetime. When set, the runtime forwards every internal telemetry event it emits — across all sessions, plus sessionless events — to this connection over the `gitHubTelemetry.event` notification, in addition to the runtime's normal GitHub/CTS emission (dual-write). Intended for first-party hosts that re-emit the events into their own telemetry stores. Both unrestricted and restricted events are forwarded, each tagged with a `restricted` discriminator; a backstop drops restricted events when restricted telemetry is disabled. + * Opt this connection in to GitHub telemetry forwarding for its lifetime. When set, the runtime forwards every internal telemetry event it emits — across all sessions, plus sessionless events — to this connection over the `gitHubTelemetry.event` notification. Regular events are also written to the runtime's normal GitHub/CTS path (dual-write); host-only compatibility events are forward-only and intentionally skip that path. Intended for first-party hosts that re-emit the events into their own telemetry stores. Both unrestricted and restricted events are forwarded, each tagged with a `restricted` discriminator; a backstop drops restricted events when restricted telemetry is disabled — using the process-global gate for ordinary events and an explicit session-scoped decision for host-only events. */ enableGitHubTelemetryForwarding?: boolean; } @@ -4187,26 +4777,73 @@ export interface ConnectResult { version: string; } /** - * A single large message currently in context. + * Local file system absolute paths within the session working directory to check against its content-exclusion policy. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "ContextHeaviestMessage". + * via the `definition` "ContentExclusionCheckPathsRequest". */ /** @experimental */ -export interface ContextHeaviestMessage { +export interface ContentExclusionCheckPathsRequest { /** - * Stable identifier for this message within the snapshot. + * Local file system absolute paths within the session working directory to check. Results are returned in the same order, including duplicates. */ - id: string; + paths: string[]; +} +/** + * Batch content-exclusion result. Callers must fail closed when policy evaluation is unavailable. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ContentExclusionCheckPathsResult". + */ +/** @experimental */ +export interface ContentExclusionCheckPathsResult { /** - * Human-readable source label, e.g. `tool: bash` or `skill: tmux`. Presentation-only. + * Whether the session's policy service was available for the complete batch. When false, checks is empty and callers must treat every requested path as excluded. */ - label: string; + available: boolean; /** - * Role of the chat message (`user`, `assistant`, or `tool`). + * Per-path decisions in request order. Empty when available is false. */ - role: string; - /** + checks: ContentExclusionPathCheck[]; +} +/** + * Content-exclusion decision for one requested path. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ContentExclusionPathCheck". + */ +/** @experimental */ +export interface ContentExclusionPathCheck { + /** + * The path supplied by the caller. + */ + path: string; + /** + * Whether the session's complete content-exclusion policy excludes the path. + */ + excluded: boolean; +} +/** + * A single large message currently in context. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ContextHeaviestMessage". + */ +/** @experimental */ +export interface ContextHeaviestMessage { + /** + * Stable identifier for this message within the snapshot. + */ + id: string; + /** + * Human-readable source label, e.g. `tool: bash` or `skill: tmux`. Presentation-only. + */ + label: string; + /** + * Role of the chat message (`user`, `assistant`, or `tool`). + */ + role: string; + /** * Token count currently in context for this individual message. */ tokens: number; @@ -4261,7 +4898,7 @@ export interface CurrentToolMetadata { * JSON Schema for tool input */ input_schema?: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }; /** * Whether the tool is loaded on demand via tool search @@ -4404,6 +5041,86 @@ export interface DebugCollectLogsSkippedEntry { */ reason: string; } +/** + * Discovered extension metadata and persistent enablement state. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "DiscoveredExtension". + */ +/** @experimental */ +export interface DiscoveredExtension { + /** + * Source-qualified ID accepted by both server and session extension enablement methods + */ + id: string; + /** + * Human-readable extension name + */ + name: string; + /** + * Absolute path to the extension entry module, suitable for revealing it in a file manager + */ + path: string; + source: DiscoveredExtensionSource; + /** + * Whether this extension's persistent per-ID preference is enabled + */ + enabled: boolean; + plugin?: DiscoveredExtensionPlugin; +} +/** + * Installed plugin that contributes a discovered extension. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "DiscoveredExtensionPlugin". + */ +/** @experimental */ +export interface DiscoveredExtensionPlugin { + /** + * Installed plugin name + */ + name: string; +} +/** + * Extensions discovered from persisted Copilot home state and their effective loading mode. Launch-scoped additional plugins are not included. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "DiscoveredExtensions". + */ +/** @experimental */ +export interface DiscoveredExtensions { + /** + * Discovered user and enabled installed-plugin extensions from persisted Copilot home state + */ + extensions: DiscoveredExtension[]; + mode: DiscoveredExtensionMode; +} +/** + * Source-qualified extension identifiers to persistently disable for future sessions. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "DiscoveredExtensionsDisableRequest". + */ +/** @experimental */ +export interface DiscoveredExtensionsDisableRequest { + /** + * Source-qualified user or plugin extension IDs to disable + */ + ids: string[]; +} +/** + * Source-qualified extension identifiers to persistently enable for future sessions. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "DiscoveredExtensionsEnableRequest". + */ +/** @experimental */ +export interface DiscoveredExtensionsEnableRequest { + /** + * Source-qualified user or plugin extension IDs to enable + */ + ids: string[]; +} /** * MCP server discovered by `mcp.discover`, with config source, optional plugin source, transport type, and enabled state. * @@ -4474,11 +5191,22 @@ export interface EventLogReadRequest { */ max?: number; /** - * Milliseconds to wait for new events when the cursor is at the tail of history. 0 (default) returns immediately even if no events are available. Capped at 30000ms. Ephemeral events that arrive during the wait are delivered in this batch but are NOT replayable on a subsequent read (use a non-zero waitMs in your next call to capture future ephemerals as they happen). + * Milliseconds to wait for new events when the cursor is at the tail of history. 0 (default) returns immediately even if no events are available. Capped at 30000ms. Ephemeral events that arrive during the wait are delivered in this batch but are NOT replayable on a subsequent read (use a non-zero waitMs in your next call to capture future ephemerals as they happen). This applies to forward reads only: a backward read always returns immediately and ignores `waitMs`, because backward paging covers persisted history only while new events append at the tail (the opposite end from a backward page), so no blocking or ephemeral delivery can occur. */ waitMs?: number; types?: EventLogTypes; agentScope?: EventsAgentScope; + /** + * Optional non-empty list of subagent identifiers. When provided, only events owned by one of these agents are returned; ownership recognizes the event envelope's agentId plus legacy data.agentId and data.parentToolCallId markers. This filter takes precedence over agentScope. + * + * @minItems 1 + */ + agentIds?: [string, ...string[]]; + direction?: EventsReadDirection; + /** + * When false, skip ephemeral events entirely and return only durable (persisted) events. History-backfill callers that discard ephemerals anyway should set this so the read is bounded by the durable log length instead of racing the ephemeral ring on a busy session. Defaults to true (ephemerals are interleaved with durable events in creation order). Ignored by backward reads, which always cover persisted history only. + */ + includeEphemeral?: boolean; } /** * Indicates whether the operation succeeded. @@ -4515,15 +5243,15 @@ export interface EventLogTailResult { /** @experimental */ export interface EventsReadResult { /** - * Events are delivered in two batches per read: persisted events first (in append order), then ephemeral events (in seq order). When `waitMs > 0` and the catch-up batches were empty, post-wait events follow the same two-batch ordering. Persisted and ephemeral events do not interleave within a single read. + * Session events for this batch, merged into a single stream in creation order: durable (persisted) events and ephemeral events interleave exactly as they were emitted. Set `includeEphemeral: false` to receive only durable events. Ephemeral events are never replayable once pruned from the in-memory ring, so a consumer that needs them should keep reading with a non-zero `waitMs`. For a backward (tail-first) read, the returned window contains persisted events only, still in chronological (oldest-to-newest) append order. */ events: SessionEvent[]; /** - * Opaque cursor for the next read. Pass back unchanged in the next read.cursor to continue from where this read left off. Always present, even when no events were returned. + * Opaque cursor for the next read. Pass back unchanged in the next read.cursor to continue from where this read left off. Always present, even when no events were returned. For a backward read this cursor pages toward OLDER events; keep passing `direction: backward` with it (the cursor is also self-describing, so backward paging continues correctly). */ cursor: string; /** - * True when the read returned `max` events and more events are available immediately. When false, the next read with a non-zero `waitMs` will block until a new event arrives or the wait expires. + * True when more events are available in the read's direction. For a forward read, true means the batch returned `max` events and more are available immediately. For a backward read, true means older persisted events remain before the returned window. */ hasMore: boolean; cursorStatus: EventsCursorStatus; @@ -4600,10 +5328,63 @@ export interface ExtensionContextPushInput { /** * Caller-supplied JSON payload (required, may be null but not undefined) */ - payload: { - [k: string]: unknown | undefined; + payload: JsonValue; +} +/** + * Opaque integrator-owned process launch profile for one extension entrypoint. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ExtensionLaunchProfile". + */ +/** @experimental */ +export interface ExtensionLaunchProfile { + /** + * Executable used to launch the extension entrypoint. + */ + executable: string; + /** + * Opaque integrator-defined arguments passed to the executable. The runtime does not append the extension entrypoint. + */ + args: string[]; + /** + * Opaque integrator-defined environment variables. Runtime-owned COPILOT_SDK_PATH, SESSION_ID, and COPILOT_EXTENSION_PARENT_PID values take precedence. + */ + env: { + [k: string]: string | undefined; }; } +/** + * A discovered extension entrypoint that the registered integrator may classify and resolve to an opaque launch profile. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ExtensionLaunchProviderResolveRequest". + */ +/** @experimental */ +export interface ExtensionLaunchProviderResolveRequest { + /** + * Source-qualified extension identifier. + */ + id: string; + /** + * Human-readable extension name. + */ + name: string; + /** + * Absolute path to the discovered extension entrypoint. + */ + modulePath: string; + source: ExtensionSource; +} +/** + * The launch profile for a supported entrypoint. Omit launch when the provider does not support the entrypoint. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ExtensionLaunchProviderResolveResult". + */ +/** @experimental */ +export interface ExtensionLaunchProviderResolveResult { + launch?: ExtensionLaunchProfile; +} /** * Extensions discovered for the session, with their current status. * @@ -4671,7 +5452,7 @@ export interface ExternalToolTextResultForLlm { * Optional tool-specific telemetry */ toolTelemetry?: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }; /** * Base64-encoded binary results returned to the model @@ -4711,7 +5492,7 @@ export interface ExternalToolTextResultForLlmBinaryResultsForLlm { * Optional metadata from the producing tool. */ metadata?: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }; } /** @@ -4948,13 +5729,20 @@ export interface FactoryAgentOptions { /** * Optional JSON Schema for structured agent output. */ - schema?: { - [k: string]: unknown | undefined; - }; + schema?: JsonValue; /** * Optional model identifier for the subagent. */ model?: string; + /** + * Optional reasoning effort for the subagent. This field is accepted but not yet honored. + */ + reasoningEffort?: string; + contextTier?: ContextTier; + /** + * Optional custom agent name for the subagent. This field is accepted but not yet honored. + */ + agent?: string; } /** * Parameters for one factory-scoped subagent call. @@ -4968,6 +5756,10 @@ export interface FactoryAgentRequest { * Factory run identifier that owns the subagent. */ factoryRunId: string; + /** + * Opaque token identifying the current factory execution attempt. + */ + executionToken: string; /** * Prompt to send to the subagent. */ @@ -4985,9 +5777,29 @@ export interface FactoryAgentResult { /** * Agent result, omitted when the agent produced no result. */ - result?: { - [k: string]: unknown | undefined; - }; + result?: JsonValue; +} +/** + * Prompt-safe durable identity and live status for a direct factory agent. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "FactoryAgentSummary". + */ +/** @experimental */ +export interface FactoryAgentSummary { + agentId: string; + toolCallId: string; + runId: string; + phaseId: string | null; + label: string; + agentType: string; + status: string; + requestedModel?: string; + resolvedModel?: string; + startedAt?: number; + completedAt?: number; + activeMs: number; + activity?: string; } /** * Parameters for cancelling a factory run. @@ -5002,6 +5814,30 @@ export interface FactoryCancelRequest { */ runId: string; } +/** + * Current factory phase identity. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "FactoryCurrentPhase". + */ +/** @experimental */ +export interface FactoryCurrentPhase { + id: string; + ordinal: number | null; +} +/** + * Declared or approved factory resource ceilings. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "FactoryDeclaredLimits". + */ +/** @experimental */ +export interface FactoryDeclaredLimits { + maxConcurrentSubagents?: number; + maxTotalSubagents?: number; + timeoutSeconds?: number; + maxAiCredits?: number; +} /** * Parameters sent to the owning extension to execute a factory closure. * @@ -5022,12 +5858,14 @@ export interface FactoryExecuteRequest { * Factory run identifier. */ runId: string; + /** + * Opaque token identifying this factory execution attempt. + */ + executionToken: string; /** * Factory input value. */ - args: { - [k: string]: unknown | undefined; - }; + args: JsonValue; } /** * Result returned by an extension factory closure. @@ -5040,9 +5878,36 @@ export interface FactoryExecuteResult { /** * Factory result value. */ - result: { - [k: string]: unknown | undefined; - }; + result?: JsonValue; +} +/** + * Parameters for paging factory progress. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "FactoryGetRunProgressRequest". + */ +/** @experimental */ +export interface FactoryGetRunProgressRequest { + /** + * Factory run identifier. + */ + runId: string; + /** + * Optional phase identifier used to scope records and cursors. + */ + phaseId?: string; + /** + * Exclusive forward cursor. + */ + afterSeq?: number; + /** + * Exclusive backward cursor. + */ + beforeSeq?: number; + /** + * Maximum records to return. Defaults to 200 and is capped at 500. + */ + limit?: number; } /** * Parameters for retrieving a factory run. @@ -5069,6 +5934,10 @@ export interface FactoryJournalGetRequest { * Factory run identifier. */ runId: string; + /** + * Opaque token identifying the current factory execution attempt. + */ + executionToken: string; /** * Namespaced journal key. */ @@ -5089,9 +5958,7 @@ export interface FactoryJournalGetResult { /** * Cached JSON result. The hit field distinguishes a cached JSON null from a miss. */ - resultJson?: { - [k: string]: unknown | undefined; - }; + resultJson?: JsonValue; } /** * Parameters for storing a factory journal entry. @@ -5105,6 +5972,10 @@ export interface FactoryJournalPutRequest { * Factory run identifier. */ runId: string; + /** + * Opaque token identifying the current factory execution attempt. + */ + executionToken: string; /** * Namespaced journal key. */ @@ -5112,105 +5983,275 @@ export interface FactoryJournalPutRequest { /** * JSON result to memoize. */ - resultJson: { - [k: string]: unknown | undefined; - }; + resultJson: JsonValue; } /** - * One ordered factory progress line. + * Parameters for paging factory runs. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "FactoryLogLine". + * via the `definition` "FactoryListRunsRequest". */ /** @experimental */ -export interface FactoryLogLine { +export interface FactoryListRunsRequest { /** - * Monotonic sequence number within the factory run. + * Exclusive forward cursor. */ - seq: number; - kind: FactoryLogLineKind; + afterSeq?: number; /** - * Progress text. + * Exclusive backward cursor. */ - text: string; + beforeSeq?: number; + /** + * Maximum terminal runs to return. Defaults to 200 and is capped at 500. + */ + limit?: number; } /** - * Parameters for recording factory progress. + * A page of factory runs in durable creation order. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "FactoryLogRequest". + * via the `definition` "FactoryListRunsResult". */ /** @experimental */ -export interface FactoryLogRequest { +export interface FactoryListRunsResult { + runs: FactoryRunSummary[]; /** - * Factory run identifier. + * Oldest terminal-run cursor in this page, or null when the terminal window is empty. */ - runId: string; + oldestSeq?: number | null; /** - * Ordered progress lines to append. + * Newest terminal-run cursor in this page, or null when the terminal window is empty. */ - lines: FactoryLogLine[]; + newestSeq?: number | null; + /** + * Whether terminal runs newer than this page exist. + */ + hasMoreNewer?: boolean; + /** + * Number of terminal runs older than this page. + */ + omittedOlder?: number; } /** - * Wire-only per-invocation factory resource ceiling overrides. + * Durable factory run summary with read-time live overlays. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "FactoryRunLimits". + * via the `definition` "FactoryRunSummary". */ /** @experimental */ -export interface FactoryRunLimits { - /** - * Maximum number of factory subagents that may run concurrently. - */ - maxConcurrentSubagents?: number; - /** - * Maximum total number of factory subagents that may be admitted. - */ - maxTotalSubagents?: number; - /** - * Factory active-run timeout in milliseconds. - */ - timeout?: number; +export interface FactoryRunSummary { + runId: string; + factoryName: string; + description: string; + status: FactoryRunStatus; + revision: number; + createdAt: number; + startedAt: number | null; + updatedAt: number; + completedAt: number | null; + currentPhase: FactoryCurrentPhase | null; + declaredPhaseCount: number; + liveAgentCount: number; + totalSpawnedAgentCount: number; + consumed: FactoryRunConsumed; + declaredLimits: FactoryDeclaredLimits; + approved: FactoryDeclaredLimits | null; + observedAt: number; + activeSegmentStartedAt: number | null; + terminal: FactoryRunTerminal | null; } /** - * Parameters for invoking a registered factory. + * Durable factory resource consumption. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "FactoryRunRequest". + * via the `definition` "FactoryRunConsumed". */ /** @experimental */ -export interface FactoryRunRequest { - /** - * Registered factory name. - */ - name: string; - /** - * Factory input value. - */ - args: { - [k: string]: unknown | undefined; - }; - options?: RunOptions; +export interface FactoryRunConsumed { + activeMs: number; + subagents: number; + nanoAiu: number; } /** - * Options controlling factory invocation. + * Prompt-safe terminal factory outcome. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "RunOptions". + * via the `definition` "FactoryRunTerminal". */ /** @experimental */ -export interface RunOptions { - limits?: FactoryRunLimits; - /** - * Run identifier whose journal and progress should seed this resumed run. - */ - resumeFromRunId?: string; +export interface FactoryRunTerminal { + reason?: string; + failure?: FactoryRunFailure; + error?: string; + resultPreview?: string; } /** - * Complete current or terminal factory run envelope. + * One ordered factory progress line. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "FactoryRunResult". + * via the `definition` "FactoryLogLine". + */ +/** @experimental */ +export interface FactoryLogLine { + /** + * Monotonic sequence number within the factory run. + */ + seq: number; + kind: FactoryLogLineKind; + /** + * Progress text. + */ + text: string; +} +/** + * Parameters for recording factory progress. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "FactoryLogRequest". + */ +/** @experimental */ +export interface FactoryLogRequest { + /** + * Factory run identifier. + */ + runId: string; + /** + * Opaque token identifying the current factory execution attempt. + */ + executionToken: string; + /** + * Ordered progress lines to append. + */ + lines: FactoryLogLine[]; +} +/** + * Durable lifecycle and timing for one factory phase. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "FactoryPhaseObservation". + */ +/** @experimental */ +export interface FactoryPhaseObservation { + id: string; + ordinal: number | null; + title: string; + detail?: string; + status: FactoryPhaseStatus; + lastEnteredRunAttempt: number; + entryCount: number; + startedAt?: number; + completedAt?: number; + accumulatedActiveMs: number; + currentActiveMs: number; + totalAgentCount: number; + liveAgentCount: number; +} +/** + * One durable factory progress record. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "FactoryProgressLine". + */ +/** @experimental */ +export interface FactoryProgressLine { + /** + * Global monotonic sequence number within the run. + */ + seq: number; + /** + * Resume attempt that emitted this record. + */ + attempt: number; + /** + * Phase active when the record was emitted, or null before any phase. + */ + phaseId: string | null; + /** + * Epoch milliseconds when the record was persisted. + */ + recordedAt: number; + kind: FactoryLogLineKind; + /** + * Prompt-safe progress text. + */ + text: string; +} +/** + * A bidirectional page of factory progress. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "FactoryProgressPage". + */ +/** @experimental */ +export interface FactoryProgressPage { + records: FactoryProgressLine[]; + oldestSeq: number | null; + newestSeq: number | null; + hasMoreOlder: boolean; + hasMoreNewer: boolean; + /** + * Run revision reflected by this page. + */ + revision: number; +} +/** + * Parameters for resuming a factory run from its persisted identity. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "FactoryResumeRequest". + */ +/** @experimental */ +export interface FactoryResumeRequest { + /** + * Factory run identifier. + */ + runId: string; + limits?: FactoryRunLimits; +} +/** + * Wire-only per-invocation factory resource ceiling overrides. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "FactoryRunLimits". + */ +/** @experimental */ +export interface FactoryRunLimits { + /** + * Maximum number of factory subagents that may run concurrently. + */ + maxConcurrentSubagents?: number; + /** + * Maximum total number of factory subagents that may be admitted. + */ + maxTotalSubagents?: number; + /** + * Maximum accumulated active-execution time in seconds. Active execution includes the entire extension body, subprocess waits, queued-agent waits, and sleeps; time between resumed attempts is not counted. + */ + timeoutSeconds?: number; + /** + * Maximum AI credits consumed by factory subagents and their descendants. The post-paid ceiling is soft: parallel turns can settle beyond it before the run stops. + */ + maxAiCredits?: number; +} +/** + * Resolved persisted factory identity and resumed run envelope. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "FactoryResumeResult". + */ +/** @experimental */ +export interface FactoryResumeResult { + /** + * Persisted factory name resolved for the resumed run. + */ + factoryName: string; + run: FactoryRunResult; +} +/** + * Complete current or terminal factory run envelope. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "FactoryRunResult". */ /** @experimental */ export interface FactoryRunResult { @@ -5222,9 +6263,7 @@ export interface FactoryRunResult { /** * Completed factory result. */ - result?: { - [k: string]: unknown | undefined; - }; + result?: JsonValue; /** * Error message for an errored run. */ @@ -5237,9 +6276,70 @@ export interface FactoryRunResult { /** * Partial journal and progress snapshot for a halted, cancelled, or errored run. */ - snapshot?: { - [k: string]: unknown | undefined; - }; + snapshot?: JsonValue; +} +/** + * Full factory run observability detail. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "FactoryRunDetail". + */ +/** @experimental */ +export interface FactoryRunDetail { + runId: string; + factoryName: string; + description: string; + status: FactoryRunStatus; + revision: number; + createdAt: number; + startedAt: number | null; + updatedAt: number; + completedAt: number | null; + currentPhase: FactoryCurrentPhase | null; + declaredPhaseCount: number; + liveAgentCount: number; + totalSpawnedAgentCount: number; + consumed: FactoryRunConsumed; + declaredLimits: FactoryDeclaredLimits; + approved: FactoryDeclaredLimits | null; + observedAt: number; + activeSegmentStartedAt: number | null; + terminal: FactoryRunTerminal | null; + phases: FactoryPhaseObservation[]; + agents: FactoryAgentSummary[]; + progress: FactoryProgressPage; +} +/** + * Parameters for invoking a registered factory. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "FactoryRunRequest". + */ +/** @experimental */ +export interface FactoryRunRequest { + /** + * Registered factory name. + */ + name: string; + /** + * Factory input value. + */ + args: JsonValue; + options?: RunOptions; +} +/** + * Options controlling factory invocation. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "RunOptions". + */ +/** @experimental */ +export interface RunOptions { + limits?: FactoryRunLimits; + /** + * Run identifier whose journal and progress should seed this resumed run. + */ + resumeFromRunId?: string; } /** * Optional user prompt to combine with the fleet orchestration instructions. @@ -5482,6 +6582,32 @@ export interface HistoryCancelBackgroundCompactionResult { */ cancelled: boolean; } +/** + * Parameters for clearing the conversation and seeding the window that replaces it. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "HistoryClearContextRequest". + */ +/** @experimental */ +export interface HistoryClearContextRequest { + /** + * First user message of the fresh context window. Required: a cleared window holding only system and developer messages is not a conversation a model can answer, so every clear seeds the window it creates. Delivered by the enclosing turn driver once the agentic loop exits, which is why the call must be made from inside a tool handler. + */ + prompt: string; +} +/** + * What a successful clear removed. A clear that could not be applied rejects instead of reporting a count. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "HistoryClearContextResult". + */ +/** @experimental */ +export interface HistoryClearContextResult { + /** + * Number of non-system, non-developer messages that were removed from the conversation. Zero only when the window already held no conversation. + */ + messagesCleared: number; +} /** * Post-compaction context window usage breakdown * @@ -5515,19 +6641,6 @@ export interface HistoryCompactContextWindow { */ toolDefinitionsTokens?: number; } -/** - * Optional compaction parameters. - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "HistoryCompactRequest". - */ -/** @experimental */ -export interface HistoryCompactRequest { - /** - * Optional user-provided instructions to focus the compaction summary - */ - customInstructions?: string; -} /** * Compaction outcome with the number of tokens and messages removed, summary text, and the resulting context window breakdown. * @@ -5554,6 +6667,180 @@ export interface HistoryCompactResult { summaryContent?: string; contextWindow?: HistoryCompactContextWindow; } +/** + * Rewind points and file-change-tracking availability for the session. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "HistoryListRewindPointsResult". + */ +/** @experimental */ +export interface HistoryListRewindPointsResult { + /** + * Whether this session captured file changes from its first turn. + */ + fileChangeTrackingEnabled: boolean; + unavailableReason?: HistoryRewindUnavailableReason; + /** + * Root user turns in chronological order. Empty when `unavailableReason` is set. + */ + points: HistoryRewindPoint[]; +} +/** + * A root user turn that the session can rewind to. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "HistoryRewindPoint". + */ +/** @experimental */ +export interface HistoryRewindPoint { + /** + * ID of the user.message event that begins the discarded suffix. + */ + eventId: string; + /** + * User-visible message text for the turn. + */ + userMessage: string; + /** + * ISO timestamp of the user turn. + */ + timestamp: string; + /** + * Whether at least one file in this turn or a later turn can be restored. + */ + canRestoreFiles: boolean; + /** + * Number of unique files in this turn and all later turns that have captured changes. + */ + fileCount: number; + /** + * Whether this turn itself captured any file changes. + */ + turnChangedFiles: boolean; + /** + * Lines added by this turn's captured file changes. + */ + linesAdded: number; + /** + * Lines removed by this turn's captured file changes. + */ + linesRemoved: number; + /** + * Whether this turn was an automatically injected autopilot continuation. + */ + isAutopilotContinuation: boolean; +} +/** + * Event boundary to preview for conversation-and-files rewind. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "HistoryPreviewRewindRequest". + */ +/** @experimental */ +export interface HistoryPreviewRewindRequest { + /** + * ID of the user.message event that begins the discarded suffix. + */ + eventId: string; +} +/** + * Files and aggregate changes for a prospective rewind. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "HistoryPreviewRewindResult". + */ +/** @experimental */ +export interface HistoryPreviewRewindResult { + /** + * Whether file restore is available for this session. This is authoritative: switch on it and read `reason` only when it is false. + */ + available: boolean; + reason?: HistoryRewindUnavailableReason; + /** + * Number of unique files in the preview. + */ + fileCount: number; + /** + * Files ordered by path. + */ + files: HistoryRewindFilePreview[]; +} +/** + * A file that a conversation-and-files rewind would restore. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "HistoryRewindFilePreview". + */ +/** @experimental */ +export interface HistoryRewindFilePreview { + /** + * Absolute path of the captured file. + */ + path: string; + changeType: HistoryRewindChangeType; + /** + * Lines added across the discarded turns. + */ + linesAdded: number; + /** + * Lines removed across the discarded turns. + */ + linesRemoved: number; +} +/** + * Boundary and mode for rewinding session history. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "HistoryRewindRequest". + */ +/** @experimental */ +export interface HistoryRewindRequest { + /** + * ID of the user.message event that begins the discarded suffix. + */ + eventId: string; + mode: HistoryRewindMode; +} +/** + * Structured outcome of a rewind request. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "HistoryRewindResult". + */ +/** @experimental */ +export interface HistoryRewindResult { + outcome: HistoryRewindOutcome; + /** + * Number of persisted events removed by conversation truncation. Present only when truncation succeeded (outcomes `success`, `checkpoint-cleanup-failed`, and `snapshot-prune-failed`); omitted for every unavailable outcome (`session-busy`, `file-change-tracking-disabled`, `unsupported-remote-session`) and for `truncation-failed`, `files-rolled-back`, and `rollback-incomplete`. + */ + eventsRemoved?: number; + /** + * Absolute paths restored to their captured preimages. Always empty for conversation-only rewinds and for the unavailable outcomes (`session-busy`, `file-change-tracking-disabled`, `unsupported-remote-session`); only conversation-and-files outcomes that reached the file-restore stage populate it. + */ + restoredFiles: string[]; + /** + * Captured files intentionally left unchanged. Always empty for conversation-only rewinds and for the unavailable outcomes (`session-busy`, `file-change-tracking-disabled`, `unsupported-remote-session`); only conversation-and-files outcomes that reached the file-restore stage populate it. + */ + skippedFiles: HistorySkippedFileRestore[]; + /** + * Failure detail. Set only for the failure and partial-failure outcomes (`files-rolled-back`, `rollback-incomplete`, `truncation-failed`, `checkpoint-cleanup-failed`, `snapshot-prune-failed`); omitted for `success` and for the unavailable outcomes (`session-busy`, `file-change-tracking-disabled`, `unsupported-remote-session`). + */ + error?: string; +} +/** + * A captured file that rewind intentionally left unchanged. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "HistorySkippedFileRestore". + */ +/** @experimental */ +export interface HistorySkippedFileRestore { + /** + * Absolute path of the skipped file. + */ + path: string; + reason: HistoryFileRestoreSkipReason; +} /** * Markdown summary of the conversation context (empty when not available). * @@ -5592,6 +6879,14 @@ export interface HistoryTruncateResult { * Number of events that were removed */ eventsRemoved: number; + /** + * True when conversation truncation succeeded but post-truncation workspace checkpoint cleanup failed. History is already truncated; callers may still prune snapshots but should report a checkpoint-cleanup rather than a truncation failure. + */ + checkpointCleanupFailed?: boolean; + /** + * Failure detail when checkpointCleanupFailed is true. + */ + checkpointCleanupError?: string; } /** * Runtime-owned wire payload for a server-to-client hook callback invocation. @@ -5604,7 +6899,7 @@ export interface HistoryTruncateResult { export interface HookInvokeRequest { sessionId: string; hookType: HookType; - input: unknown; + input: JsonValue; } /** * Optional output returned by an SDK callback hook. @@ -5615,7 +6910,7 @@ export interface HookInvokeRequest { /** @experimental */ /** @internal */ export interface HookInvokeResponse { - output?: unknown; + output?: JsonValue; } /** * Installed plugin record from global state, with marketplace, version, install time, enabled state, cache path, and source. @@ -5650,9 +6945,13 @@ export interface InstalledPlugin { */ cache_path?: string; source?: InstalledPluginSource; + /** + * Per-plugin source fingerprint (a SHA-256 hash of the plugin's catalog source spec plus its resolved source subtree — NOT a Git commit SHA) captured at marketplace install/update time. Auto-update compares it against the freshly recomputed fingerprint to detect a content change that does not bump the version. Absent for pre-existing installs and for direct (non-marketplace) installs. + */ + source_sha?: string; } /** - * Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref, and optional subpath. + * Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref or full commit SHA, and optional subpath. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "InstalledPluginSourceGitHub". @@ -5665,10 +6964,14 @@ export interface InstalledPluginSourceGitHub { source: "github"; repo: string; ref?: string; + /** + * Optional full 40-character hexadecimal commit SHA. + */ + sha?: string; path?: string; } /** - * Source descriptor for a direct URL plugin install, with URL, optional ref, and optional subpath. + * Source descriptor for a direct URL plugin install, with URL, optional ref or full commit SHA, and optional subpath. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "InstalledPluginSourceUrl". @@ -5681,6 +6984,10 @@ export interface InstalledPluginSourceUrl { source: "url"; url: string; ref?: string; + /** + * Optional full 40-character hexadecimal commit SHA. + */ + sha?: string; path?: string; } /** @@ -5848,10 +7155,36 @@ export interface InstructionSource { */ defaultDisabled?: boolean; /** - * The project path this source was discovered from. Only set by sessionless discovery for repository/working-directory sources, where it disambiguates same-named files (e.g. .github/copilot-instructions.md) across multiple workspace roots. The session-scoped getSources leaves it unset. + * The project path this source was discovered from. Only set by sessionless discovery for repository, working-directory, and project-scoped plugin sources, where it disambiguates sources across multiple workspace roots. The session-scoped getSources leaves it unset. */ projectPath?: string; } +/** + * Parameters for interrupting the main agent turn. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "InterruptMainTurnRequest". + */ +/** @experimental */ +export interface InterruptMainTurnRequest { + /** + * When true, the user's queued prompts are preserved and run as the next turn once the interrupted turn unwinds; when false (the default), the queue is cleared like a plain abort. + */ + flushQueued?: boolean; +} +/** + * Result of interrupting the main agent turn. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "InterruptMainTurnResult". + */ +/** @experimental */ +export interface InterruptMainTurnResult { + /** + * Whether an in-flight main agent turn was interrupted. False when the main loop was not processing. + */ + interrupted: boolean; +} /** * HTTP headers as a map from lowercased header name to a list of values. Multi-valued headers (e.g. Set-Cookie) preserve all values. * @@ -6198,6 +7531,23 @@ export interface LspInitializeRequest { */ force?: boolean; } +/** + * Validated device-managed settings discovered before a session exists. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ManagedSettingsReadResult". + */ +/** @experimental */ +export interface ManagedSettingsReadResult { + /** + * Validated, canonical managed-settings JSON. Omitted when no managed settings were discovered or when discovered settings failed validation. + */ + settingsJson?: JsonValue; + /** + * Discovery or validation error text when managed settings could not be read safely. + */ + errorMessage?: string; +} /** * Result of registering a new marketplace. * @@ -6363,7 +7713,7 @@ export interface McpAppsCallToolRequest { * Tool arguments */ arguments?: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }; /** * **Required.** Server whose ui:// view issued the request. Per SEP-1865 ('callable by the app from this server only'), the call is rejected when this differs from `serverName`, and rejected outright when missing. @@ -6508,7 +7858,7 @@ export interface McpAppsListToolsResult { * App-callable tools from the server */ tools: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }[]; } /** @@ -6569,7 +7919,7 @@ export interface McpAppsResourceContent { * Resource-level metadata (CSP, permissions, etc.) */ _meta?: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }; } /** @@ -6675,6 +8025,10 @@ export interface McpServerConfigStdio { oidc?: McpServerAuthConfig; auth?: McpServerAuthConfig; deferTools?: McpServerConfigDeferTools; + /** + * Set to true to disable persisted MCP tool snapshots for this server. Live tool discovery is unaffected. + */ + disableToolCache?: boolean; /** * Executable command used to start the Stdio MCP server process. */ @@ -6732,6 +8086,10 @@ export interface McpServerConfigHttp { oidc?: McpServerAuthConfig; auth?: McpServerAuthConfig; deferTools?: McpServerConfigDeferTools; + /** + * Set to true to disable persisted MCP tool snapshots for this server. Live tool discovery is unaffected. + */ + disableToolCache?: boolean; /** * URL of the remote MCP server endpoint. */ @@ -6834,9 +8192,7 @@ export interface McpConfigureGitHubRequest { * * @internal */ - authInfo: { - [k: string]: unknown | undefined; - }; + authInfo: OpaqueInProcessValue; } /** * Result of configuring GitHub MCP. @@ -6922,9 +8278,7 @@ export interface McpExecuteSamplingParams { /** * The original MCP JSON-RPC request ID (string or number). Used by the runtime to correlate the inference with the originating MCP request for telemetry; this is distinct from `requestId` (which is the schema-level cancellation handle). */ - mcpRequestId: { - [k: string]: unknown | undefined; - }; + mcpRequestId: JsonValue; request: McpExecuteSamplingRequest; } /** @@ -6948,7 +8302,7 @@ export interface McpExecuteSamplingResult { [k: string]: unknown | undefined; } /** - * MCP server filtered by policy, with name, reason, optional redacted reason, and enterprise login. + * MCP server filtered by policy, with name, reason, and optional redacted reason. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "McpFilteredServer". @@ -6968,7 +8322,8 @@ export interface McpFilteredServer { */ redactedReason?: string; /** - * Enterprise login associated with an allowlist policy + * @deprecated + * Deprecated. This field is no longer populated. */ enterpriseName?: string; } @@ -7016,7 +8371,7 @@ export interface McpHostState { */ disabledServers: string[]; /** - * Configured servers filtered out by enterprise allowlist policy. + * Configured servers filtered out by MCP server policy. */ filteredServers: string[]; /** @@ -7157,6 +8512,23 @@ export interface McpToolUi { */ visibility?: McpToolUiVisibility[]; } +/** + * Identifies the MCP server whose persisted OAuth credentials were updated. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpOauthAuthenticationStateChangedRequest". + */ +/** @experimental */ +export interface McpOauthAuthenticationStateChangedRequest { + /** + * Name of the MCP server whose OAuth credentials were updated. Omit only when the host cannot identify the server. + */ + serverName?: string; + /** + * Whether the target session must mint a session-scoped access token instead of reusing a shared access token persisted by another session. + */ + refreshSessionToken?: boolean; +} /** * Pending MCP OAuth request ID and host-provided token or cancellation response. * @@ -7235,6 +8607,32 @@ export interface McpOauthLoginResult { */ authorizationUrl?: string; } +/** + * Pending MCP OAuth request id to respond to. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpOauthRespondRequest". + */ +/** @experimental */ +export interface McpOauthRespondRequest { + /** + * OAuth request identifier from the mcp.oauth_required event + */ + requestId: string; +} +/** + * Indicates whether the pending MCP OAuth response was accepted. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpOauthRespondResult". + */ +/** @experimental */ +export interface McpOauthRespondResult { + /** + * Whether the response was accepted. False if the request was unknown, timed out, or already resolved. + */ + success: boolean; +} /** * Registration parameters for an external MCP client. * @@ -7253,25 +8651,19 @@ export interface McpRegisterExternalClientRequest { * * @internal */ - client: { - [k: string]: unknown | undefined; - }; + client: OpaqueInProcessValue; /** * In-process MCP Transport instance. Marked internal: cannot be serialized across the JSON-RPC boundary. * * @internal */ - transport: { - [k: string]: unknown | undefined; - }; + transport: OpaqueInProcessValue; /** * In-process server config (MCPServerConfig) paired with the in-process client/transport. Marked internal alongside its companions. * * @internal */ - config: { - [k: string]: unknown | undefined; - }; + config: OpaqueInProcessValue; } /** * Opaque MCP reload configuration. @@ -7287,9 +8679,7 @@ export interface McpReloadWithConfigRequest { * * @internal */ - config: { - [k: string]: unknown | undefined; - }; + config: OpaqueInProcessValue; } /** * Indicates whether the auto-managed `github` MCP server was removed (false when nothing to remove). @@ -7345,13 +8735,13 @@ export interface McpResource { * Resource-level metadata */ _meta?: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }; /** * Server-provided non-standard descriptor fields preserved from the MCP response */ additionalProperties?: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }; } /** @@ -7382,7 +8772,7 @@ export interface McpResourceIcon { * Server-provided non-standard icon fields preserved from the MCP response */ additionalProperties?: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }; } /** @@ -7409,7 +8799,7 @@ export interface McpResourceAnnotations { * Server-provided non-standard annotation fields preserved from the MCP response */ additionalProperties?: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }; } /** @@ -7440,7 +8830,7 @@ export interface McpResourceContent { * Resource-level metadata (CSP, permissions, etc.) */ _meta?: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }; } /** @@ -7548,13 +8938,13 @@ export interface McpResourceTemplate { * Resource-template-level metadata */ _meta?: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }; /** * Server-provided non-standard descriptor fields preserved from the MCP response */ additionalProperties?: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }; } /** @@ -7678,7 +9068,7 @@ export interface McpSetEnvValueModeResult { mode: McpSetEnvValueModeDetails; } /** - * Server name and configuration for an individual MCP server start. + * Server name and optional configuration for an individual MCP server start. Omit `config` for a config-free start-by-name of an already-configured server. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "McpStartServerRequest". @@ -7689,7 +9079,7 @@ export interface McpStartServerRequest { * Name of the MCP server to start */ serverName: string; - config: McpServerConfig; + config?: McpServerConfig; } /** * MCP server startup filtering result. @@ -8017,10 +9407,6 @@ export interface Model { * Supported reasoning effort levels (only present if model supports reasoning effort) */ supportedReasoningEfforts?: string[]; - /** - * Default reasoning effort level (only present if model supports reasoning effort) - */ - defaultReasoningEffort?: string; modelPickerCategory?: ModelPickerCategory; modelPickerPriceCategory?: ModelPickerPriceCategory; } @@ -8213,7 +9599,7 @@ export interface ModelBillingTokenPricesLongContext { maxPromptTokens?: number; } /** - * Active server-driven promotion for a model, including its discount and expiry. + * Active server-driven promotion for a model, including its discount and optional expiry. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "ModelBillingPromo". @@ -8229,11 +9615,11 @@ export interface ModelBillingPromo { */ discountPercent?: number; /** - * UTC ISO 8601 timestamp marking when the promotion ends. Always present: the API only surfaces a promo whose expiry parses and is in the future. Consumers should treat a past value as expired. + * UTC ISO 8601 timestamp marking when the promotion ends. Optional: an open-ended promotion omits this field. When present, the API only surfaces a promo whose expiry parses and is in the future, so consumers should treat a past value as expired. */ - endsAt: string; + endsAt?: string; /** - * Human-readable promotion message. Does not include the expiry timestamp; consumers may format endsAt and append it. + * Human-readable promotion message. Does not include the expiry timestamp; consumers may format endsAt and append it when present. */ message?: string; } @@ -8322,19 +9708,6 @@ export interface ModelList { */ models: Model[]; } -/** - * Optional listing options. - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "ModelListRequest". - */ -/** @experimental */ -export interface ModelListRequest { - /** - * If true, bypasses the per-session model list cache and re-fetches from CAPI. - */ - skipCache?: boolean; -} /** * Reasoning effort level to apply to the currently selected model. * @@ -8382,13 +9755,17 @@ export interface ModelSwitchToRequest { */ modelId: string; /** - * Reasoning effort level to use for the model. "none" disables reasoning. + * Reasoning effort level to use for the model. CAPI values are model-defined and validated against the selected model; BYOK providers may define additional values. "none" disables reasoning. When omitted, no effort override is applied. */ reasoningEffort?: string; reasoningSummary?: ReasoningSummary; verbosity?: Verbosity; modelCapabilities?: ModelCapabilitiesOverride; contextTier?: ContextTier; + /** + * When true, defer this switch (enqueue it) if another model change is already queued, even when no turn is active — so it drains last (FIFO) and wins over the already-queued change. Intended for genuine user-initiated model selections; internal restore/reapply switches omit it and apply immediately when no turn is active. When no other model change is queued this has no effect (a switch still applies immediately unless a turn is active). + */ + deferIfModelChangeQueued?: boolean; } /** * The model identifier active on the session after the switch. @@ -8402,6 +9779,10 @@ export interface ModelSwitchToResult { * Currently active model identifier after the switch */ modelId?: string; + /** + * True when the switch was deferred (enqueued as a cancellable `/model` command) because a turn was active or another model change was already queued, rather than applied immediately. When true, the session's live model is unchanged until the queued change drains. + */ + deferred?: boolean; } /** * Agent interaction mode to apply to the session. @@ -8526,7 +9907,7 @@ export interface NameSetRequest { /** @experimental */ export interface OptionsUpdateAdditionalContentExclusionPolicy { rules: OptionsUpdateAdditionalContentExclusionPolicyRule[]; - last_updated_at: unknown; + last_updated_at: JsonValue; scope: OptionsUpdateAdditionalContentExclusionPolicyScope; } /** @@ -8592,6 +9973,10 @@ export interface PermissionDecisionApproveOnce { * Approve this single request only */ kind: "approve-once"; + /** + * True only when a host surfaced this request to a user who approved it. + */ + approvedInteractively?: boolean; } /** * Permission-decision request variant to approve for the rest of the session, with optional tool approval or URL domain. @@ -8739,6 +10124,23 @@ export interface PermissionDecisionApproveForSessionApprovalExtensionManagement */ operation?: string; } +/** + * Session-scoped factory approval, optionally narrowed by approval key. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "PermissionDecisionApproveForSessionApprovalFactory". + */ +/** @experimental */ +export interface PermissionDecisionApproveForSessionApprovalFactory { + /** + * Approval covering factory operations. + */ + kind: "factory"; + /** + * Optional factory operation name or canonical approval key; when omitted, the approval covers all factory operations. + */ + approvalKey?: string; +} /** * Session-scoped approval details for an extension's permission-gated capability access, keyed by extension name. * @@ -8902,6 +10304,23 @@ export interface PermissionDecisionApproveForLocationApprovalExtensionManagement */ operation?: string; } +/** + * Location-scoped factory approval, optionally narrowed by approval key. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "PermissionDecisionApproveForLocationApprovalFactory". + */ +/** @experimental */ +export interface PermissionDecisionApproveForLocationApprovalFactory { + /** + * Approval covering factory operations. + */ + kind: "factory"; + /** + * Optional factory operation name or canonical approval key; when omitted, the approval covers all factory operations. + */ + approvalKey?: string; +} /** * Location-scoped approval details for an extension's permission-gated capability access, keyed by extension name. * @@ -9121,6 +10540,18 @@ export interface PermissionDecisionDeniedByPermissionRequestHook { */ interrupt?: boolean; } +/** + * Optional informational context describing how and where the permission decision was made. This does not affect permission behavior. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "PermissionDecisionContext". + */ +/** @experimental */ +export interface PermissionDecisionContext { + outcome: PermissionDecisionOutcome; + source: PermissionDecisionSource; + surface: PermissionDecisionSurface; +} /** * Pending permission request ID and the decision to apply (approve/reject and scope). * @@ -9134,6 +10565,7 @@ export interface PermissionDecisionRequest { */ requestId: string; result: PermissionDecision; + decisionContext?: PermissionDecisionContext; } /** * Location-scoped tool approval to persist. @@ -9277,6 +10709,23 @@ export interface PermissionsLocationsAddToolApprovalDetailsExtensionManagement { */ operation?: string; } +/** + * Location-persisted factory approval, optionally narrowed by approval key. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "PermissionsLocationsAddToolApprovalDetailsFactory". + */ +/** @experimental */ +export interface PermissionsLocationsAddToolApprovalDetailsFactory { + /** + * Approval covering factory operations. + */ + kind: "factory"; + /** + * Optional factory operation name or canonical approval key; when omitted, the approval covers all factory operations. + */ + approvalKey?: string; +} /** * Location-persisted tool approval details for an extension's permission-gated capability access, keyed by extension name. * @@ -9536,7 +10985,7 @@ export interface PermissionRulesSet { /** @experimental */ export interface PermissionsConfigureAdditionalContentExclusionPolicy { rules: PermissionsConfigureAdditionalContentExclusionPolicyRule[]; - last_updated_at: unknown; + last_updated_at: JsonValue; scope: PermissionsConfigureAdditionalContentExclusionPolicyScope; } /** @@ -9742,13 +11191,18 @@ export interface PermissionsPathsUpdatePrimaryResult { /** @experimental */ export interface PermissionsPendingRequestsRequest {} /** - * No parameters; clears all session-scoped tool permission approvals. + * Clears session-scoped tool permission approvals, and optionally the location-scoped ones. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "PermissionsResetSessionApprovalsRequest". */ /** @experimental */ -export interface PermissionsResetSessionApprovalsRequest {} +export interface PermissionsResetSessionApprovalsRequest { + /** + * Whether location-scoped approvals are cleared too. Defaults to `true`. + */ + includeLocation?: boolean; +} /** * Indicates whether the operation succeeded. * @@ -9776,7 +11230,7 @@ export interface PermissionsSetAllowAllRequest { */ enabled?: boolean; /** - * Optional model id for the `auto` mode auto-approval LLM judging. Only meaningful when `mode` is `auto`; ignored otherwise. When omitted, the session's active model is used. + * Optional model id for the `auto` mode auto-approval LLM judging. Only meaningful when `mode` is `auto`; ignored otherwise. When omitted, the session resolves a default judge model: `gpt-5.5` for CAPI sessions and the session's active model for BYOK sessions. */ model?: string; source?: PermissionsSetAllowAllSource; @@ -10156,49 +11610,20 @@ export interface PluginsMarketplacesRefreshRequest { } /** * Name of the marketplace to remove and an optional force flag. - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "PluginsMarketplacesRemoveRequest". - */ -/** @experimental */ -export interface PluginsMarketplacesRemoveRequest { - /** - * Marketplace name to remove - */ - name: string; - /** - * When true, also uninstall every plugin sourced from this marketplace. When false (default), removal is a no-op if any plugin from this marketplace is installed and the dependent plugin names are returned in the result. - */ - force?: boolean; -} -/** - * Optional flags controlling which side effects the reload performs. - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "PluginsReloadRequest". - */ -/** @experimental */ -export interface PluginsReloadRequest { - /** - * Reload MCP server connections after refreshing plugins. Defaults to true. - */ - reloadMcp?: boolean; - /** - * Re-run custom-agent discovery after refreshing plugins. Defaults to true. - */ - reloadCustomAgents?: boolean; - /** - * Re-load user, plugin, and (subject to `deferRepoHooks`) repo hooks. Defaults to true. Has no effect when the host has not registered a hook reloader (e.g. remote sessions). - */ - reloadHooks?: boolean; + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "PluginsMarketplacesRemoveRequest". + */ +/** @experimental */ +export interface PluginsMarketplacesRemoveRequest { /** - * Re-discover and relaunch subprocess extensions (including plugin-shipped extensions) after refreshing plugins. Defaults to true. Has no effect when the session has no active extension controller (e.g. extensions were not requested for the session). + * Marketplace name to remove */ - reloadExtensions?: boolean; + name: string; /** - * When true, skip repo-level hooks during the hook reload. Use before folder trust is confirmed; load them post-trust via `sessions.loadDeferredRepoHooks`. + * When true, also uninstall every plugin sourced from this marketplace. When false (default), removal is a no-op if any plugin from this marketplace is installed and the dependent plugin names are returned in the result. */ - deferRepoHooks?: boolean; + force?: boolean; } /** * Name (or spec) of the plugin to uninstall. @@ -10371,7 +11796,7 @@ export interface ProviderAddResult { /** * Synthesized selectable model entries for the newly added BYOK models, each under its provider-qualified selection id (`provider/id`). Empty when only providers were added. */ - models: unknown[]; + models: JsonValue[]; } /** * Custom model-provider configuration (BYOK). @@ -10480,19 +11905,6 @@ export interface ProviderSessionToken { */ expiresAt?: string; } -/** - * Optional model identifier to scope the endpoint snapshot to. - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "ProviderGetEndpointRequest". - */ -/** @experimental */ -export interface ProviderGetEndpointRequest { - /** - * Model identifier the caller intends to use against the returned endpoint. Used to pick the correct wire shape. Omit to use whichever model the session is currently using. - */ - modelId?: string; -} /** * Asks the SDK client to acquire a bearer token for a BYOK provider whose config set `hasBearerTokenProvider: true`. Issued by the runtime before each outbound model request; the runtime does no caching, so this is sent once per request. * @@ -10982,6 +12394,251 @@ export interface PushAttachmentBlob { */ displayName?: string; } +/** + * Inputs for starting a deferred-idle drain. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "QueueBeginDeferredIdleDrainRequest". + */ +/** @experimental */ +export interface QueueBeginDeferredIdleDrainRequest { + /** + * Whether the host still has active background work. + */ + activeBackgroundWork: boolean; +} +/** + * Whether a deferred-idle drain should run. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "QueueBeginDeferredIdleDrainResult". + */ +/** @experimental */ +export interface QueueBeginDeferredIdleDrainResult { + /** + * True when the host should run finishDeferredIdleDrain asynchronously. + */ + shouldDrain: boolean; +} +/** + * Internal filter for consuming queued system notifications. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "QueueConsumeSystemNotificationsRequest". + */ +/** @experimental */ +export interface QueueConsumeSystemNotificationsRequest { + /** + * Opaque runtime-owned filter object. + */ + filter: JsonValue; +} +/** + * Inputs for marking session.idle deferred in native state. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "QueueDeferSessionIdleRequest". + */ +/** @experimental */ +export interface QueueDeferSessionIdleRequest { + /** + * Whether the deferred idle was caused by an aborted foreground turn. + */ + aborted: boolean; +} +/** + * Parameters for duplicating a queued item. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "QueueDuplicateAtRequest". + */ +/** @experimental */ +export interface QueueDuplicateAtRequest { + id: string; +} +/** + * Result of duplicating a queued item. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "QueueDuplicateAtResult". + */ +/** @experimental */ +export interface QueueDuplicateAtResult { + /** + * Fresh stable opaque id assigned to the duplicate. + */ + id: string; +} +/** + * Result of enqueueing the resume-pending wake item. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "QueueEnqueueResumePendingResult". + */ +/** @experimental */ +export interface QueueEnqueueResumePendingResult { + /** + * True when a wake item was newly queued. + */ + queued: boolean; +} +/** + * Inputs for completing a deferred-idle drain. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "QueueFinishDeferredIdleDrainRequest". + */ +/** @experimental */ +export interface QueueFinishDeferredIdleDrainRequest { + /** + * Whether the host still has active background work. + */ + activeBackgroundWork: boolean; + /** + * Whether native queued work remains. + */ + hasPending: boolean; +} +/** + * Action selected by the native deferred-idle drain. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "QueueFinishDeferredIdleDrainResult". + */ +/** @experimental */ +export interface QueueFinishDeferredIdleDrainResult { + /** + * One of none, processQueue, or emitSessionIdle. + */ + action: string; + /** + * Whether the deferred idle was caused by an aborted foreground turn. + */ + aborted: boolean; +} +/** + * Whether the native queue has pending work. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "QueueHasPendingResult". + */ +/** @experimental */ +export interface QueueHasPendingResult { + /** + * True when queued or immediate native work is pending. + */ + hasPending: boolean; +} +/** + * Parameters for inserting a queued message at a public visible position. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "QueueInsertAtRequest". + */ +/** @experimental */ +export interface QueueInsertAtRequest { + /** + * Zero-based position in the public visible queue. Values outside the queue clamp to an end. + */ + position: number; + message: QueueInsertMessage; +} +/** + * Serializable message fields accepted by queue.insertAt. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "QueueInsertMessage". + */ +/** @experimental */ +export interface QueueInsertMessage { + /** + * The user message text. + */ + prompt: string; + /** + * Optional user-facing display text. + */ + displayPrompt?: string; + /** + * Optional attachments for the message. + */ + attachments?: Attachment[]; + agentMode?: SendAgentMode; + /** + * Optional provenance source. `system` is rejected: it would hide the inserted row from `pendingItems` and make it unaddressable while still executing, so inserted items must stay visible. + */ + source?: string; + /** + * Whether the message is billable. + */ + billable?: boolean; + /** + * Required tool name for the turn, when any. + */ + requiredTool?: string; + /** + * Per-turn request headers. + */ + requestHeaders?: { + [k: string]: string | undefined; + }; + mode?: SendMode; + /** + * Accepted for SendOptions compatibility but ignored; the requested public position controls placement. + */ + prepend?: boolean; + /** + * Accepted for SendOptions compatibility but ignored; insertion scheduling is controlled by the queue drain state. + */ + wait?: boolean; + /** + * Accepted for internal SendOptions compatibility but ignored; delivery is derived from current session activity. + */ + delivery?: string; +} +/** + * Result of inserting a queued message. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "QueueInsertAtResult". + */ +/** @experimental */ +export interface QueueInsertAtResult { + /** + * Fresh stable opaque id assigned to the inserted item. + */ + id: string; +} +/** + * Parameters for moving a queued item by stable id. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "QueueMoveItemRequest". + */ +/** @experimental */ +export interface QueueMoveItemRequest { + /** + * Stable opaque queued-item id. + */ + id: string; + /** + * Zero-based target position in the public visible queue. Values outside the queue clamp to an end. + */ + toPosition: number; +} +/** + * Result of moving a queued item. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "QueueMoveItemResult". + */ +/** @experimental */ +export interface QueueMoveItemResult { + /** + * True when the item changed position; false when it was already at the requested position. + */ + changed: boolean; +} /** * User-facing pending queue entry, with kind and display text for a queued message, slash command, or model change. * @@ -10990,41 +12647,152 @@ export interface PushAttachmentBlob { */ /** @experimental */ export interface QueuePendingItems { + /** + * Stable opaque id for the canonical queued item. Batch rows share one id. + */ + id: string; kind: QueuePendingItemsKind; /** * Human-readable text to display for this queue entry in the UI */ displayText: string; + agentMode: SendAgentMode; +} +/** + * Snapshot of the session's pending queued items and immediate-steering messages. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "QueuePendingItemsResult". + */ +/** @experimental */ +export interface QueuePendingItemsResult { + /** + * Pending queued items in submission order. Includes user messages, queued slash commands, and queued model changes; omits internal system items. + */ + items: QueuePendingItems[]; + /** + * Display text for messages currently in the immediate steering queue (interjections sent during a running turn). + */ + steeringMessages: string[]; +} +/** + * Parameters for removing a queued item by stable id. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "QueueRemoveAtRequest". + */ +/** @experimental */ +export interface QueueRemoveAtRequest { + id: string; +} +/** + * Result of removing a queued item. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "QueueRemoveAtResult". + */ +/** @experimental */ +export interface QueueRemoveAtResult { + /** + * True when the addressed item was removed. + */ + removed: boolean; +} +/** + * Indicates whether a user-facing pending item was removed. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "QueueRemoveMostRecentResult". + */ +/** @experimental */ +export interface QueueRemoveMostRecentResult { + /** + * True if a user-facing pending item was removed (LIFO across both queues); false when no removable items remained. + */ + removed: boolean; +} +/** + * Parameters for steering a queued message into a live turn. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "QueueSendNowRequest". + */ +/** @experimental */ +export interface QueueSendNowRequest { + id: string; +} +/** + * Result of trying to steer a queued message into a live turn. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "QueueSendNowResult". + */ +/** @experimental */ +export interface QueueSendNowResult { + /** + * True when the item was accepted into the steering lane; false when no main turn was live. + */ + steered: boolean; +} +/** + * Parameters for acquiring or releasing the queued-lane drain pause. Acquisition is exclusive and non-idempotent: `paused: true` against an already-paused session fails with `queue_already_paused`. The pause is never released automatically — it is not tied to the caller's lifetime, so a client that exits without sending `paused: false` leaves the lane frozen. Release is unowned: `paused: false` clears the pause for any caller, including one that never acquired it. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "QueueSetDrainPausedRequest". + */ +/** @experimental */ +export interface QueueSetDrainPausedRequest { + paused: boolean; } /** - * Snapshot of the session's pending queued items and immediate-steering messages. + * Internal snapshot of native queue state for local session orchestration. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "QueuePendingItemsResult". + * via the `definition` "QueueSnapshotResult". */ /** @experimental */ -export interface QueuePendingItemsResult { +export interface QueueSnapshotResult { /** - * Pending queued items in submission order. Includes user messages, queued slash commands, and queued model changes; omits internal system items. + * User-facing pending items in FIFO order. */ items: QueuePendingItems[]; /** - * Display text for messages currently in the immediate steering queue (interjections sent during a running turn). + * Immediate steering messages waiting for an active turn. */ steeringMessages: string[]; + /** + * Insertion orders for queued items, aligned with `items`. + */ + itemOrders?: number[]; + /** + * Insertion orders for immediate steering messages, aligned with `steeringMessages`. + */ + steeringMessageOrders?: number[]; } /** - * Indicates whether a user-facing pending item was removed. + * Parameters for editing a single queued message. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "QueueRemoveMostRecentResult". + * via the `definition` "QueueUpdateTextRequest". */ /** @experimental */ -export interface QueueRemoveMostRecentResult { +export interface QueueUpdateTextRequest { + id: string; + prompt: string; + displayPrompt?: string; +} +/** + * Result of editing a queued message. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "QueueUpdateTextResult". + */ +/** @experimental */ +export interface QueueUpdateTextResult { /** - * True if a user-facing pending item was removed (LIFO across both queues); false when no removable items remained. + * True when the stored text changed. */ - removed: boolean; + updated: boolean; } /** * Event type to register consumer interest for, used by runtime gating logic. @@ -11072,9 +12840,7 @@ export interface RegisterExtensionToolsParams { * * @internal */ - loader: { - [k: string]: unknown | undefined; - }; + loader: OpaqueInProcessValue; options?: SessionsRegisterExtensionToolsOnSessionOptions; } /** @@ -11090,9 +12856,7 @@ export interface SessionsRegisterExtensionToolsOnSessionOptions { * * @internal */ - enabled?: { - [k: string]: unknown | undefined; - }; + enabled?: OpaqueInProcessValue; } /** * Handle for releasing the extension tool registration. @@ -11110,9 +12874,7 @@ export interface RegisterExtensionToolsResult { * * @internal */ - unsubscribe: { - [k: string]: unknown | undefined; - }; + unsubscribe: OpaqueInProcessValue; } /** * Opaque handle previously returned by `registerInterest` to release. @@ -11233,9 +12995,7 @@ export interface RemoteControlStatusActive { * * @internal */ - promptManager?: { - [k: string]: unknown | undefined; - }; + promptManager?: OpaqueInProcessValue; /** * True while a read-only/session-sync export is deferred, awaiting the first `user.message` before its MC session exists. Marked internal: this field is excluded from the public SDK surface and is populated only on the CLI in-process path. * @@ -11479,14 +13239,11 @@ export interface SandboxConfig { * Whether to auto-add the current working directory to readwritePaths. Default: true. */ addCurrentWorkingDirectory?: boolean; + auth?: SandboxConfigAuth; /** - * Whether to inject the Copilot GitHub token as an `http..extraheader` so authenticated HTTPS git works inside the sandbox without the shell-based credential helper the sandbox blocks. Default: false (opt-in). - */ - gitAuth?: boolean; - /** - * Whether to export `GH_TOKEN` so the `gh` CLI authenticates inside the sandbox without the OS keyring the sandbox blocks. Default: false (opt-in). + * Whether to auto-grant read access to common developer-tool caches, registries, and toolchains in their default home locations (cargo, go, npm, Maven, and more), plus read-write access to (and, on Unix, up-front creation of) the scratch caches builds write on every run (go-build, ccache, sccache, Gradle caches, Cargo lock/tracker files), so builds work without extra configuration; a relocated CARGO_HOME additionally gets its Cargo lock files granted read-write. Default: true (enabled by default; set to false to opt out). */ - ghAuth?: boolean; + allowDevToolAccess?: boolean; } /** * User-managed sandbox policy fragment merged into the auto-discovered base policy. @@ -11542,6 +13299,28 @@ export interface SandboxConfigUserPolicyNetwork { * Whether traffic to local/loopback addresses is allowed. */ allowLocalNetwork?: boolean; + proxy?: SandboxConfigUserPolicyNetworkProxy; +} +/** + * HTTP proxy configuration for sandboxed traffic. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SandboxConfigUserPolicyNetworkProxy". + */ +/** @experimental */ +export interface SandboxConfigUserPolicyNetworkProxy { + /** + * Proxy URL (e.g. http://proxy.example.com:8080). The port is optional and defaults to the scheme's standard port when omitted. Credentials must not be embedded here — a `user:pass@` authority is rejected; put them in the separate `username`/`password` fields. A credential-free http:// loopback URL is routed through the localhost proxy automatically; loopback covers localhost and any *.localhost subdomain, the whole 127.0.0.0/8 range, ::1, and IPv4-mapped loopback (::ffff:127.0.0.1). An https:// URL, or one with a username/password set, is used as-is. + */ + url: string; + /** + * Optional username for proxy authentication. Combined with the URL (and `password`) into `user:pass@host` when the sandboxed process routes through the proxy. + */ + username?: string; + /** + * Optional password for proxy authentication, combined with the URL at spawn time. The persisted value may be a literal password, a `${secret:…}` reference resolved from the OS keychain, or a `${VAR}`/`$VAR` environment reference; it is resolved just before the sandboxed process routes through the proxy. The /sandbox dialog stores a real password in the OS keychain and persists only a `${secret:…}` placeholder (never plaintext in settings.json); the field is masked in the dialog and redacted by /settings show. + */ + password?: string; } /** * macOS seatbelt-specific options. @@ -11579,6 +13358,116 @@ export interface SandboxConfigUserPolicyExperimentalSeatbelt { */ keychainAccess?: boolean; } +/** + * Credential-injection capability flags applied while the sandbox is enabled. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SandboxConfigAuth". + */ +/** @experimental */ +export interface SandboxConfigAuth { + /** + * Whether to inject git credentials as an `http..extraheader` so authenticated HTTPS git works inside the sandbox without the shell-based credential helper the sandbox blocks. github.com is served by the Copilot token; every other forge (Azure DevOps, GitHub Enterprise Server, GitLab, ...) by a credential the host resolves from the user's own helper before the sandbox is applied. Default: false (opt-in). + */ + git?: boolean; + /** + * Whether to export `GH_TOKEN` so the `gh` CLI authenticates inside the sandbox without the OS keyring the sandbox blocks. Default: false (opt-in). + */ + gh?: boolean; +} +/** + * Register an absolute-time scheduled prompt. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ScheduleAddAtRequest". + */ +/** @experimental */ +export interface ScheduleAddAtRequest { + /** + * Epoch milliseconds when the prompt should fire. + */ + at: number; + /** + * Prompt text to enqueue when the schedule fires. + */ + prompt: string; + /** + * Whether the schedule should re-arm after each tick. Defaults to false. + */ + recurring?: boolean; + /** + * Optional display-only prompt label. + */ + displayPrompt?: string; +} +/** + * Register a cron scheduled prompt. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ScheduleAddCronRequest". + */ +/** @experimental */ +export interface ScheduleAddCronRequest { + /** + * 5-field cron expression. + */ + cron: string; + /** + * Prompt text to enqueue when the schedule fires. + */ + prompt: string; + /** + * Whether the schedule should re-arm after each tick. Defaults to true. + */ + recurring?: boolean; + /** + * Optional display-only prompt label. + */ + displayPrompt?: string; + /** + * IANA timezone for evaluating the cron expression. + */ + tz?: string; +} +/** + * Register a relative-interval scheduled prompt. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ScheduleAddRequest". + */ +/** @experimental */ +export interface ScheduleAddRequest { + /** + * Human-readable interval such as `30s`, `5m`, or `2h`. + */ + interval: string; + /** + * Prompt text to enqueue when the schedule fires. + */ + prompt: string; + /** + * Whether the schedule should re-arm after each tick. Defaults to true. + */ + recurring?: boolean; + /** + * Optional display-only prompt label. + */ + displayPrompt?: string; +} +/** + * Result of registering or re-arming a scheduled prompt. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ScheduleAddResult". + */ +/** @experimental */ +export interface ScheduleAddResult { + entry?: ScheduleEntry; + /** + * User-facing validation error, when registration failed. + */ + error?: string; +} /** * Scheduled prompt entry with ID, timing (`intervalMs`, `cron`, or `at`), prompt text, recurrence, and next run time. * @@ -11628,6 +13517,36 @@ export interface ScheduleEntry { */ nextRunAt: string; } +/** + * Register a self-paced scheduled prompt. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ScheduleAddSelfPacedRequest". + */ +/** @experimental */ +export interface ScheduleAddSelfPacedRequest { + /** + * Prompt text to enqueue when the schedule fires. + */ + prompt: string; + /** + * Optional display-only prompt label. + */ + displayPrompt?: string; +} +/** + * Whether the session currently has an active self-paced schedule. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ScheduleHasSelfPacedResult". + */ +/** @experimental */ +export interface ScheduleHasSelfPacedResult { + /** + * True when at least one active schedule is self-paced. + */ + hasSelfPaced: boolean; +} /** * Snapshot of the currently active recurring prompts for this session. * @@ -11641,6 +13560,23 @@ export interface ScheduleList { */ entries: ScheduleEntry[]; } +/** + * Re-arm a self-paced scheduled prompt. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ScheduleRearmSelfPacedRequest". + */ +/** @experimental */ +export interface ScheduleRearmSelfPacedRequest { + /** + * Id of the self-paced scheduled prompt. + */ + id: number; + /** + * Epoch milliseconds when the prompt should next fire. + */ + at: number; +} /** * Identifier of the scheduled prompt to remove. * @@ -11738,7 +13674,7 @@ export interface SendMessageItem { */ requiredTool?: string; /** - * Optional provenance tag copied to the resulting user.message event. Must match one of three forms: the literal `system`, `command-` for messages originating from a command (e.g. slash command, Mission Control command), or `schedule-` for messages originating from a scheduled job. + * Optional provenance tag copied to the resulting user.message event. Must be `user`, `system`, `command-` for command-originated messages, `schedule-` for scheduled prompts, or `agent-` for prompts sent by another agent. * * @internal */ @@ -11777,7 +13713,7 @@ export interface SendMessagesRequest { */ tracestate?: string; /** - * If true, await completion of the agentic loop for this turn before returning. Defaults to false (fire-and-forget). When true, the result still contains the same `messageIds`; the caller can rely on the agent having processed the messages before the call resolves. + * If true, await completion of the agentic loop for this turn before returning. Defaults to false (fire-and-forget). When true, the result still contains the same `messageIds`; the caller can rely on the agent having processed the messages before the call resolves. Transport-dependent tail semantics: on a LOCAL (in-process) session the wait additionally blocks until the completed turn's event tail has been dispatched to this session's in-process subscribers, so a subsequent read of subscriber state already reflects the turn; on a REMOTE session the wait resolves once the loop completes and mirrored delivery follows over the wire. Callers that need the stronger local guarantee on remote sessions should await the event stream explicitly. */ wait?: boolean; } @@ -11828,7 +13764,7 @@ export interface SendRequest { */ requiredTool?: string; /** - * Optional provenance tag copied to the resulting user.message event. Must match one of three forms: the literal `system`, `command-` for messages originating from a command (e.g. slash command, Mission Control command), or `schedule-` for messages originating from a scheduled job. + * Optional provenance tag copied to the resulting user.message event. Must be `user`, `system`, `command-` for command-originated messages, `schedule-` for scheduled prompts, or `agent-` for prompts sent by another agent. * * @internal */ @@ -11849,7 +13785,7 @@ export interface SendRequest { */ tracestate?: string; /** - * If true, await completion of the agentic loop for this message before returning. Defaults to false (fire-and-forget). When true, the result still contains the same `messageId`; the caller can rely on the agent having processed the message before the call resolves. + * If true, await completion of the agentic loop for this message before returning. Defaults to false (fire-and-forget). When true, the result still contains the same `messageId`; the caller can rely on the agent having processed the message before the call resolves. Transport-dependent tail semantics: on a LOCAL (in-process) session the wait additionally blocks until the completed turn's event tail has been dispatched to this session's in-process subscribers, so a subsequent read of subscriber state already reflects the turn; on a REMOTE session the wait resolves once the loop completes and mirrored delivery follows over the wire. Callers that need the stronger local guarantee on remote sessions should await the event stream explicitly. */ wait?: boolean; } @@ -11866,6 +13802,27 @@ export interface SendResult { */ messageId: string; } +/** + * Internal request for sending a system notification. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SendSystemNotificationRequest". + */ +/** @experimental */ +export interface SendSystemNotificationRequest { + /** + * Notification text to deliver to the model. + */ + message: string; + /** + * Optional structured notification kind. + */ + kind?: JsonValue; + /** + * Internal delivery options, including passive policy. + */ + options?: JsonValue; +} /** * Agents discovered across user, project, plugin, and remote sources. * @@ -11904,6 +13861,10 @@ export interface ServerSkill { * Unique identifier for the skill */ name: string; + /** + * Canonical slash command name used to invoke the skill, without the leading '/' + */ + commandName?: string; /** * Description of what the skill does */ @@ -12328,7 +14289,7 @@ export interface SessionFsSqliteExistsResult { exists: boolean; } /** - * SQL query, query type, and optional bind parameters for executing a SQLite query against the per-session database. + * SQL query, query type, and optional bind parameters for executing a SQLite query against the per-session database. The provider applies its SQLite busy timeout for every call. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "SessionFsSqliteQueryRequest". @@ -12348,7 +14309,7 @@ export interface SessionFsSqliteQueryRequest { * Optional named bind parameters */ params?: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }; } /** @@ -12363,7 +14324,7 @@ export interface SessionFsSqliteQueryResult { * For SELECT: array of row objects. For others: empty array. */ rows: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }[]; /** * Column names from the result set @@ -12379,6 +14340,62 @@ export interface SessionFsSqliteQueryResult { lastInsertRowid?: number; error?: SessionFsError; } +/** + * Classified SQLite transaction failure. busyOrLocked guarantees rollback; postCommitAmbiguous must never be retried. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionFsSqliteTransactionError". + */ +/** @experimental */ +export interface SessionFsSqliteTransactionError { + errorClass: SessionFsSqliteTransactionErrorClass; + message: string; +} +/** + * Statements to execute atomically. Providers apply busy handling for every call. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionFsSqliteTransactionRequest". + */ +/** @experimental */ +export interface SessionFsSqliteTransactionRequest { + /** + * Target session identifier + */ + sessionId: string; + statements: SessionFsSqliteTransactionStatement[]; +} +/** + * One statement in an atomic SQLite transaction. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionFsSqliteTransactionStatement". + */ +/** @experimental */ +export interface SessionFsSqliteTransactionStatement { + /** + * SQL statement to execute. + */ + query: string; + queryType: SessionFsSqliteQueryType; + /** + * Optional named bind parameters. + */ + params?: { + [k: string]: JsonValue | undefined; + }; +} +/** + * Per-statement results, or a classified transaction error. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionFsSqliteTransactionResult". + */ +/** @experimental */ +export interface SessionFsSqliteTransactionResult { + results: SessionFsSqliteQueryResult[]; + error?: SessionFsSqliteTransactionError; +} /** * Path whose metadata should be returned from the client-provided session filesystem. * @@ -12484,9 +14501,13 @@ export interface SessionInstalledPlugin { */ cache_path?: string; source?: SessionInstalledPluginSource; + /** + * Per-plugin source fingerprint (a SHA-256 hash of the plugin's catalog source spec plus its resolved source subtree — NOT a Git commit SHA) captured at marketplace install/update time. Auto-update compares it against the freshly recomputed fingerprint to detect a content change that does not bump the version. Absent for pre-existing installs and for direct (non-marketplace) installs. + */ + source_sha?: string; } /** - * Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref, and optional subpath. + * Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref or full commit SHA, and optional subpath. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "SessionInstalledPluginSourceGitHub". @@ -12499,10 +14520,14 @@ export interface SessionInstalledPluginSourceGitHub { source: "github"; repo: string; ref?: string; + /** + * Optional full 40-character hexadecimal commit SHA. + */ + sha?: string; path?: string; } /** - * Source descriptor for a direct URL plugin install, with URL, optional ref, and optional subpath. + * Source descriptor for a direct URL plugin install, with URL, optional ref or full commit SHA, and optional subpath. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "SessionInstalledPluginSourceUrl". @@ -12515,6 +14540,10 @@ export interface SessionInstalledPluginSourceUrl { source: "url"; url: string; ref?: string; + /** + * Optional full 40-character hexadecimal commit SHA. + */ + sha?: string; path?: string; } /** @@ -12531,6 +14560,70 @@ export interface SessionInstalledPluginSourceLocal { source: "local"; path: string; } +/** + * Baseline data provenance for a prediction. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionLimitPredictionBaselineData". + */ +/** @experimental */ +export interface SessionLimitPredictionBaselineData { + /** + * Start of the baseline data slice. + */ + windowStart: string; + /** + * End of the baseline data slice. + */ + windowEnd: string; +} +/** + * Explainable AI-credit session-limit prediction. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionLimitPredictionDetails". + */ +/** @experimental */ +export interface SessionLimitPredictionDetails { + clientType: SessionLimitPredictionClientType; + /** + * Model identifier used for lookup. + */ + modelId: string; + source: SessionLimitPredictionSource; + /** + * Key matched at the source level, such as a model id, family id, or `global`. + */ + sourceKey: string; + /** + * Resolved model family when known. + */ + family?: string; + /** + * Ordered usage tiers and their AI-credit caps. + */ + tiers: SessionLimitPredictionTierOption[]; + baselineData: SessionLimitPredictionBaselineData; + recommendedTier: SessionLimitPredictionTier; + /** + * Recommended maximum AI credits for this session. + */ + recommendedCap: number; +} +/** + * Semantic usage tier and its AI-credit cap. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionLimitPredictionTierOption". + */ +/** @experimental */ +export interface SessionLimitPredictionTierOption { + tier: SessionLimitPredictionTier; + /** + * AI-credit cap for this tier. + */ + cap: number; +} /** * Sessions matching the filter, ordered most-recently-modified first. * @@ -12582,9 +14675,41 @@ export interface SessionLoadDeferredRepoHooksResult { */ startupPrompts: string[]; /** - * Total hook command count (user + plugin + repo) loaded for the session by this call. Captured atomically with startupPrompts so callers don't need to read a separate counter. + * Total hook command count (user + plugin + repo) loaded for the session by this call. Captured atomically with startupPrompts so callers don't need to read a separate counter. + */ + hookCount: number; +} +/** + * Enterprise permission policy expressed with the runtime's managed permission-rule syntax. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionManagedPermissions". + */ +/** @experimental */ +export interface SessionManagedPermissions { + disableBypassPermissionsMode?: DisableBypassPermissionsMode; + /** + * Permission rules that block matching operations. Deny has highest precedence. + */ + deny?: string[]; + /** + * Permission rules that require explicit human approval. + */ + ask?: string[]; + /** + * Permission rules that allow matching operations unless another managed source, deny, or ask rule restricts them. */ - hookCount: number; + allow?: string[]; +} +/** + * Managed settings an SDK host may inject at session startup. Only permissions are accepted in this initial contract. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionManagedSettings". + */ +/** @experimental */ +export interface SessionManagedSettings { + permissions?: SessionManagedPermissions; } /** * Point-in-time snapshot of slow-changing session identifier and state fields @@ -12660,7 +14785,7 @@ export interface SessionModelList { /** * Available models, ordered with the most preferred default first. Includes both Copilot (CAPI) models and any registry BYOK models; a BYOK model appears under its provider-qualified selection id (`provider/id`). */ - list: unknown[]; + list: JsonValue[]; /** * Cost categories for the full CAPI catalog, including picker-disabled models that Auto may select. Metadata only; entries absent from `list` are not manually selectable. */ @@ -12669,7 +14794,7 @@ export interface SessionModelList { * Per-quota snapshots returned alongside the model list, keyed by quota type. */ quotaSnapshots?: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }; } /** @@ -12704,7 +14829,7 @@ export interface SessionOpenOptions { */ model?: string; /** - * Initial reasoning effort level. + * Initial reasoning effort level. CAPI values are model-defined and validated against the selected model; BYOK providers may define additional values. When omitted, no effort override is applied. */ reasoningEffort?: string; reasoningSummary?: SessionOpenOptionsReasoningSummary; @@ -12730,13 +14855,16 @@ export interface SessionOpenOptions { * * @internal */ - expAssignments?: { - [k: string]: unknown | undefined; - }; + expAssignments?: JsonValue; /** * Opt-in: self-fetch and enforce enterprise managed settings at session bootstrap. */ enableManagedSettings?: boolean; + managedSettings?: SessionManagedSettings; + /** + * Opt in to capturing file changes for session rewind and session diff. Capture cannot reconstruct changes made before it was enabled. On create it starts capture from the first turn. It is also honored on resume: for a session that already has tracked prior turns, tracking continues automatically even if this is omitted; passing it on resume additionally enables tracking for an eligible session that has no prior root turn yet. Resuming a session whose prior root turns were never tracked has no restorable baseline, so tracking stays disabled for it and rewind reports file change tracking as unavailable; the resume itself still succeeds, so sessions that predate tracking remain loadable. The opt-in is only rejected when the session can never track (a subagent session, or one without local session storage). It is intentionally absent from the mutable options update because enabling it after edits have occurred would create an incomplete, misleading baseline. Subagents share the parent session's capture store and are not tracked as separate rewind points: a file a subagent writes is attributed to whichever root user turn was open when the capture was staged, just before the tool body ran. A turn cannot open while a staged capture is still in flight, so a subagent tool that staged under the spawning turn stays attributed to it however late the write lands, while a capture it stages after the user's next message belongs to that later turn. Attribution decides which turn's rewind point counts and file preview include that write; it does not narrow which rewinds revert it, because a rewind restores every capture from the selected turn onward, so the earlier spawning turn reverts it as well. + */ + enableFileChangeTracking?: boolean; /** * Feature-flag values resolved by the host. */ @@ -12766,6 +14894,10 @@ export interface SessionOpenOptions { * Working directory to anchor the session. */ workingDirectory?: string; + /** + * Additional directories the agent may access beyond the working directory. Each entry is granted to the session's file-access allow-list and surfaced to the model (system prompt context and `@`-mention completion). Absolute paths are recommended; a relative path is resolved against the session's working directory. Nonexistent or unresolvable entries are skipped with a warning. This is applied on both session creation and resume, and is not persisted: a resumed session that omits this option does not retain previously supplied directories (re-supply them, exactly as the CLI re-passes `--add-dir`). + */ + additionalDirectories?: string[]; workingDirectoryContext?: SessionContext; /** * Whether this session supports remote steering. @@ -12807,12 +14939,14 @@ export interface SessionOpenOptions { * Whether shell-script safety heuristics are enabled. */ enableScriptSafety?: boolean; + shell?: ShellOptions; /** - * Shell init profile. + * @deprecated + * Use shell.initProfile instead. Shell init profile. */ shellInitProfile?: string; /** - * Per-shell process flags. + * PowerShell process flags applied to built-in and user-requested shell commands. */ shellProcessFlags?: string[]; sandboxConfig?: SandboxConfig; @@ -12821,6 +14955,10 @@ export interface SessionOpenOptions { */ logInteractiveShells?: boolean; envValueMode?: SessionOpenOptionsEnvValueMode; + /** + * MCP server names disabled for this session. Disabled servers are not started or authenticated on create or cold resume. + */ + disabledMcpServers?: string[]; /** * Whether to include instructions from every MCP server in the system prompt instead of only allowlisted servers. */ @@ -12901,6 +15039,10 @@ export interface SessionOpenOptions { * Override directory for session event logs. */ eventsLogDirectory?: string; + /** + * Whether subagent callback events should be forwarded into the session event log sink. + */ + eventsLogIncludesSubagents?: boolean; /** * Override Copilot configuration directory. */ @@ -12917,6 +15059,48 @@ export interface SessionOpenOptions { */ sessionCapabilities?: SessionCapability[]; } +/** + * Per-session settings for built-in shell tools. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ShellOptions". + */ +/** @experimental */ +export interface ShellOptions { + initProfile?: ShellInitProfile; + /** + * Ordered host-provided script paths sourced before each built-in shell command when the + * entry's shell target matches the active shell. Use these for rc files, environment setup scripts, + * or other custom scripts. A script that returns a nonzero status is reported, and later scripts + * and the user command continue while the shell remains running. Because scripts are sourced into + * the command shell, `exit`, `exec`, failures under `set -e`, or other shell-terminating behavior + * can prevent continuation. Script standard output is preserved; Bash script stderr is discarded, + * PowerShell exception messages are replaced, and runtime-generated failure notices omit + * configured script paths. When sandboxing is enabled, each script must already be readable under + * the active sandbox filesystem policy. Pass an empty array to clear the list. + */ + initScripts?: ShellInitScript[]; + /** + * Flags passed to the active built-in shell process on startup, replacing its default flags. + * When omitted, the built-in Bash shell uses `--norc --noprofile`, + * and the built-in PowerShell shell uses `-NoProfile -NoLogo`. + */ + processFlags?: string[]; +} +/** + * A host-provided script sourced before each built-in shell command when its shell target matches the active shell. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ShellInitScript". + */ +/** @experimental */ +export interface ShellInitScript { + /** + * Path to the script to source. + */ + path: string; + shell: ShellInitScriptShell; +} /** * Content-exclusion policy supplied to `sessions.open` options, with rules, last-updated data, and scope. * @@ -12926,7 +15110,7 @@ export interface SessionOpenOptions { /** @experimental */ export interface SessionOpenOptionsAdditionalContentExclusionPolicy { rules: SessionOpenOptionsAdditionalContentExclusionPolicyRule[]; - last_updated_at: unknown; + last_updated_at: JsonValue; scope: SessionOpenOptionsAdditionalContentExclusionPolicyScope; } /** @@ -13075,9 +15259,7 @@ export interface SessionsOpenCloud { * * @internal */ - onTaskCreated?: { - [k: string]: unknown | undefined; - }; + onTaskCreated?: OpaqueInProcessValue; } /** * Parameters for fetching a remote session and handing it off to a new local session. @@ -13099,17 +15281,13 @@ export interface SessionsOpenHandoff { * * @internal */ - onProgress?: { - [k: string]: unknown | undefined; - }; + onProgress?: OpaqueInProcessValue; /** * In-process confirmation callback `(request) => boolean | Promise` invoked when the handoff needs the caller to confirm a non-fatal blocker (e.g. a repository mismatch between the current working directory and the remote session). Returning `true` proceeds with the handoff; returning `false` (or omitting the callback) aborts it. Marked internal because a function reference cannot cross the JSON-RPC boundary, for the same reasons as `onProgress`. * * @internal */ - onConfirm?: { - [k: string]: unknown | undefined; - }; + onConfirm?: OpaqueInProcessValue; } /** * Result of opening a session. @@ -13131,9 +15309,7 @@ export interface SessionOpenResult { * * @internal */ - sessionApi?: { - [k: string]: unknown | undefined; - }; + sessionApi?: OpaqueInProcessValue; /** * Startup prompts queued by user-level hook configs at session creation. Only populated when status is `created`; resumed sessions return an empty array. */ @@ -13252,6 +15428,23 @@ export interface SessionsCloseRequest { */ /** @experimental */ export interface SessionsCloseResult {} +/** + * Session ID to delete from disk. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionsDeleteRequest". + */ +/** @experimental */ +export interface SessionsDeleteRequest { + /** + * Session ID to delete + */ + sessionId: string; + /** + * Internal resolved session directory path to delete + */ + sessionPath?: string | null; +} /** * Session metadata records to enrich with summary and context information. * @@ -13585,6 +15778,29 @@ export interface SessionsGetLastForContextResult { */ sessionId?: string; } +/** + * Session ID whose persisted metadata should be read. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionsGetMetadataRequest". + */ +/** @experimental */ +export interface SessionsGetMetadataRequest { + /** + * Session ID to inspect + */ + sessionId: string; +} +/** + * Persisted local session metadata when the session exists. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionsGetMetadataResult". + */ +/** @experimental */ +export interface SessionsGetMetadataResult { + session?: LocalSessionMetadataValue; +} /** * Session ID to look up the persisted remote-steerable flag for. * @@ -13626,6 +15842,32 @@ export interface SessionSizes { [k: string]: number | undefined; }; } +/** + * Limit for non-empty local session IDs. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionsListNonEmptySessionIdsRequest". + */ +/** @experimental */ +export interface SessionsListNonEmptySessionIdsRequest { + /** + * Maximum number of session IDs to return. + */ + limit?: number; +} +/** + * Recent local session IDs that contain user-visible history. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionsListNonEmptySessionIdsResult". + */ +/** @experimental */ +export interface SessionsListNonEmptySessionIdsResult { + /** + * Session IDs ordered newest-first. + */ + sessionIds: string[]; +} /** * Optional source filter, metadata-load limit, and context filter applied to the returned sessions. * @@ -13858,7 +16100,7 @@ export interface SessionUpdateOptionsParams { model?: string; modelCapabilitiesOverrides?: ModelCapabilitiesOverride; /** - * Reasoning effort for the selected model (model-defined enum). + * Reasoning effort for the selected model. CAPI values are model-defined and validated against the selected model; BYOK providers may define additional values. When omitted, no effort override is applied. */ reasoningEffort?: string; reasoningSummary?: OptionsUpdateReasoningSummary; @@ -13912,12 +16154,14 @@ export interface SessionUpdateOptionsParams { * Whether shell-script safety heuristics are enabled. */ enableScriptSafety?: boolean; + shell?: ShellOptions; /** - * Shell init profile (`None` or `NonInteractive`). + * @deprecated + * Use shell.initProfile instead. Shell init profile (`None` or `NonInteractive`). */ shellInitProfile?: string; /** - * Per-shell process flags (e.g., `pwsh` arguments). + * PowerShell process flags applied to built-in and user-requested shell commands. */ shellProcessFlags?: string[]; sandboxConfig?: SandboxConfig; @@ -13939,7 +16183,7 @@ export interface SessionUpdateOptionsParams { */ disabledSkills?: string[]; /** - * Whether to discover custom instructions on demand after successful file views (AGENTS.md / CLAUDE.md / .github/copilot-instructions.md surfacing). Combined with `skipCustomInstructions` and the runtime-side `ON_DEMAND_INSTRUCTIONS` feature flag. + * Whether to discover custom instructions on demand after successful file views (AGENTS.md / CLAUDE.md / .github/copilot-instructions.md surfacing). Combined with `skipCustomInstructions`. */ enableOnDemandInstructionDiscovery?: boolean; /** @@ -14006,6 +16250,10 @@ export interface SessionUpdateOptionsParams { * Override directory for the session-events log. When unset, the runtime's default events log directory is used. */ eventsLogDirectory?: string; + /** + * Whether subagent callback events should be forwarded into the session event log sink. + */ + eventsLogIncludesSubagents?: boolean; /** * Additional content-exclusion policies to merge into the session's policy set. * @@ -14184,6 +16432,10 @@ export interface Skill { * Unique identifier for the skill */ name: string; + /** + * Canonical slash command name used to invoke the skill, without the leading '/' + */ + commandName?: string; /** * Description of what the skill does */ @@ -15000,7 +17252,7 @@ export interface Tool { * JSON Schema for the tool's input parameters */ parameters?: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }; /** * Optional instructions for how to use this tool effectively @@ -15433,17 +17685,13 @@ export interface UIEphemeralQueryRequest { * * @internal */ - onChunk?: { - [k: string]: unknown | undefined; - }; + onChunk?: OpaqueInProcessValue; /** * In-process `AbortSignal` forwarded to the model client to cancel an in-flight request. Marked internal: excluded from the public SDK surface. Replaced by an explicit cancellation token + cancel RPC in the SDK migration. * * @internal */ - abortSignal?: { - [k: string]: unknown | undefined; - }; + abortSignal?: OpaqueInProcessValue; } /** * Transient answer generated from current conversation context. @@ -15479,6 +17727,10 @@ export interface UIExitPlanModeResponse { * Feedback from the user when they declined the plan or requested changes. */ feedback?: string; + /** + * When true, the agent is instructed to end its turn without starting implementation so the client can restore the session model and auto-submit a fresh implementation turn on it. Set only when a distinct plan configuration (a different model, reasoning effort, or context tier) actually ran the planning turn. + */ + deferImplementation?: boolean; } /** * Request ID of a pending `auto_mode_switch.requested` event and the user's response. @@ -15890,15 +18142,11 @@ export interface UserSettingMetadata { /** * The effective value: the user's value if set, otherwise the default. */ - value: { - [k: string]: unknown | undefined; - }; + value: JsonValue; /** * The centrally-known default for this setting (null when no default is registered). */ - default: { - [k: string]: unknown | undefined; - }; + default: JsonValue; /** * True when the user has not set an explicit value for this setting (i.e. it is left at its default). Reflects whether the user has overridden the key, not whether the effective value happens to equal the default — a key explicitly set to a value identical to the default still reports false. */ @@ -15930,9 +18178,7 @@ export interface UserSettingsSetRequest { /** * Partial user settings to write, as a free-form object keyed by setting name */ - settings: { - [k: string]: unknown | undefined; - }; + settings: JsonValue; } /** * Outcome of writing user settings. @@ -16002,7 +18248,7 @@ export interface VisibilitySetResult { /** @experimental */ export interface WorkspaceDiffFileChange { /** - * Path to the changed file, relative to the workspace root. + * Path to the changed file, relative to the workspace root when the file lives under it. A file changed outside the workspace root keeps a `../`-relative path, or an absolute path when no relative path exists (for example a different Windows drive). */ path: string; /** @@ -16038,9 +18284,52 @@ export interface WorkspaceDiffResult { */ baseBranch?: string; /** - * Whether a requested branch diff fell back to unstaged changes because branch diff failed. + * Whether the requested diff fell back to unstaged changes, either because branch diff failed or session diff was unavailable. */ isFallback: boolean; + unavailableReason?: HistoryRewindUnavailableReason; +} +/** + * Compaction summary checkpoint to persist. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "WorkspacesAddSummaryRequest". + */ +/** @experimental */ +export interface WorkspacesAddSummaryRequest { + /** + * Summary title shown in checkpoint listings. + */ + title: string; + /** + * Markdown summary content to persist. + */ + content: string; +} +/** + * Persisted summary metadata and refreshed workspace metadata. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "WorkspacesAddSummaryResult". + */ +/** @experimental */ +export interface WorkspacesAddSummaryResult { + summary?: {}; + workspace?: {}; + [k: string]: unknown | undefined; +} +/** + * Whether the autopilot objective file exists. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "WorkspacesAutopilotObjectiveExistsResult". + */ +/** @experimental */ +export interface WorkspacesAutopilotObjectiveExistsResult { + /** + * True when the objective file exists. + */ + exists: boolean; } /** * Workspace checkpoint metadata with assigned number, human-readable title, and checkpoint filename. @@ -16080,6 +18369,19 @@ export interface WorkspacesCreateFileRequest { */ content: string; } +/** + * Result of deleting the autopilot objective file. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "WorkspacesDeleteAutopilotObjectiveResult". + */ +/** @experimental */ +export interface WorkspacesDeleteAutopilotObjectiveResult { + /** + * True when a file was deleted. + */ + deleted: boolean; +} /** * Parameters for computing a workspace diff. * @@ -16094,6 +18396,19 @@ export interface WorkspacesDiffRequest { */ ignoreWhitespace?: boolean; } +/** + * Optional session context used when creating a local workspace. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "WorkspacesEnsureRequest". + */ +/** @experimental */ +export interface WorkspacesEnsureRequest { + /** + * Opaque workspace context supplied by the session host. + */ + context?: JsonValue; +} /** * Current workspace metadata for the session, including its absolute filesystem path when available. * @@ -16155,6 +18470,19 @@ export interface WorkspacesListFilesResult { */ files: string[]; } +/** + * Autopilot objective file content, or null when missing. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "WorkspacesReadAutopilotObjectiveResult". + */ +/** @experimental */ +export interface WorkspacesReadAutopilotObjectiveResult { + /** + * Autopilot objective file content, or null when missing. + */ + content: string | null; +} /** * Checkpoint number to read. * @@ -16246,15 +18574,167 @@ export interface WorkspacesSaveLargePasteResult { sizeBytes: number; } | null; } -/** - * Standard MCP CallToolResult - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "SessionMcpAppsCallToolResult". - */ +/** + * Rollback point for local workspace summaries. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "WorkspacesTruncateSummariesRequest". + */ +/** @experimental */ +export interface WorkspacesTruncateSummariesRequest { + /** + * Number of newest summaries to keep. + */ + keepCount: number; +} +/** + * Workspace metadata fields to update. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "WorkspacesUpdateMetadataRequest". + */ +/** @experimental */ +export interface WorkspacesUpdateMetadataRequest { + /** + * Opaque workspace context supplied by the session host. + */ + context?: JsonValue; + /** + * Optional workspace display name override. + */ + name?: string; +} +/** + * Autopilot objective file content to persist. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "WorkspacesWriteAutopilotObjectiveRequest". + */ +/** @experimental */ +export interface WorkspacesWriteAutopilotObjectiveRequest { + /** + * Autopilot objective file content. + */ + content: string; +} +/** + * Result of writing the autopilot objective file. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "WorkspacesWriteAutopilotObjectiveResult". + */ +/** @experimental */ +export interface WorkspacesWriteAutopilotObjectiveResult { + /** + * Filesystem operation performed. + */ + operation: string; +} + +/** @experimental */ +export interface SessionModelListRequest { + /** + * If true, bypasses the per-session model list cache and re-fetches from CAPI. + */ + skipCache?: boolean; +} + +/** @experimental */ +export interface SessionAgentListRequest { + /** + * When true, request the session's configured built-in agents alongside custom agents. Listing applies feature, context, inclusion, exclusion, and user-disabled-agent policy, but does not evaluate transient invocation requirements such as model availability. Built-in metadata may be omitted when the session cannot project it, such as a relay session. + */ + includeBuiltInAgents?: boolean; + /** + * When true, request authored base prompt text on each AgentInfo. Prompt text may be omitted when unavailable, such as for agents projected through a relay session. + */ + includePrompt?: boolean; +} +/** + * Standard MCP CallToolResult + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionMcpAppsCallToolResult". + */ +/** @experimental */ +export interface SessionMcpAppsCallToolResult { + [k: string]: JsonValue | undefined; +} + +/** @experimental */ +export interface SessionPluginsReloadRequest { + /** + * Reload MCP server connections after refreshing plugins. Defaults to true. + */ + reloadMcp?: boolean; + /** + * Re-run custom-agent discovery after refreshing plugins. Defaults to true. + */ + reloadCustomAgents?: boolean; + /** + * Re-load user, plugin, and (subject to `deferRepoHooks`) repo hooks. Defaults to true. Has no effect when the host has not registered a hook reloader (e.g. remote sessions). + */ + reloadHooks?: boolean; + /** + * Re-discover and relaunch subprocess extensions (including plugin-shipped extensions) after refreshing plugins. Defaults to true. Has no effect when the session has no active extension controller (e.g. extensions were not requested for the session). + */ + reloadExtensions?: boolean; + /** + * When true, skip repo-level hooks during the hook reload. Use before folder trust is confirmed; load them post-trust via `sessions.loadDeferredRepoHooks`. + */ + deferRepoHooks?: boolean; +} + +/** @experimental */ +export interface SessionProviderGetEndpointRequest { + /** + * Model identifier the caller intends to use against the returned endpoint. Used to pick the correct wire shape. Omit to use whichever model the session is currently using. + */ + modelId?: string; +} + +/** @experimental */ +export interface SessionCommandsListRequest { + /** + * Include runtime built-in commands + */ + includeBuiltins?: boolean; + /** + * Include enabled user-invocable skills and commands + */ + includeSkills?: boolean; + /** + * Include commands registered by protocol clients, including SDK clients and extensions + */ + includeClientCommands?: boolean; +} + /** @experimental */ -export interface SessionMcpAppsCallToolResult { - [k: string]: unknown | undefined; +export interface SessionHistoryCompactRequest { + /** + * Optional user-provided instructions to focus the compaction summary + */ + customInstructions?: string; + /** + * What initiated this compaction request, recorded as the `trigger` on the persisted `session.compaction_start` / `session.compaction_complete` events. When absent, the compaction is persisted without trigger attribution (initiator unknown). + */ + trigger?: /** User-requested compaction, e.g. the /compact command or a direct history.compact call. */ + | "manual" + /** Compaction requested while switching to a model with a smaller context window. */ + | "model_switch"; + /** + * Context window token limit this compaction is targeting, recorded as the `tokenLimit` on the persisted `session.compaction_start` / `session.compaction_complete` events. Set it when the compaction targets a window other than the compacting model's own, e.g. switching to a model with a smaller context window: the compaction still runs on the current model, so the limit that motivated it would otherwise be lost. When absent, the events record the compacting model's own resolved limit. Attribution metadata only - it does not change how much the compaction removes. + */ + tokenLimit?: number; +} + +/** @experimental */ +export interface SessionLimitPredictionPredictRequest { + /** + * Optional model identifier override. If omitted, the session's current model is used. + */ + modelId?: string; + clientType?: SessionLimitPredictionClientType; } /** * Identifies the target session. @@ -16295,6 +18775,13 @@ export function createServerRpc(connection: MessageConnection) { */ list: async (params: ModelsListRequest): Promise => connection.sendRequest("models.list", params), + /** + * Returns the running runtime's complete catalog of well-known built-in model IDs without authentication or network access. + * + * @returns The running runtime's complete catalog of well-known built-in model IDs, including supported models and additional IDs with built-in metadata. + */ + getBuiltInCatalog: async (): Promise => + connection.sendRequest("models.getBuiltInCatalog", {}), }, /** @experimental */ tools: { @@ -16427,6 +18914,37 @@ export function createServerRpc(connection: MessageConnection) { connection.sendRequest("mcp.discover", params), }, /** @experimental */ + extensions: { + /** + * Discovers user and enabled installed-plugin extensions from persisted Copilot home state, including enablement preferences. Launch-scoped additional plugins are not included. + * + * @returns Extensions discovered from persisted Copilot home state and their effective loading mode. Launch-scoped additional plugins are not included. + */ + discover: async (): Promise => + connection.sendRequest("extensions.discover", {}), + /** + * Persistently enables extension IDs for future sessions. Active sessions are unchanged; use session.extensions.enable to update them. + * + * @param params Source-qualified extension identifiers to persistently enable for future sessions. + */ + enable: async (params: DiscoveredExtensionsEnableRequest): Promise => + connection.sendRequest("extensions.enable", params), + /** + * Persistently disables extension IDs for future sessions. Active sessions are unchanged; use session.extensions.disable to update them. + * + * @param params Source-qualified extension identifiers to persistently disable for future sessions. + */ + disable: async (params: DiscoveredExtensionsDisableRequest): Promise => + connection.sendRequest("extensions.disable", params), + }, + /** + * Registers the calling SDK client as the per-entrypoint extension launch provider. Call before creating any sessions. When omitted, the runtime temporarily falls back to its built-in Node launcher for backward compatibility. + * + * @experimental + */ + registerExtensionLaunchProvider: async (): Promise => + connection.sendRequest("registerExtensionLaunchProvider", {}), + /** @experimental */ plugins: { /** * Lists plugins installed in user/global state. @@ -16639,6 +19157,16 @@ export function createServerRpc(connection: MessageConnection) { }, }, /** @experimental */ + managedSettings: { + /** + * Discovers device-managed settings from production MDM and managed-file sources, validates them against the runtime-owned managed-settings schema, and returns the canonical JSON without requiring a session. + * + * @returns Validated device-managed settings discovered before a session exists. + */ + read: async (): Promise => + connection.sendRequest("managedSettings.read", {}), + }, + /** @experimental */ runtime: { /** * Gracefully shuts down an SDK-owned runtime. The response is sent only after cleanup completes; callers may then terminate the owned runtime process. @@ -16927,6 +19455,24 @@ export function createInternalServerRpc(connection: MessageConnection) { connection.sendRequest("connect", params), /** @experimental */ sessions: { + /** + * Reads lightweight persisted metadata for one local session without opening it. + * + * @param params Session ID whose persisted metadata should be read. + * + * @returns Persisted local session metadata when the session exists. + */ + getMetadata: async (params: SessionsGetMetadataRequest): Promise => + connection.sendRequest("sessions.getMetadata", params), + /** + * Lists recent local session IDs that contain user-visible history, omitting housekeeping-only sessions. + * + * @param params Limit for non-empty local session IDs. + * + * @returns Recent local session IDs that contain user-visible history. + */ + listNonEmptySessionIds: async (params: SessionsListNonEmptySessionIdsRequest): Promise => + connection.sendRequest("sessions.listNonEmptySessionIds", params), /** * Computes the absolute path to a session's persisted events.jsonl file. Internal: filesystem paths are only meaningful in-process (CLI and runtime share a filesystem). Currently used by the CLI's contribution-graph feature to read historical events directly. Remote SDK consumers must not depend on this; a proper event-query API would replace it if the contribution graph ever needed to work over the wire. * @@ -16945,6 +19491,13 @@ export function createInternalServerRpc(connection: MessageConnection) { */ getPersistedRemoteSteerable: async (params: SessionsGetPersistedRemoteSteerableRequest): Promise => connection.sendRequest("sessions.getPersistedRemoteSteerable", params), + /** + * Deletes one local session from disk after running the same lifecycle hooks as the session manager. + * + * @param params Session ID to delete from disk. + */ + delete: async (params: SessionsDeleteRequest): Promise => + connection.sendRequest("sessions.delete", params), /** * Gets the dynamic-context board entry count associated with a session, when available. Internal: this exists solely so CLI telemetry events (`rem_spawn_gate`, `rem_consolidation_complete`) can pair START / END board counts around the detached rem-agent spawn. "Dynamic context board" is a runtime-internal concept that is not part of the public SDK contract; the long-term plan is to relocate the telemetry emission into the runtime so this method can be deleted entirely. * @@ -17017,6 +19570,26 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin */ abort: async (params: AbortRequest): Promise => connection.sendRequest("session.abort", { sessionId, ...params }), + /** + * Interrupts the current main agent turn while leaving running background work (subagents, sidekicks, and promoted attached shells) alive. No-op when the main loop is not processing. + * + * @param params Parameters for interrupting the main agent turn. + * + * @returns Result of interrupting the main agent turn. + * + * @experimental + */ + interruptMainTurn: async (params: InterruptMainTurnRequest): Promise => + connection.sendRequest("session.interruptMainTurn", { sessionId, ...params }), + /** + * Cancels every running background agent (task-registry subagents plus sidekick agents) without interrupting the main agent loop. Promoted attached shells are left running. + * + * @returns The number of running background agents (task-registry agents) that were cancelled. + * + * @experimental + */ + cancelAllBackgroundAgents: async (): Promise => + connection.sendRequest("session.cancelAllBackgroundAgents", { sessionId }), /** * Shuts down the session and persists its final state. Awaits any deferred sessionEnd hooks before resolving so user-supplied hook scripts complete before the runtime tears down. * @@ -17113,6 +19686,15 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin */ run: async (params: FactoryRunRequest): Promise => connection.sendRequest("session.factory.run", { sessionId, ...params }), + /** + * Resumes a factory run using its persisted name, arguments, journal, and accounting. + * + * @param params Parameters for resuming a factory run from its persisted identity. + * + * @returns Resolved persisted factory identity and resumed run envelope. + */ + resume: async (params: FactoryResumeRequest): Promise => + connection.sendRequest("session.factory.resume", { sessionId, ...params }), /** * Gets the current or settled envelope for a factory run. * @@ -17122,6 +19704,33 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin */ getRun: async (params: FactoryGetRunRequest): Promise => connection.sendRequest("session.factory.getRun", { sessionId, ...params }), + /** + * Lists durable factory runs for this session in creation order. + * + * @param params Parameters for paging factory runs. + * + * @returns A page of factory runs in durable creation order. + */ + listRuns: async (params: FactoryListRunsRequest): Promise => + connection.sendRequest("session.factory.listRuns", { sessionId, ...params }), + /** + * Gets durable and live observability detail for one factory run. + * + * @param params Parameters for retrieving a factory run. + * + * @returns Full factory run observability detail. + */ + getRunDetail: async (params: FactoryGetRunRequest): Promise => + connection.sendRequest("session.factory.getRunDetail", { sessionId, ...params }), + /** + * Pages durable progress for one factory run. + * + * @param params Parameters for paging factory progress. + * + * @returns A bidirectional page of factory progress. + */ + getRunProgress: async (params: FactoryGetRunProgressRequest): Promise => + connection.sendRequest("session.factory.getRunProgress", { sessionId, ...params }), /** * Requests cancellation of a factory run and returns its run envelope. * @@ -17205,7 +19814,7 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin * * @returns The list of models available to this session. */ - list: async (params?: ModelListRequest): Promise => + list: async (params?: SessionModelListRequest): Promise => connection.sendRequest("session.model.list", { sessionId, ...params }), }, /** @experimental */ @@ -17296,6 +19905,24 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin */ getWorkspace: async (): Promise => connection.sendRequest("session.workspaces.getWorkspace", { sessionId }), + /** + * Updates workspace metadata for a local session and returns the refreshed workspace. + * + * @param params Workspace metadata fields to update. + * + * @returns Current workspace metadata for the session, including its absolute filesystem path when available. + */ + updateMetadata: async (params: WorkspacesUpdateMetadataRequest): Promise => + connection.sendRequest("session.workspaces.updateMetadata", { sessionId, ...params }), + /** + * Ensures a local session workspace exists and returns it. + * + * @param params Optional session context used when creating a local workspace. + * + * @returns Current workspace metadata for the session, including its absolute filesystem path when available. + */ + ensure: async (params: WorkspacesEnsureRequest): Promise => + connection.sendRequest("session.workspaces.ensure", { sessionId, ...params }), /** * Lists files stored in the session workspace files directory. * @@ -17335,6 +19962,54 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin */ readCheckpoint: async (params: WorkspacesReadCheckpointRequest): Promise => connection.sendRequest("session.workspaces.readCheckpoint", { sessionId, ...params }), + /** + * Adds a compaction summary checkpoint to the local session workspace. + * + * @param params Compaction summary checkpoint to persist. + * + * @returns Persisted summary metadata and refreshed workspace metadata. + */ + addSummary: async (params: WorkspacesAddSummaryRequest): Promise => + connection.sendRequest("session.workspaces.addSummary", { sessionId, ...params }), + /** + * Truncates local workspace compaction summaries after a rollback. + * + * @param params Rollback point for local workspace summaries. + * + * @returns Current workspace metadata for the session, including its absolute filesystem path when available. + */ + truncateSummaries: async (params: WorkspacesTruncateSummariesRequest): Promise => + connection.sendRequest("session.workspaces.truncateSummaries", { sessionId, ...params }), + /** + * Reads the autopilot objective state file from the local session workspace. + * + * @returns Autopilot objective file content, or null when missing. + */ + readAutopilotObjective: async (): Promise => + connection.sendRequest("session.workspaces.readAutopilotObjective", { sessionId }), + /** + * Writes the autopilot objective state file in the local session workspace. + * + * @param params Autopilot objective file content to persist. + * + * @returns Result of writing the autopilot objective file. + */ + writeAutopilotObjective: async (params: WorkspacesWriteAutopilotObjectiveRequest): Promise => + connection.sendRequest("session.workspaces.writeAutopilotObjective", { sessionId, ...params }), + /** + * Deletes the autopilot objective state file from the local session workspace. + * + * @returns Result of deleting the autopilot objective file. + */ + deleteAutopilotObjective: async (): Promise => + connection.sendRequest("session.workspaces.deleteAutopilotObjective", { sessionId }), + /** + * Checks whether the local session workspace has an autopilot objective state file. + * + * @returns Whether the autopilot objective file exists. + */ + autopilotObjectiveExists: async (): Promise => + connection.sendRequest("session.workspaces.autopilotObjectiveExists", { sessionId }), /** * Saves pasted content as a UTF-8 file in the session workspace. * @@ -17345,7 +20020,7 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin saveLargePaste: async (params: WorkspacesSaveLargePasteRequest): Promise => connection.sendRequest("session.workspaces.saveLargePaste", { sessionId, ...params }), /** - * Computes a diff for the session workspace. + * Computes a diff for the session workspace. Never rejects for a busy session: a `session`-mode diff that cannot read the session's file-change captures falls back to an unstaged git diff with `isFallback: true` and reports why in `unavailableReason`. * * @param params Parameters for computing a workspace diff. * @@ -17398,12 +20073,21 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin /** @experimental */ agent: { /** - * Lists custom agents available to the session. + * Lists agents available to the session. Defaults to custom agents only; pass includeBuiltInAgents to include the effective built-in agents. + * + * @param params Controls whether built-in agents and authored prompt text are included. + * + * @returns Agents available to the session. + */ + list: async (params?: SessionAgentListRequest): Promise => + connection.sendRequest("session.agent.list", { sessionId, ...params }), + /** + * Sets an in-memory authored prompt override for an available agent. For built-in agents, this replaces only the static base prompt while preserving runtime-owned dynamic prompt composition and behavior. The special `general-purpose` agent is not overrideable. Overrides are not persisted; resumed and forked sessions start without them, so the host must re-apply them. * - * @returns Custom agents available to the session. + * @param params An in-memory authored prompt override for an available agent. */ - list: async (): Promise => - connection.sendRequest("session.agent.list", { sessionId }), + setPrompt: async (params: AgentSetPromptRequest): Promise => + connection.sendRequest("session.agent.setPrompt", { sessionId, ...params }), /** * Gets the currently selected custom agent for the session. * @@ -17640,9 +20324,9 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin removeGitHub: async (): Promise => connection.sendRequest("session.mcp.removeGitHub", { sessionId }), /** - * Starts an individual MCP server on the live session from a caller-supplied config. Session-scoped and ephemeral: the server is added to this session's running set only and is reaped when the session ends. Does NOT modify persistent user configuration (`mcp.config.*`), so it does not affect future sessions. The server surfaces through `session.mcp.list` and the `session.mcp_servers_loaded` / `session.mcp_server_status_changed` events like any other server. + * Starts an individual MCP server on the live session. Omit `config` for a config-free start-by-name of an already-configured server (reuses the server's already-registered configuration); supply `config` to start from a caller-supplied configuration. Session-scoped and ephemeral: the server is added to this session's running set only and is reaped when the session ends. Does NOT modify persistent user configuration (`mcp.config.*`), so it does not affect future sessions. The server surfaces through `session.mcp.list` and the `session.mcp_servers_loaded` / `session.mcp_server_status_changed` events like any other server. * - * @param params Server name and configuration for an individual MCP server start. + * @param params Server name and optional configuration for an individual MCP server start. Omit `config` for a config-free start-by-name of an already-configured server. */ startServer: async (params: McpStartServerRequest): Promise => connection.sendRequest("session.mcp.startServer", { sessionId, ...params }), @@ -17680,6 +20364,13 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin */ handlePendingRequest: async (params: McpOauthHandlePendingRequest): Promise => connection.sendRequest("session.mcp.oauth.handlePendingRequest", { sessionId, ...params }), + /** + * Notifies the session that MCP OAuth authentication succeeded and updated credentials were persisted, so cached tool definitions can be refreshed. + * + * @param params Identifies the MCP server whose persisted OAuth credentials were updated. + */ + authenticationStateChanged: async (params: McpOauthAuthenticationStateChangedRequest): Promise => + connection.sendRequest("session.mcp.oauth.authenticationStateChanged", { sessionId, ...params }), /** * Starts OAuth authentication for a remote MCP server. * @@ -17689,6 +20380,15 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin */ login: async (params: McpOauthLoginRequest): Promise => connection.sendRequest("session.mcp.oauth.login", { sessionId, ...params }), + /** + * Responds to a pending MCP OAuth authorization request by its request id. + * + * @param params Pending MCP OAuth request id to respond to. + * + * @returns Indicates whether the pending MCP OAuth response was accepted. + */ + respond: async (params: McpOauthRespondRequest): Promise => + connection.sendRequest("session.mcp.oauth.respond", { sessionId, ...params }), }, /** @experimental */ headers: { @@ -17800,7 +20500,7 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin * * @param params Optional flags controlling which side effects the reload performs. */ - reload: async (params?: PluginsReloadRequest): Promise => + reload: async (params?: SessionPluginsReloadRequest): Promise => connection.sendRequest("session.plugins.reload", { sessionId, ...params }), }, /** @experimental */ @@ -17812,7 +20512,7 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin * * @returns A snapshot of the provider endpoint the session is currently configured to talk to. */ - getEndpoint: async (params?: ProviderGetEndpointRequest): Promise => + getEndpoint: async (params?: SessionProviderGetEndpointRequest): Promise => connection.sendRequest("session.provider.getEndpoint", { sessionId, ...params }), /** * Adds BYOK providers and/or models to the session's registry at runtime, extending the additive registry built from the session's `providers`/`models` options. Both fields are optional, so a call may add providers only, models only, or both. Within a single call providers are registered before models, so a model may reference a provider added in the same call; across calls a model may reference any provider already registered (from session creation or a prior add). A model whose referenced provider is not registered by the end of the call is rejected. Newly added models become selectable via `model.list` / `model.switchTo` and are inherited by sub-agents spawned afterwards. @@ -17926,7 +20626,7 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin * * @returns Slash commands available in the session, after applying any include/exclude filters. */ - list: async (params?: CommandsListRequest): Promise => + list: async (params?: SessionCommandsListRequest): Promise => connection.sendRequest("session.commands.list", { sessionId, ...params }), /** * Invokes a slash command in the session. @@ -18155,10 +20855,12 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin /** * Clears session-scoped tool permission approvals. * + * @param params Clears session-scoped tool permission approvals, and optionally the location-scoped ones. + * * @returns Indicates whether the operation succeeded. */ - resetSessionApprovals: async (): Promise => - connection.sendRequest("session.permissions.resetSessionApprovals", { sessionId }), + resetSessionApprovals: async (params: PermissionsResetSessionApprovalsRequest): Promise => + connection.sendRequest("session.permissions.resetSessionApprovals", { sessionId, ...params }), /** * Notifies the runtime that a permission prompt UI has been shown to the user. * @@ -18366,9 +21068,21 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin connection.sendRequest("session.metadata.recomputeContextTokens", { sessionId, ...params }), }, /** @experimental */ + contentExclusion: { + /** + * Checks local file system absolute paths within the session working directory against its content-exclusion policy. Results preserve input order. Unsupported paths/filesystems and unavailable policy evaluation return available false, and callers must treat every requested path as excluded. + * + * @param params Local file system absolute paths within the session working directory to check against its content-exclusion policy. + * + * @returns Batch content-exclusion result. Callers must fail closed when policy evaluation is unavailable. + */ + checkPaths: async (params: ContentExclusionCheckPathsRequest): Promise => + connection.sendRequest("session.contentExclusion.checkPaths", { sessionId, ...params }), + }, + /** @experimental */ shell: { /** - * Starts a shell command and streams output through session notifications. + * Starts a shell command and streams output through session notifications. The command runs as the leader of its own process group (POSIX) or in a dedicated job object (Windows), so a forced termination — via "shell.kill", the request timeout, or session disposal — signals that whole group/job rather than only the direct child. Two gaps are worth planning for: a command that exits on its own does not trigger that teardown, and on POSIX a descendant that moves itself into a new session or process group (for example via "setsid") leaves the signalled group, so either can leave a background process running. * * @param params Shell command to run, with optional working directory and timeout in milliseconds. * @@ -18377,7 +21091,7 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin exec: async (params: ShellExecRequest): Promise => connection.sendRequest("session.shell.exec", { sessionId, ...params }), /** - * Sends a signal to a shell process previously started via "shell.exec". + * Sends a signal to a shell process previously started via "shell.exec". The signal targets the command's whole process group (POSIX) or job object (Windows), so descendants still in that group are signalled too, not just the direct child. On POSIX a descendant that moved itself into a new session or process group (for example via "setsid") is no longer in the signalled group and survives. * * @param params Identifier of a process previously returned by "shell.exec" and the signal to send. * @@ -18413,7 +21127,7 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin * * @returns Compaction outcome with the number of tokens and messages removed, summary text, and the resulting context window breakdown. */ - compact: async (params?: HistoryCompactRequest): Promise => + compact: async (params?: SessionHistoryCompactRequest): Promise => connection.sendRequest("session.history.compact", { sessionId, ...params }), /** * Truncates persisted session history to a specific event. @@ -18424,6 +21138,31 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin */ truncate: async (params: HistoryTruncateRequest): Promise => connection.sendRequest("session.history.truncate", { sessionId, ...params }), + /** + * Lists the user turns that the session can rewind to. Never rejects for a busy session: rewind reads need the session's file-change captures to be settled, so a session that still holds active work answers with `unavailableReason: "session-busy"` and no points, which the caller can retry. + * + * @returns Rewind points and file-change-tracking availability for the session. + */ + listRewindPoints: async (): Promise => + connection.sendRequest("session.history.listRewindPoints", { sessionId }), + /** + * Previews the files that a conversation-and-files rewind would restore. + * + * @param params Event boundary to preview for conversation-and-files rewind. + * + * @returns Files and aggregate changes for a prospective rewind. + */ + previewRewind: async (params: HistoryPreviewRewindRequest): Promise => + connection.sendRequest("session.history.previewRewind", { sessionId, ...params }), + /** + * Rewinds the session conversation, optionally restoring files changed by the discarded turns. Not crash-atomic: file restore and conversation truncation are separate stores, applied in that order, so a process crash between them can leave the workspace rewound while the conversation still contains the discarded turns. There is no recovery journal; re-running the same rewind is the recovery path for a crash before truncation lands, since file restore is idempotent (already-restored files are reported as skipped) and truncation is re-derived from the still-retained boundary event. After truncation lands that boundary no longer exists, so the same request is rejected; the only stage that can still be outstanding is snapshot pruning, whose failure leaves orphan snapshots the capture store tolerates. The reverse inconsistency cannot occur, because truncation is never applied before file restore succeeds. + * + * @param params Boundary and mode for rewinding session history. + * + * @returns Structured outcome of a rewind request. + */ + rewind: async (params: HistoryRewindRequest): Promise => + connection.sendRequest("session.history.rewind", { sessionId, ...params }), /** * Cancels any in-progress background compaction on a local session. * @@ -18445,6 +21184,15 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin */ summarizeForHandoff: async (): Promise => connection.sendRequest("session.history.summarizeForHandoff", { sessionId }), + /** + * Clears the session's conversation history, keeping only system and developer messages, and seeds the fresh context window with a first user message. Must be called from inside a tool handler: the clear has to drop the results of the tool calls its wipe orphans, and it rejects when no tool call is in flight. + * + * @param params Parameters for clearing the conversation and seeding the window that replaces it. + * + * @returns What a successful clear removed. A clear that could not be applied rejects instead of reporting a count. + */ + clearContext: async (params: HistoryClearContextRequest): Promise => + connection.sendRequest("session.history.clearContext", { sessionId, ...params }), }, /** @experimental */ queue: { @@ -18455,6 +21203,67 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin */ pendingItems: async (): Promise => connection.sendRequest("session.queue.pendingItems", { sessionId }), + /** + * Moves an addressable queued item to a public visible position. + * + * @param params Parameters for moving a queued item by stable id. + * + * @returns Result of moving a queued item. + */ + moveItem: async (params: QueueMoveItemRequest): Promise => + connection.sendRequest("session.queue.moveItem", { sessionId, ...params }), + /** + * Inserts a new queued message at a public visible position. + * + * @param params Parameters for inserting a queued message at a public visible position. + * + * @returns Result of inserting a queued message. + */ + insertAt: async (params: QueueInsertAtRequest): Promise => + connection.sendRequest("session.queue.insertAt", { sessionId, ...params }), + /** + * Removes an addressable queued item by its stable id. + * + * @param params Parameters for removing a queued item by stable id. + * + * @returns Result of removing a queued item. + */ + removeAt: async (params: QueueRemoveAtRequest): Promise => + connection.sendRequest("session.queue.removeAt", { sessionId, ...params }), + /** + * Updates the text of an addressable single-message queue item. + * + * @param params Parameters for editing a single queued message. + * + * @returns Result of editing a queued message. + */ + updateText: async (params: QueueUpdateTextRequest): Promise => + connection.sendRequest("session.queue.updateText", { sessionId, ...params }), + /** + * Duplicates an addressable queued item immediately after its source. + * + * @param params Parameters for duplicating a queued item. + * + * @returns Result of duplicating a queued item. + */ + duplicateAt: async (params: QueueDuplicateAtRequest): Promise => + connection.sendRequest("session.queue.duplicateAt", { sessionId, ...params }), + /** + * Acquires or releases the queued-lane drain pause. + * + * @param params Parameters for acquiring or releasing the queued-lane drain pause. Acquisition is exclusive and non-idempotent: `paused: true` against an already-paused session fails with `queue_already_paused`. The pause is never released automatically — it is not tied to the caller's lifetime, so a client that exits without sending `paused: false` leaves the lane frozen. Release is unowned: `paused: false` clears the pause for any caller, including one that never acquired it. + */ + setDrainPaused: async (params: QueueSetDrainPausedRequest): Promise => + connection.sendRequest("session.queue.setDrainPaused", { sessionId, ...params }), + /** + * Moves an addressable queued message into the live turn's steering lane. + * + * @param params Parameters for steering a queued message into a live turn. + * + * @returns Result of trying to steer a queued message into a live turn. + */ + sendNow: async (params: QueueSendNowRequest): Promise => + connection.sendRequest("session.queue.sendNow", { sessionId, ...params }), /** * Removes the most recently queued user-facing item (LIFO). * @@ -18471,7 +21280,7 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin /** @experimental */ eventLog: { /** - * Reads a batch of session events from a cursor, optionally waiting for new events. + * Reads a batch of session events from a cursor, optionally waiting for new events. Supports tail-first reads via `direction: backward`. * * @param params Cursor, batch size, and optional long-poll/filter parameters for reading session events. * @@ -18516,6 +21325,18 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin connection.sendRequest("session.usage.getMetrics", { sessionId }), }, /** @experimental */ + limitPrediction: { + /** + * Predicts an AI-credit session limit for the session's resolved model. Returns an unavailable result instead of falling back when the current model is unresolved auto. + * + * @param params Parameters for predicting an AI-credit session limit. Omitting `modelId` uses the session's currently selected model. + * + * @returns Prediction result. Available results include prediction details; unavailable results include an explicit reason. + */ + predict: async (params?: SessionLimitPredictionPredictRequest): Promise => + connection.sendRequest("session.limitPrediction.predict", { sessionId, ...params }), + }, + /** @experimental */ remote: { /** * Enables remote session export or steering. @@ -18589,6 +21410,15 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin */ export function createInternalSessionRpc(connection: MessageConnection, sessionId: string) { return { + /** + * Queues or sends an internal system notification to the session according to its passive policy. + * + * @param params Internal request for sending a system notification. + * + * @experimental + */ + sendSystemNotification: async (params: SendSystemNotificationRequest): Promise => + connection.sendRequest("session.sendSystemNotification", { sessionId, ...params }), /** @experimental */ mcp: { /** @@ -18643,6 +21473,129 @@ export function createInternalSessionRpc(connection: MessageConnection, sessionI evaluatePredicate: async (params: SessionSettingsEvaluatePredicateRequest): Promise => connection.sendRequest("session.settings.evaluatePredicate", { sessionId, ...params }), }, + /** @experimental */ + queue: { + /** + * Returns the internal native queue snapshot for in-process session orchestration. + * + * @returns Internal snapshot of native queue state for local session orchestration. + */ + snapshot: async (): Promise => + connection.sendRequest("session.queue.snapshot", { sessionId }), + /** + * Reports whether the local session has native queued work pending. + * + * @returns Whether the native queue has pending work. + */ + hasPending: async (): Promise => + connection.sendRequest("session.queue.hasPending", { sessionId }), + /** + * Begins a native deferred-idle drain when background work has quiesced. + * + * @param params Inputs for starting a deferred-idle drain. + * + * @returns Whether a deferred-idle drain should run. + */ + beginDeferredIdleDrain: async (params: QueueBeginDeferredIdleDrainRequest): Promise => + connection.sendRequest("session.queue.beginDeferredIdleDrain", { sessionId, ...params }), + /** + * Finishes a native deferred-idle drain and reports whether to drain queue work or emit idle. + * + * @param params Inputs for completing a deferred-idle drain. + * + * @returns Action selected by the native deferred-idle drain. + */ + finishDeferredIdleDrain: async (params: QueueFinishDeferredIdleDrainRequest): Promise => + connection.sendRequest("session.queue.finishDeferredIdleDrain", { sessionId, ...params }), + /** + * Marks session.idle as deferred by native background work state. + * + * @param params Inputs for marking session.idle deferred in native state. + */ + deferSessionIdle: async (params: QueueDeferSessionIdleRequest): Promise => + connection.sendRequest("session.queue.deferSessionIdle", { sessionId, ...params }), + /** + * Consumes queued native system notifications matching an internal filter. + * + * @param params Internal filter for consuming queued system notifications. + * + * @returns Indicates whether a user-facing pending item was removed. + */ + consumeSystemNotifications: async (params: QueueConsumeSystemNotificationsRequest): Promise => + connection.sendRequest("session.queue.consumeSystemNotifications", { sessionId, ...params }), + /** + * Enqueues the internal resume-pending wake item when orphan handling needs a follow-up turn. + * + * @returns Result of enqueueing the resume-pending wake item. + */ + enqueueResumePending: async (): Promise => + connection.sendRequest("session.queue.enqueueResumePending", { sessionId }), + /** + * Drains the native local-session work queue for in-process session orchestration. + */ + process: async (): Promise => + connection.sendRequest("session.queue.process", { sessionId }), + }, + /** @experimental */ + schedule: { + /** + * Hydrates the native schedule registry from persisted session events. + */ + hydrate: async (): Promise => + connection.sendRequest("session.schedule.hydrate", { sessionId }), + /** + * Reports whether the session has an active self-paced scheduled prompt. + * + * @returns Whether the session currently has an active self-paced schedule. + */ + hasSelfPaced: async (): Promise => + connection.sendRequest("session.schedule.hasSelfPaced", { sessionId }), + /** + * Registers a relative-interval scheduled prompt. + * + * @param params Register a relative-interval scheduled prompt. + * + * @returns Result of registering or re-arming a scheduled prompt. + */ + add: async (params: ScheduleAddRequest): Promise => + connection.sendRequest("session.schedule.add", { sessionId, ...params }), + /** + * Registers a recurring cron scheduled prompt. + * + * @param params Register a cron scheduled prompt. + * + * @returns Result of registering or re-arming a scheduled prompt. + */ + addCron: async (params: ScheduleAddCronRequest): Promise => + connection.sendRequest("session.schedule.addCron", { sessionId, ...params }), + /** + * Registers an absolute-time scheduled prompt. + * + * @param params Register an absolute-time scheduled prompt. + * + * @returns Result of registering or re-arming a scheduled prompt. + */ + addAt: async (params: ScheduleAddAtRequest): Promise => + connection.sendRequest("session.schedule.addAt", { sessionId, ...params }), + /** + * Registers a self-paced scheduled prompt. + * + * @param params Register a self-paced scheduled prompt. + * + * @returns Result of registering or re-arming a scheduled prompt. + */ + addSelfPaced: async (params: ScheduleAddSelfPacedRequest): Promise => + connection.sendRequest("session.schedule.addSelfPaced", { sessionId, ...params }), + /** + * Re-arms an active self-paced scheduled prompt. + * + * @param params Re-arm a self-paced scheduled prompt. + * + * @returns Result of registering or re-arming a scheduled prompt. + */ + rearmSelfPaced: async (params: ScheduleRearmSelfPacedRequest): Promise => + connection.sendRequest("session.schedule.rearmSelfPaced", { sessionId, ...params }), + }, }; } @@ -18764,13 +21717,21 @@ export interface SessionFsHandler { */ rename(params: SessionFsRenameRequest): Promise; /** - * Executes a SQLite query against the per-session database. + * Executes a SQLite query against the per-session database. Providers apply busy handling for every call. * - * @param params SQL query, query type, and optional bind parameters for executing a SQLite query against the per-session database. + * @param params SQL query, query type, and optional bind parameters for executing a SQLite query against the per-session database. The provider applies its SQLite busy timeout for every call. * * @returns Query results including rows, columns, and rows affected, or a filesystem error if execution failed. */ sqliteQuery(params: SessionFsSqliteQueryRequest): Promise; + /** + * Executes SQLite statements atomically on the provider-owned connection. + * + * @param params Statements to execute atomically. Providers apply busy handling for every call. + * + * @returns Per-statement results, or a classified transaction error. + */ + sqliteTransaction(params: SessionFsSqliteTransactionRequest): Promise; /** * Checks whether the per-session SQLite database already exists, without creating it. * @@ -18896,6 +21857,11 @@ export function registerClientSessionApiHandlers( if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`); return handler.sqliteQuery(params); }); + connection.onRequest("sessionFs.sqliteTransaction", async (params: SessionFsSqliteTransactionRequest) => { + const handler = getHandlers(params.sessionId).sessionFs; + if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`); + return handler.sqliteTransaction(params); + }); connection.onRequest("sessionFs.sqliteExists", async (params: SessionFsSqliteExistsRequest) => { const handler = getHandlers(params.sessionId).sessionFs; if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`); @@ -18918,17 +21884,17 @@ export function registerClientSessionApiHandlers( }); } -/** Handler for `hooks` client global API methods. */ +/** Handler for `extensionLaunchProvider` client global API methods. */ /** @experimental */ -export interface HooksHandler { +export interface ExtensionLaunchProviderHandler { /** - * Dispatches one SDK callback hook from the runtime to the connection that registered it. Internal transport plumbing: clients opt in through session initialization and the Rust hook processor owns ordering, policy, timeout, and callback routing. + * Asks the registered SDK client to resolve an opaque process launch profile for one discovered extension entrypoint immediately before launch or reload. The provider must respond within 15 seconds. * - * @param params Runtime-owned wire payload for a server-to-client hook callback invocation. + * @param params A discovered extension entrypoint that the registered integrator may classify and resolve to an opaque launch profile. * - * @returns Optional output returned by an SDK callback hook. + * @returns The launch profile for a supported entrypoint. Omit launch when the provider does not support the entrypoint. */ - invoke(params: HookInvokeRequest): Promise; + resolve(params: ExtensionLaunchProviderResolveRequest): Promise; } /** Handler for `llmInference` client global API methods. */ @@ -18965,7 +21931,7 @@ export interface GitHubTelemetryHandler { /** All client global API handler groups. */ export interface ClientGlobalApiHandlers { - hooks?: HooksHandler; + extensionLaunchProvider?: ExtensionLaunchProviderHandler; llmInference?: LlmInferenceHandler; gitHubTelemetry?: GitHubTelemetryHandler; } @@ -18981,10 +21947,10 @@ export function registerClientGlobalApiHandlers( connection: MessageConnection, handlers: ClientGlobalApiHandlers, ): void { - connection.onRequest("hooks.invoke", async (params: HookInvokeRequest) => { - const handler = handlers.hooks; - if (!handler) throw new Error("No hooks client-global handler registered"); - return handler.invoke(params); + connection.onRequest("extensionLaunchProvider.resolve", async (params: ExtensionLaunchProviderResolveRequest) => { + const handler = handlers.extensionLaunchProvider; + if (!handler) throw new Error("No extensionLaunchProvider client-global handler registered"); + return handler.resolve(params); }); connection.onRequest("llmInference.httpRequestStart", async (params: LlmInferenceHttpRequestStartRequest) => { const handler = handlers.llmInference; diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts index b9c9612b8..4bdfa1994 100644 --- a/nodejs/src/generated/session-events.ts +++ b/nodejs/src/generated/session-events.ts @@ -3,6 +3,9 @@ * Generated from: session-events.schema.json */ +/** A value that can be represented losslessly on the SDK JSON wire. */ +export type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue }; + /** * Union of all session event variants emitted by the Copilot CLI runtime. */ @@ -33,13 +36,13 @@ export type SessionEvent = | UsageCheckpointEvent | ContextChangedEvent | UsageInfoEvent + | ContextClearedEvent | CompactionStartEvent | CompactionCompleteEvent | TaskCompleteEvent | UserMessageEvent | PendingMessagesModifiedEvent | AssistantTurnStartEvent - | AssistantTurnRetryEvent | AssistantIntentEvent | AssistantServerToolProgressEvent | AssistantReasoningEvent @@ -53,7 +56,6 @@ export type SessionEvent = | AssistantIdleEvent | AssistantUsageEvent | ModelCallFailureEvent - | ModelCallStartEvent | AbortEvent | ToolUserRequestedEvent | ToolExecutionStartEvent @@ -104,6 +106,7 @@ export type SessionEvent = | ExitPlanModeCompletedEvent | ToolsUpdatedEvent | BackgroundTasksChangedEvent + | FactoryRunUpdatedEvent | SkillsLoadedEvent | CustomAgentsUpdatedEvent | McpServersLoadedEvent @@ -156,6 +159,14 @@ export type Verbosity = | "medium" /** A more detailed response was requested. */ | "high"; +/** + * Who created the schedule: `user` (an explicit user action such as `/every` or `/after`) or `model` (the agent via the `manage_schedule` tool). Gates whether a scheduled skill that opted out of model invocation may fire: only user-created schedules may. + */ +export type ScheduleOrigin = + /** The schedule was created by an explicit user action, such as `/every` or `/after`. */ + | "user" + /** The schedule was created by the agent via the `manage_schedule` tool. */ + | "model"; /** * The type of operation performed on the autopilot objective state file */ @@ -233,6 +244,30 @@ export type ShutdownType = | "routine" /** The session ended because of a crash or fatal error. */ | "error"; +/** + * What initiated a conversation compaction + */ +export type CompactionTrigger = + /** Background compaction started automatically because context utilization crossed the background threshold. */ + | "threshold" + /** Compaction forced by a context-limit model response (e.g. HTTP 413) before retrying the request. */ + | "context_limit_retry" + /** User-requested compaction, e.g. the /compact command or the history.compact API. */ + | "manual" + /** Emergency compaction triggered by high process memory usage. */ + | "memory_pressure" + /** Compaction requested while switching to a model with a smaller context window. */ + | "model_switch"; +/** + * Semantic result of evaluating a task completion request + */ +export type TaskCompletionOutcome = + /** The completion request was accepted and the objective is complete. */ + | "completed" + /** The completion request was rejected because more work or validation remains. */ + | "continue" + /** Completion cannot proceed without intervention; the active objective is paused when one is identified. */ + | "blocked"; /** * The agent mode that was active when this message was sent */ @@ -371,7 +406,9 @@ export type AbortReason = /** A remote command requested the abort. */ | "remote_command" /** An MCP server delivered a user.abort notification. */ - | "user_abort"; + | "user_abort" + /** Autopilot stopped the run because the active objective reached its user-set --max-ai-credits limit. */ + | "autopilot_credit_limit"; /** * Allowed values for the `ToolExecutionStartToolDescriptionMetaUIVisibility` enumeration. */ @@ -475,7 +512,9 @@ export type SystemNotification = | SystemNotificationNewInboxMessage | SystemNotificationShellCompleted | SystemNotificationShellDetachedCompleted - | SystemNotificationInstructionDiscovered; + | SystemNotificationInstructionDiscovered + | SystemNotificationFactoryCompleted + | SystemNotificationUnclassified; /** * Whether the agent completed successfully or failed */ @@ -484,6 +523,18 @@ export type SystemNotificationAgentCompletedStatus = | "completed" /** The agent failed. */ | "failed"; +/** + * Terminal status reached by a factory execution attempt. + */ +export type SystemNotificationFactoryCompletedStatus = + /** The factory completed successfully. */ + | "completed" + /** The factory was halted. */ + | "halted" + /** The factory was cancelled. */ + | "cancelled" + /** The factory failed. */ + | "error"; /** * Details of the permission being requested */ @@ -497,6 +548,7 @@ export type PermissionRequest = | PermissionRequestCustomTool | PermissionRequestHook | PermissionRequestExtensionManagement + | PermissionRequestFactory | PermissionRequestExtensionPermissionAccess; /** * Whether this is a store or vote memory operation @@ -514,6 +566,14 @@ export type PermissionRequestMemoryDirection = | "upvote" /** Vote that the memory is incorrect or outdated. */ | "downvote"; +/** + * Operation gated by a factory permission request. + */ +export type FactoryPermissionOperation = + /** Running a registered factory, which spends subagents, active time, and AI credits under the approved limits. */ + | "run" + /** Authoring a factory, which writes JavaScript into a session-scoped extension and loads it. */ + | "author"; /** * Derived user-facing permission prompt details for UI consumers */ @@ -528,7 +588,23 @@ export type PermissionPromptRequest = | PermissionPromptRequestPath | PermissionPromptRequestHook | PermissionPromptRequestExtensionManagement + | PermissionPromptRequestFactory | PermissionPromptRequestExtensionPermissionAccess; +/** + * Why the auto-approval judge produced no usable recommendation. Present only alongside an `error` recommendation, where the human-readable reason is a fixed string and therefore cannot distinguish these cases. Intended to make a judge failure reportable by a consumer that has no access to the host's logs. + */ +/** @experimental */ +export type AutoApprovalJudgeFailureReason = + /** The judge model call exceeded its deadline. */ + | "timeout" + /** The judge model call was cancelled before it returned. */ + | "abort" + /** The judge model call completed but returned no content. */ + | "empty_response" + /** The judge model call failed (for example a transport, authentication, or rate-limit error). */ + | "model_error" + /** The judge model replied, but the reply carried no ALLOW/DENY verdict. */ + | "parse_error"; /** * Outcome of the auto-approval safety judge for a permission request. Present only when auto mode is enabled; its absence means the judge did not evaluate the request (auto mode was off). */ @@ -576,6 +652,7 @@ export type UserToolSessionApproval = | UserToolSessionApprovalMemory | UserToolSessionApprovalCustomTool | UserToolSessionApprovalExtensionManagement + | UserToolSessionApprovalFactory | UserToolSessionApprovalExtensionPermissionAccess; /** * Elicitation mode; "form" for structured input, "url" for browser-based. Defaults to "form" when absent. @@ -595,6 +672,10 @@ export type ElicitationCompletedAction = | "decline" /** The user dismissed the request. */ | "cancel"; +/** + * Opaque JSON value submitted for one field in accepted `elicitation.completed` form content. + */ +export type ElicitationCompletedContent = JsonValue | undefined; /** * Reason the runtime is requesting host-provided MCP OAuth credentials */ @@ -635,6 +716,10 @@ export type McpHeadersRefreshCompletedOutcome = | "none" /** No response arrived within the bounded window. */ | "timeout"; +/** + * Source-defined JSON payload for the custom notification + */ +export type CustomNotificationPayload = JsonValue; /** * The user's auto-mode-switch choice */ @@ -668,14 +753,18 @@ export type AutoModeResolvedReasoningBucket = /** The request looks high-reasoning; a stronger model is appropriate. */ | "high"; /** - * Which channel supplied the effective enterprise managed settings (highest-authority present layer wins wholesale) + * Summary of which managed-settings channels contributed to the effective session policy. Use the per-channel booleans for exact provenance. */ export type ManagedSettingsResolvedSource = - /** Account/org policy self-fetched from the GitHub managed-settings endpoint (higher authority). */ + /** Only the server/account channel contributed. */ | "server" - /** Device-level MDM policy discovered from plist/registry/file (lower authority). */ + /** Only the device MDM/plist/registry/file channel contributed. */ | "device" - /** No managed policy is in force (no layer contributed). */ + /** Only session-local SDK-host injection contributed. */ + | "client" + /** More than one channel contributed. Ordinary keys resolve device over server per key, while permissions compose restrictively across all present layers. */ + | "mixed" + /** No managed policy is in force (no channel contributed). */ | "none"; /** * The category of runtime action that enterprise managed settings governed (blocked or capped) @@ -740,7 +829,7 @@ export type McpServerSource = /** Server bundled with the runtime. */ | "builtin"; /** - * Connection status: connected, failed, needs-auth, pending, disabled, or not_configured + * Connection status: connected, failed, needs-auth, pending, disabled, stopped, or not_configured */ export type McpServerStatus = /** The server is connected and available. */ @@ -753,6 +842,8 @@ export type McpServerStatus = | "pending" /** The server is configured but disabled. */ | "disabled" + /** The server was intentionally stopped and can be restarted on demand when policy permits; a server quarantined by restrictive managed policy stays stopped and cannot be restarted until the policy allows it. */ + | "stopped" /** The server is not configured for this session. */ | "not_configured"; /** @@ -843,6 +934,7 @@ export interface StartData { * When set, identifies a parent session whose context this session continues — e.g., a detached headless rem-agent run launched on the parent's interactive shutdown. Telemetry from this session is reported under the parent's session_id. */ detachedFromSpawningParentSessionId?: string; + githubMcpToolConfig?: GitHubMcpToolConfig; /** * Identifier of the software producing the events (e.g., "copilot-agent") */ @@ -900,6 +992,10 @@ export interface WorkingDirectoryContext { */ headCommit?: string; hostType?: WorkingDirectoryContextHostType; + /** + * Set on the immediate preliminary event of a working-directory change, before the git context is resolved. A settled follow-up event (enriched with git context, or cwd-only for a non-repository) is always emitted afterward, so observers may defer to it. Absent on standalone/final events (e.g. relay context changes). + */ + pendingGitContext?: boolean; /** * Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) */ @@ -909,6 +1005,27 @@ export interface WorkingDirectoryContext { */ repositoryHost?: string; } +/** + * Per-session configuration for the built-in GitHub MCP server + */ +export interface GitHubMcpToolConfig { + /** + * Additional GitHub MCP tools requested by the session + */ + additionalTools?: string[]; + /** + * Additional GitHub MCP toolsets requested by the session + */ + additionalToolsets?: string[]; + /** + * Whether to use the read-write endpoint and request all toolsets + */ + enableAllTools?: boolean; + /** + * Whether to request the GitHub MCP insiders build + */ + enableInsidersMode?: boolean; +} /** * Optional session limits. */ @@ -962,7 +1079,7 @@ export interface ResumeData { */ contextTier?: ContextTier | null; /** - * When true, tool calls and permission requests left in flight by the previous session lifetime remain pending after resume and the agentic loop awaits their results. User sends are queued behind the pending work until all such requests reach a terminal state. When false (the default), any such tool calls and permission requests are immediately marked as interrupted on resume. + * When true, tool calls and permission requests left in flight by the previous session lifetime remain pending after resume and the agentic loop awaits their results. User sends are queued behind the pending work until all such requests reach a terminal state. When false or omitted, pending work is normally marked as interrupted unless the resume passively joined live work owned by another client; sessionWasActive distinguishes that case. */ continuePendingWork?: boolean; /** @@ -995,7 +1112,7 @@ export interface ResumeData { */ sessionLimits?: SessionLimitsConfig | null; /** - * True when this resume attached to a session that the runtime already had running in-memory (for example, an extension joining a session another client was actively driving). False (or omitted) for cold resumes — the runtime had to reconstitute the session from its persisted event log. + * True when this resume passively joined a session that already had live work running in the runtime - an agent turn, a native queue run, a queued resume continuation, or an in-flight send (for example, an extension joining a session another client was actively driving). False (or omitted) when the session had no live work or when the resume explicitly abandoned pending work, including cold resumes and suspended sessions that remain resident in memory. */ sessionWasActive?: boolean; verbosity?: Verbosity; @@ -1242,6 +1359,7 @@ export interface ScheduleCreatedData { * Interval between ticks in milliseconds (relative-interval schedules) */ intervalMs?: number; + origin?: ScheduleOrigin; /** * Prompt text that gets enqueued on every tick */ @@ -1515,7 +1633,7 @@ export interface ModelChangeEvent { */ export interface ModelChangeData { /** - * Reason the change happened, when not user-initiated. Currently `"rate_limit_auto_switch"` for changes triggered by the auto-mode-switch rate-limit recovery path. UI clients can use this to render contextual copy. + * Reason the change happened, when not user-initiated. `"rate_limit_auto_switch"` for changes triggered by the auto-mode-switch rate-limit recovery path, or `"refusal_fallback"` when the active model declined a request (content refusal) and the runtime switched to the configured refusal-fallback model. UI clients can use this to render contextual copy. */ cause?: string; /** @@ -2325,6 +2443,49 @@ export interface UsageInfoData { */ toolDefinitionsTokens?: number; } +/** + * Session event "session.context_cleared". Context-cleared details emitted when the host clears the conversation (the session.history.clearContext RPC / Session.clearContextMessages) + */ +export interface ContextClearedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: ContextClearedData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * Type discriminator. Always "session.context_cleared". + */ + type: "session.context_cleared"; +} +/** + * Context-cleared details emitted when the host clears the conversation (the session.history.clearContext RPC / Session.clearContextMessages) + */ +export interface ContextClearedData { + /** + * Optional initial message set after clearing + */ + initialMessage?: string; + /** + * Number of conversation messages that were cleared + */ + messagesCleared: number; +} /** * Session event "session.compaction_start". Context window breakdown at the start of LLM-powered conversation compaction */ @@ -2363,6 +2524,10 @@ export interface CompactionStartData { * Token count from non-system messages (user, assistant, tool) at compaction start */ conversationTokens?: number; + /** + * Total context tokens (system + conversation + tool definitions) at compaction start, when known + */ + currentTokens?: number; /** * Model identifier used for compaction, when known */ @@ -2371,10 +2536,15 @@ export interface CompactionStartData { * Token count from system message(s) at compaction start */ systemTokens?: number; + /** + * Model context window token limit the compaction is targeting, when known + */ + tokenLimit?: number; /** * Token count from tool definitions at compaction start */ toolDefinitionsTokens?: number; + trigger?: CompactionTrigger; } /** * Session event "session.compaction_complete". Conversation compaction results including success status, metrics, and optional error details @@ -2471,6 +2641,10 @@ export interface CompactionCompleteData { * Token count from system message(s) after compaction */ systemTokens?: number; + /** + * Model context window token limit the compaction was targeting, when known + */ + tokenLimit?: number; /** * Number of tokens removed during compaction */ @@ -2479,6 +2653,7 @@ export interface CompactionCompleteData { * Token count from tool definitions after compaction */ toolDefinitionsTokens?: number; + trigger?: CompactionTrigger; } /** * Token usage breakdown for the compaction LLM call (aligned with assistant.usage format) @@ -2587,7 +2762,16 @@ export interface TaskCompleteEvent { */ export interface TaskCompleteData { /** - * Whether the tool call succeeded. False when validation failed (e.g., invalid arguments) + * Active autopilot objective ID evaluated by the completion reviewer + */ + objectiveId?: number; + outcome?: TaskCompletionOutcome; + /** + * Label-safe runtime rationale for the completion decision (e.g. a cancellation or pause/resume downgrade), when one applies. Reviewer-authored rationale is intentionally omitted here because this event has no IFC label channel; the reviewer's findings remain available through its own labeled sub-agent events + */ + reason?: string; + /** + * Whether the task was accepted as complete. False when validation failed or completion was rejected or blocked by the reviewer */ success?: boolean; /** @@ -2656,7 +2840,7 @@ export interface UserMessageData { */ parentAgentTaskId?: string; /** - * Origin of this message, used for timeline filtering (e.g., "skill-pdf" for skill-injected messages that should be hidden from the user) + * Origin of this message, used for timeline filtering and attribution (e.g., `skill-pdf` for hidden skill injection or `agent-` for an inter-agent prompt) */ source?: string; /** @@ -3096,9 +3280,7 @@ export interface AttachmentExtensionContext { /** * Caller-supplied JSON payload */ - payload?: { - [k: string]: unknown | undefined; - }; + payload?: JsonValue; /** * Human-readable composer pill label */ @@ -3189,54 +3371,6 @@ export interface AssistantTurnStartData { */ turnId: string; } -/** - * Session event "assistant.turn_retry". Metadata for an additional model inference attempt within an existing assistant turn - */ -/** @internal */ -export interface AssistantTurnRetryEvent { - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - data: AssistantTurnRetryData; - /** - * Always true for events that are transient and not persisted to the session event log on disk. - */ - ephemeral: true; - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * Type discriminator. Always "assistant.turn_retry". - */ - type: "assistant.turn_retry"; -} -/** - * Metadata for an additional model inference attempt within an existing assistant turn - */ -export interface AssistantTurnRetryData { - /** - * Model identifier used for this retry, when known - */ - model?: string; - /** - * Provider or runtime classification that caused the retry, when known - */ - reason?: string; - /** - * Identifier of the turn whose model inference is being retried - */ - turnId: string; -} /** * Session event "assistant.intent". Agent intent description for current activity or plan */ @@ -3365,6 +3499,7 @@ export interface AssistantReasoningData { * Unique identifier for this reasoning block */ reasoningId: string; + rte?: boolean; } /** * Session event "assistant.reasoning_delta". Streaming reasoning delta for incremental extended thinking updates @@ -3534,6 +3669,14 @@ export interface AssistantMessageData { * Provider's completion / response identifier; shared across all chunks of a single API call. Used to group multi-chunk assistant utterances. */ apiCallId?: string; + /** + * Total messages the model call's response was split into, one per reasoning boundary. Absent for a single-message response; the last chunk is the one where chunkIndex is chunkCount - 1. + */ + chunkCount?: number; + /** + * Zero-based position of this message within its model call's response. Absent when the response was not split into chunks. + */ + chunkIndex?: number; /** * Provider-agnostic citations linking spans of this message's content to the sources that support them. Experimental; only populated when citation emission is enabled. * @@ -3593,6 +3736,7 @@ export interface AssistantMessageData { * GitHub request tracing ID (x-github-request-id header) for correlating with server-side logs */ requestId?: string; + rte?: boolean; serverTools?: AssistantMessageServerTools; /** * Copilot service request ID (x-copilot-service-request-id header) for CAPI log correlation @@ -3675,9 +3819,7 @@ export interface CitationReference { /** * Provider-native citation correlation data (e.g. Anthropic search_result_index / document_index), passed through opaquely for debugging and forward compatibility. */ - providerMetadata?: { - [k: string]: unknown | undefined; - }; + providerMetadata?: JsonValue; /** * Identifier of the CitationSource this reference points to (CitationSource.id). */ @@ -3746,9 +3888,9 @@ export interface AssistantMessageServerTools { functionCallNamespaces?: { [k: string]: string | undefined; }; - items?: unknown[]; + items?: JsonValue[]; provider: string; - rawContentBlocks?: unknown[]; + rawContentBlocks?: JsonValue[]; } /** * A tool invocation request from the assistant @@ -3757,9 +3899,7 @@ export interface AssistantMessageToolRequest { /** * Arguments to pass to the tool, format depends on the tool */ - arguments?: { - [k: string]: unknown | undefined; - }; + arguments?: JsonValue; /** * Resolved intention summary describing what this specific call does */ @@ -3998,6 +4138,12 @@ export interface AssistantUsageData { */ apiCallId?: string; apiEndpoint?: AssistantUsageApiEndpoint; + /** + * Number of tools available to the model for this call + * + * @internal + */ + availableToolCount?: number; /** * Updated prompt-cache expiration for this model call. Present only when the call establishes or refreshes known cache state. */ @@ -4037,6 +4183,10 @@ export interface AssistantUsageData { * Number of input tokens consumed */ inputTokens?: number; + /** + * Coarse classification of the interaction that produced this call, mirroring the session's per-request agent context (e.g. `conversation-agent`, `conversation-subagent`, `conversation-sampling`, `conversation-background`, `conversation-compaction`, `conversation-user`). Non-billing; lets consumers attribute a model call to a call class (e.g. sub-agent/sidekick) independently of the billing initiator. Absent when the runtime did not classify the request. + */ + interactionType?: string; /** * Average inter-token latency in milliseconds. Only available for streaming requests */ @@ -4045,6 +4195,12 @@ export interface AssistantUsageData { * Model identifier used for this API call */ model: string; + /** + * Number of tool calls returned by the model + * + * @internal + */ + numToolCalls?: number; /** * Number of output tokens produced */ @@ -4074,6 +4230,7 @@ export interface AssistantUsageData { * Number of output tokens used for reasoning (e.g., chain-of-thought) */ reasoningTokens?: number; + rte?: boolean; /** * Copilot service request ID (x-copilot-service-request-id header) for CAPI log correlation */ @@ -4082,6 +4239,20 @@ export interface AssistantUsageData { * Time to first token in milliseconds. Only available for streaming requests */ timeToFirstTokenMs?: number; + /** + * Tool-call counts keyed by tool name + * + * @internal + */ + toolCounts?: { + [k: string]: number | undefined; + }; + /** + * Number of tokens used by tool definitions for this call + * + * @internal + */ + toolTokenCount?: number; } /** * Per-request cost and usage data from the CAPI copilot_usage response field @@ -4289,6 +4460,7 @@ export interface ModelCallFailureData { */ reasoningEffort?: string; requestFingerprint?: ModelCallFailureRequestFingerprint; + rte?: boolean; /** * Copilot service request ID (x-copilot-service-request-id header) for CAPI log correlation */ @@ -4333,50 +4505,6 @@ export interface ModelCallFailureRequestFingerprint { */ toolResultMessageCount: number; } -/** - * Session event "model.call_start". Model API dispatch metadata for internal telemetry - */ -/** @internal */ -export interface ModelCallStartEvent { - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - data: ModelCallStartData; - /** - * Always true for events that are transient and not persisted to the session event log on disk. - */ - ephemeral: true; - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * Type discriminator. Always "model.call_start". - */ - type: "model.call_start"; -} -/** - * Model API dispatch metadata for internal telemetry - */ -export interface ModelCallStartData { - /** - * Model identifier used for this API call, when known - */ - model?: string; - /** - * Identifier of the assistant turn that initiated the model call - */ - turnId: string; -} /** * Session event "abort". Turn abort information including the reason for termination */ @@ -4450,9 +4578,7 @@ export interface ToolUserRequestedData { /** * Arguments for the tool invocation */ - arguments?: { - [k: string]: unknown | undefined; - }; + arguments?: JsonValue; /** * Unique identifier for this tool call */ @@ -4499,9 +4625,7 @@ export interface ToolExecutionStartData { /** * Arguments passed to the tool */ - arguments?: { - [k: string]: unknown | undefined; - }; + arguments?: JsonValue; /** * When true, the tool output should be displayed expanded (verbatim) in the CLI timeline */ @@ -4523,6 +4647,7 @@ export interface ToolExecutionStartData { * Tool call ID of the parent tool invocation when this event originates from a sub-agent */ parentToolCallId?: string; + rte?: boolean; shellToolInfo?: ToolExecutionStartShellToolInfo; /** * Unique identifier for this tool call @@ -4542,6 +4667,12 @@ export interface ToolExecutionStartData { * Shell-aware path hints for a shell tool's command, captured at start time so consumers can snapshot a file's pre-image before the tool runs. */ export interface ToolExecutionStartShellToolInfo { + /** + * The command with a redundant leading `cd` into the working directory removed, present only when there was one to remove. Computed with the same routine the shell driver applies before spawning, so a surface that renders this shows the text that actually runs. Consumers that display it should keep the original tool arguments available on demand. + * + * @experimental + */ + displayCommand?: string; /** * Whether the command includes a file write redirection (e.g., > or >>). */ @@ -4718,9 +4849,7 @@ export interface ToolExecutionCompleteData { * * @experimental */ - mcpMeta?: { - [k: string]: unknown | undefined; - }; + mcpMeta?: JsonValue; /** * Model identifier that generated this tool call */ @@ -4731,6 +4860,7 @@ export interface ToolExecutionCompleteData { */ parentToolCallId?: string; result?: ToolExecutionCompleteResult; + rte?: boolean; /** * Whether this tool execution ran inside a sandbox container */ @@ -4748,7 +4878,7 @@ export interface ToolExecutionCompleteData { * Tool-specific telemetry data (e.g., CodeQL check counts, grep match counts) */ toolTelemetry?: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }; /** * Identifier for the agent loop turn this tool was invoked in, matching the corresponding assistant.turn_start event @@ -4801,15 +4931,11 @@ export interface ToolExecutionCompleteResult { * * @experimental */ - mcpMeta?: { - [k: string]: unknown | undefined; - }; + mcpMeta?: JsonValue; /** * Structured content (arbitrary JSON) returned verbatim by the MCP tool */ - structuredContent?: { - [k: string]: unknown | undefined; - }; + structuredContent?: JsonValue; uiResource?: ToolExecutionCompleteUIResource; } /** @@ -4828,7 +4954,7 @@ export interface PersistedBinaryImage { * Optional metadata from the producing tool. */ metadata?: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }; /** * MIME type of the binary data @@ -4853,7 +4979,7 @@ export interface OmittedBinaryResult { * Optional metadata from the producing tool. */ metadata?: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }; /** * MIME type of the omitted binary data @@ -4883,7 +5009,7 @@ export interface BinaryAssetReference { * Optional metadata from the producing tool. */ metadata?: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }; /** * MIME type of the referenced binary data @@ -5430,6 +5556,10 @@ export interface SubagentCompletedData { * Internal name of the sub-agent */ agentName: string; + /** + * Whether the sub-agent was torn down by cancellation - its own abort, or an ancestor being killed - instead of finishing its work. Cancellation is not a failure, so the run still reports completion; this distinguishes a torn-down sub-agent from one that ran to the end. + */ + cancelled?: boolean; /** * Wall-clock duration of the sub-agent execution in milliseconds */ @@ -5644,9 +5774,7 @@ export interface HookStartData { /** * Input data passed to the hook */ - input?: { - [k: string]: unknown | undefined; - }; + input?: JsonValue; } /** * Session event "hook.end". Hook invocation completion details including output, success status, and error information @@ -5694,9 +5822,7 @@ export interface HookEndData { /** * Output data produced by the hook */ - output?: { - [k: string]: unknown | undefined; - }; + output?: JsonValue; /** * Whether the hook completed successfully */ @@ -5817,7 +5943,7 @@ export interface BinaryAssetData { * Optional metadata from the producing tool. */ metadata?: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }; /** * MIME type of the binary asset @@ -5863,6 +5989,10 @@ export interface SystemMessageData { * The system or developer prompt text sent as model input */ content: string; + /** + * Logical interaction identifier for the model run receiving this prompt + */ + interactionId?: string; metadata?: SystemMessageMetadata; /** * Optional name identifier for the message source @@ -5882,7 +6012,7 @@ export interface SystemMessageMetadata { * Template variables used when constructing the prompt */ variables?: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }; } /** @@ -6060,6 +6190,65 @@ export interface SystemNotificationInstructionDiscovered { */ type: "instruction_discovered"; } +/** + * System notification metadata for a factory execution attempt that reached a terminal state. + */ +export interface SystemNotificationFactoryCompleted { + /** + * Execution attempt that reached this terminal state. + */ + attempt: number; + /** + * Consumed AI usage in nano-AIU. + */ + consumedNanoAiu: number; + /** + * Subagents consumed by the run across all attempts. + */ + consumedSubagents: number; + /** + * Accumulated active execution time in milliseconds. + */ + elapsedMs: number; + /** + * Persisted factory name. + */ + factoryName: string; + /** + * Machine-readable terminal failure details, when present. + */ + failure?: JsonValue; + /** + * Bounded prompt-safe preview of the completed result. + */ + resultPreview?: string; + /** + * Actionable run_factory resume guidance for a resource-limit failure. + */ + retryGuidance?: string; + /** + * Factory run identifier. + */ + runId: string; + status: SystemNotificationFactoryCompletedStatus; + /** + * Type discriminator. Always "factory_completed". + */ + type: "factory_completed"; +} +/** + * System notification metadata from an external host that does not match a runtime-owned notification kind. + */ +export interface SystemNotificationUnclassified { + /** + * Opaque metadata supplied by the external host, when present. + */ + metadata?: JsonValue; + /** + * Type discriminator. Always "unclassified". + */ + type: "unclassified"; +} /** * Session event "permission.requested". Permission request notification requiring client approval with request details */ @@ -6104,6 +6293,10 @@ export interface PermissionRequestedData { * When true, this permission was already resolved by a permissionRequest hook and requires no client action */ resolvedByHook?: boolean; + /** + * Neutral risk metadata supplied by the tool host. Consumers may display this value but must not use it to bypass the permission decision. + */ + riskAssessment?: JsonValue; } /** * Shell command permission request @@ -6117,6 +6310,10 @@ export interface PermissionRequestShell { * Parsed command identifiers found in the command text */ commands: PermissionRequestShellCommand[]; + /** + * Parsed command segments, including arguments, used for managed policy matching + */ + commandSegments?: PermissionRequestShellCommandSegment[]; /** * The complete shell command text to be executed */ @@ -6133,6 +6330,10 @@ export interface PermissionRequestShell { * Permission kind discriminator */ kind: "shell"; + /** + * Whether managed policy requires a human response and forbids host auto-approval + */ + managedApprovalRequired?: boolean; /** * File paths that may be read or written by the command */ @@ -6171,6 +6372,19 @@ export interface PermissionRequestShellCommand { */ readOnly: boolean; } +/** + * A parsed shell command segment used for argument-aware managed policy matching. + */ +export interface PermissionRequestShellCommandSegment { + /** + * Full text of this command segment, including arguments + */ + fullCommandText: string; + /** + * Command identifier (e.g., executable name) + */ + identifier: string; +} /** * A URL that may be accessed by a command in a shell permission request. */ @@ -6204,6 +6418,10 @@ export interface PermissionRequestWrite { * Permission kind discriminator */ kind: "write"; + /** + * Whether managed policy requires a human response and forbids host auto-approval + */ + managedApprovalRequired?: boolean; /** * Complete new file contents for newly created files */ @@ -6233,6 +6451,10 @@ export interface PermissionRequestRead { * Permission kind discriminator */ kind: "read"; + /** + * Whether managed policy requires a human response and forbids host auto-approval + */ + managedApprovalRequired?: boolean; /** * Path of the file or directory being read */ @@ -6257,9 +6479,7 @@ export interface PermissionRequestMcp { /** * Arguments to pass to the MCP tool */ - args?: { - [k: string]: unknown | undefined; - }; + args?: JsonValue; /** * Permission kind discriminator */ @@ -6297,6 +6517,14 @@ export interface PermissionRequestUrl { * Permission kind discriminator */ kind: "url"; + /** + * Whether managed policy requires a human response and forbids host auto-approval + */ + managedApprovalRequired?: boolean; + /** + * Immediately preceding URL when this request is for a redirect target + */ + redirectedFrom?: string; /** * True when this URL fetch is requesting to bypass the sandbox network policy: either the model set requestSandboxBypass: true, or the tool re-issued the request as an interactive bypass after the network policy denied the approved URL (host opted in via sandbox.allowBypass). This is a request, not a grant: the fetch runs only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI. */ @@ -6352,9 +6580,7 @@ export interface PermissionRequestCustomTool { /** * Arguments to pass to the custom tool */ - args?: { - [k: string]: unknown | undefined; - }; + args?: JsonValue; /** * Permission kind discriminator */ @@ -6387,9 +6613,7 @@ export interface PermissionRequestHook { /** * Arguments of the tool call being gated */ - toolArgs?: { - [k: string]: unknown | undefined; - }; + toolArgs?: JsonValue; /** * Tool call ID that triggered this permission request */ @@ -6420,6 +6644,73 @@ export interface PermissionRequestExtensionManagement { */ toolCallId?: string; } +/** + * Factory run or authoring permission request + */ +export interface PermissionRequestFactory { + /** + * Canonical key used for scoped factory approvals + */ + approvalKey: string; + /** + * Whether this factory is eligible for persistent approval + */ + canPersistApproval: boolean; + declaredMaxAiCredits?: number; + declaredMaxConcurrentSubagents?: number; + declaredMaxTotalSubagents?: number; + declaredTimeoutSeconds?: number; + /** + * Factory description + */ + description: string; + /** + * Permission kind discriminator + */ + kind: "factory"; + /** + * Effective AI-credit limit; omitted means unlimited + */ + maxAiCredits?: number; + /** + * Effective concurrent-subagent limit; omitted means unlimited + */ + maxConcurrentSubagents?: number; + /** + * Effective total-subagent limit; omitted means unlimited + */ + maxTotalSubagents?: number; + /** + * Factory name + */ + name: string; + operation: FactoryPermissionOperation; + /** + * Declared factory phases + */ + phases: FactoryPermissionPhase[]; + /** + * Effective active-time limit in seconds; omitted means unlimited + */ + timeoutSeconds?: number; + /** + * Tool call ID that triggered this permission request + */ + toolCallId?: string; +} +/** + * A declared phase shown in a factory permission prompt. + */ +export interface FactoryPermissionPhase { + /** + * Optional phase detail + */ + detail?: string; + /** + * Phase title + */ + title: string; +} /** * Extension permission access request */ @@ -6471,6 +6762,10 @@ export interface PermissionPromptRequestCommands { * Prompt kind discriminator */ kind: "commands"; + /** + * Whether managed policy requires a human response and forbids host auto-approval + */ + managedApprovalRequired?: boolean; /** * Tool call ID that triggered this permission request */ @@ -6485,6 +6780,11 @@ export interface PermissionPromptRequestCommands { */ /** @experimental */ export interface PermissionAutoApproval { + failureReason?: AutoApprovalJudgeFailureReason; + /** + * Model id that produced the recommendation, when the judge was consulted and reported one. Absent for `excluded` (the judge was not consulted) and for failures that occurred before a model was selected. + */ + model?: string; /** * Human-readable reason for the judge's recommendation, when available. */ @@ -6521,6 +6821,10 @@ export interface PermissionPromptRequestWrite { * Prompt kind discriminator */ kind: "write"; + /** + * Whether managed policy requires a human response and forbids host auto-approval + */ + managedApprovalRequired?: boolean; /** * Complete new file contents for newly created files */ @@ -6548,6 +6852,10 @@ export interface PermissionPromptRequestRead { * Prompt kind discriminator */ kind: "read"; + /** + * Whether managed policy requires a human response and forbids host auto-approval + */ + managedApprovalRequired?: boolean; /** * Path of the file or directory being read */ @@ -6564,9 +6872,7 @@ export interface PermissionPromptRequestMcp { /** * Arguments to pass to the MCP tool */ - args?: { - [k: string]: unknown | undefined; - }; + args?: JsonValue; /** * Auto-approval judge information for this request; present only when auto mode is enabled. * @@ -6612,6 +6918,14 @@ export interface PermissionPromptRequestUrl { * Prompt kind discriminator */ kind: "url"; + /** + * Whether managed policy requires a human response and forbids host auto-approval + */ + managedApprovalRequired?: boolean; + /** + * Immediately preceding URL when this prompt is for a redirect target + */ + redirectedFrom?: string; /** * True when this URL fetch is requesting to bypass the sandbox network policy: either the model set requestSandboxBypass: true, or the tool re-issued the request as an interactive bypass after the network policy denied the approved URL (host opted in via sandbox.allowBypass). This is a request, not a grant: the fetch runs only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI. */ @@ -6673,9 +6987,7 @@ export interface PermissionPromptRequestCustomTool { /** * Arguments to pass to the custom tool */ - args?: { - [k: string]: unknown | undefined; - }; + args?: JsonValue; /** * Auto-approval judge information for this request; present only when auto mode is enabled. * @@ -6744,9 +7056,7 @@ export interface PermissionPromptRequestHook { /** * Arguments of the tool call being gated */ - toolArgs?: { - [k: string]: unknown | undefined; - }; + toolArgs?: JsonValue; /** * Tool call ID that triggered this permission request */ @@ -6783,6 +7093,70 @@ export interface PermissionPromptRequestExtensionManagement { */ toolCallId?: string; } +/** + * Factory run or authoring permission prompt + */ +export interface PermissionPromptRequestFactory { + /** + * Canonical key used for scoped factory approvals + */ + approvalKey: string; + /** + * Auto-approval judge information for this request; present only when auto mode is enabled. + * + * @experimental + */ + autoApproval?: PermissionAutoApproval; + /** + * Whether this factory is eligible for persistent approval + */ + canPersistApproval: boolean; + declaredMaxAiCredits?: number; + declaredMaxConcurrentSubagents?: number; + declaredMaxTotalSubagents?: number; + declaredTimeoutSeconds?: number; + /** + * Factory description + */ + description: string; + /** + * Prompt kind discriminator + */ + kind: "factory"; + /** + * Whether managed policy requires a human response and forbids host auto-approval + */ + managedApprovalRequired?: boolean; + /** + * Effective AI-credit limit; omitted means unlimited + */ + maxAiCredits?: number; + /** + * Effective concurrent-subagent limit; omitted means unlimited + */ + maxConcurrentSubagents?: number; + /** + * Effective total-subagent limit; omitted means unlimited + */ + maxTotalSubagents?: number; + /** + * Factory name + */ + name: string; + operation: FactoryPermissionOperation; + /** + * Declared factory phases + */ + phases: FactoryPermissionPhase[]; + /** + * Effective active-time limit in seconds; omitted means unlimited + */ + timeoutSeconds?: number; + /** + * Tool call ID that triggered this permission request + */ + toolCallId?: string; +} /** * Extension permission access prompt */ @@ -6956,6 +7330,19 @@ export interface UserToolSessionApprovalExtensionManagement { */ operation?: string; } +/** + * Session-scoped factory approval, optionally narrowed by approval key. + */ +export interface UserToolSessionApprovalFactory { + /** + * Optional factory operation name or canonical approval key + */ + approvalKey?: string; + /** + * Factory approval kind + */ + kind: "factory"; +} /** * Session-scoped tool-approval rule for an extension's permission-gated capability access, keyed by extension name. */ @@ -7249,7 +7636,7 @@ export interface ElicitationRequestedSchema { * Form field definitions, keyed by field name */ properties: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }; /** * List of required field names @@ -7306,12 +7693,6 @@ export interface ElicitationCompletedData { */ requestId: string; } -/** - * Opaque JSON value submitted for one field in accepted `elicitation.completed` form content. - */ -export interface ElicitationCompletedContent { - [k: string]: unknown | undefined; -} /** * Session event "sampling.requested". Sampling request from an MCP server; contains the server name and a requestId for correlation */ @@ -7349,9 +7730,7 @@ export interface SamplingRequestedData { /** * The JSON-RPC request ID from the MCP protocol */ - mcpRequestId: { - [k: string]: unknown | undefined; - }; + mcpRequestId: JsonValue; /** * Unique identifier for this sampling request; used to respond via session.respondToSampling() */ @@ -7700,12 +8079,6 @@ export interface CustomNotificationData { */ version?: number; } -/** - * Source-defined JSON payload for the custom notification - */ -export interface CustomNotificationPayload { - [k: string]: unknown | undefined; -} /** * Optional source-defined string identifiers describing the payload subject */ @@ -7749,9 +8122,7 @@ export interface ExternalToolRequestedData { /** * Arguments to pass to the external tool */ - arguments?: { - [k: string]: unknown | undefined; - }; + arguments?: JsonValue; /** * Unique identifier for this request; used to respond via session.respondToExternalTool() */ @@ -8177,6 +8548,10 @@ export interface AutoModeResolvedEvent { */ /** @experimental */ export interface AutoModeResolvedData { + /** + * Models offered to the router for this resolution + */ + availableModels?: string[]; /** * Ordered candidate model list the router returned, when not a fallback */ @@ -8191,18 +8566,50 @@ export interface AutoModeResolvedData { * The concrete model the session will use after any intent refinement */ chosenModel: string; + /** + * The chosen model's score shortfall relative to the top candidate + */ + chosenShortfall?: number; /** * Classifier confidence for the predicted label, when available */ confidence?: number; + /** + * End-to-end client wait time for the router request in milliseconds + */ + endToEndLatencyMs?: number; + /** + * Whether the router fell back to the standard Auto selection + */ + fallback?: boolean; + /** + * Server-provided reason for falling back, when available + */ + fallbackReason?: string; + /** + * Whether the routed prompt contained an image + */ + hasImage?: boolean; /** * The predicted classifier label (e.g. `needs_reasoning`), when available */ predictedLabel?: string; reasoningBucket?: AutoModeResolvedReasoningBucket; + /** + * Server-reported router processing time in milliseconds + */ + routerLatencyMs?: number; + /** + * The routing method the server applied, when Auto Intent ran + */ + routingMethod?: string; + /** + * Whether a sticky model choice overrode the router result + */ + stickyOverride?: boolean; } /** - * Session event "session.managed_settings_resolved". Enterprise managed-settings resolution: the effective managed settings the session applied and where they came from, so SDK clients can show users what is enterprise-managed and by which authority. Fires whenever managed policy is (re)applied — at session start, on resume, and on account switch. This is an ephemeral live snapshot (delivered to subscribers but not persisted to the session event log), because at session start it resolves before `session.start` is emitted; for a session-independent pull, use the SDK `getManagedSettings()` API, which returns the identical payload. Managed settings have a single authoritative source, so the highest-authority present layer (server > device) wins wholesale; `bypassPermissionsDisabled` is deny-wins across layers. Marked experimental while the managed-settings surface stabilizes. + * Session event "session.managed_settings_resolved". Enterprise managed-settings resolution: the effective managed settings the session applied and which channels contributed, so SDK clients can show users what is enterprise-managed. Fires whenever managed policy is (re)applied — at session start, on resume, and on account switch. This is an ephemeral live snapshot (delivered to subscribers but not persisted to the session event log), because at session start it resolves before `session.start` is emitted. Device values take precedence over server values per ordinary key, while permissions compose restrictively across device, server, and SDK-client layers. The account-scoped `getManagedSettings()` API does not include session-local client injection. Marked experimental while the managed-settings surface stabilizes. */ /** @experimental */ export interface ManagedSettingsResolvedEvent { @@ -8233,7 +8640,7 @@ export interface ManagedSettingsResolvedEvent { type: "session.managed_settings_resolved"; } /** - * Enterprise managed-settings resolution: the effective managed settings the session applied and where they came from, so SDK clients can show users what is enterprise-managed and by which authority. Fires whenever managed policy is (re)applied — at session start, on resume, and on account switch. This is an ephemeral live snapshot (delivered to subscribers but not persisted to the session event log), because at session start it resolves before `session.start` is emitted; for a session-independent pull, use the SDK `getManagedSettings()` API, which returns the identical payload. Managed settings have a single authoritative source, so the highest-authority present layer (server > device) wins wholesale; `bypassPermissionsDisabled` is deny-wins across layers. Marked experimental while the managed-settings surface stabilizes. + * Enterprise managed-settings resolution: the effective managed settings the session applied and which channels contributed, so SDK clients can show users what is enterprise-managed. Fires whenever managed policy is (re)applied — at session start, on resume, and on account switch. This is an ephemeral live snapshot (delivered to subscribers but not persisted to the session event log), because at session start it resolves before `session.start` is emitted. Device values take precedence over server values per ordinary key, while permissions compose restrictively across device, server, and SDK-client layers. The account-scoped `getManagedSettings()` API does not include session-local client injection. Marked experimental while the managed-settings surface stabilizes. */ /** @experimental */ export interface ManagedSettingsResolvedData { @@ -8242,7 +8649,11 @@ export interface ManagedSettingsResolvedData { */ bypassPermissionsDisabled: boolean; /** - * Whether the device (MDM/plist/registry/file) managed-settings layer was present + * Whether a session-local permissions layer injected by the SDK host was present + */ + clientManaged?: boolean; + /** + * Whether an actual device MDM/plist/registry/file managed-settings layer was present */ deviceManaged: boolean; /** @@ -8253,6 +8664,10 @@ export interface ManagedSettingsResolvedData { * The setting keys under enterprise management in the effective managed settings (e.g. `model`, `enabledPlugins`, `permissions`). Empty when no managed settings are in force. */ managedKeys: string[]; + /** + * Whether at least two managed sources supplied permission allowlists, so enforcement intersects them and the flattened settings payload omits `permissions.allow`. + */ + permissionsAllowIntersected?: boolean; /** * Whether the server (account/org) managed-settings layer was present */ @@ -8260,9 +8675,7 @@ export interface ManagedSettingsResolvedData { /** * The effective (resolved) managed settings values, so clients can render exactly what is enforced. Absent when no managed policy is in force. */ - settings?: { - [k: string]: unknown | undefined; - }; + settings?: JsonValue; source: ManagedSettingsResolvedSource; } /** @@ -8598,6 +9011,48 @@ export interface BackgroundTasksChangedEvent { * Empty payload for `session.background_tasks_changed`, indicating background task state changed. */ export interface BackgroundTasksChangedData {} +/** + * Session event "factory.run_updated". Ephemeral invalidation signal for a changed factory run. + */ +/** @experimental */ +export interface FactoryRunUpdatedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: FactoryRunUpdatedData; + /** + * Always true for events that are transient and not persisted to the session event log on disk. + */ + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * Type discriminator. Always "factory.run_updated". + */ + type: "factory.run_updated"; +} +/** + * Ephemeral invalidation signal for a changed factory run. + */ +/** @experimental */ +export interface FactoryRunUpdatedData { + /** + * Monotonic revision now available for the run. + */ + revision: number; + runId: string; +} /** * Session event "session.skills_loaded". Payload of `session.skills_loaded` listing resolved skill metadata. */ @@ -8645,6 +9100,10 @@ export interface SkillsLoadedSkill { * Optional freeform hint describing the skill's expected arguments, from the `argument-hint` frontmatter field */ argumentHint?: string; + /** + * Canonical slash command name used to invoke the skill, without the leading '/' + */ + commandName?: string; /** * Description of what the skill does */ @@ -9066,9 +9525,7 @@ export interface CanvasOpenedData { /** * Input supplied when the instance was opened */ - input?: { - [k: string]: unknown | undefined; - }; + input?: JsonValue; /** * Stable caller-supplied canvas instance identifier */ @@ -9163,9 +9620,7 @@ export interface CanvasRegistryChangedCanvas { /** * JSON Schema for canvas open input */ - inputSchema?: { - [k: string]: unknown | undefined; - }; + inputSchema?: JsonValue; } /** * A single action within a canvas declaration, with its name, optional description, and optional input schema. @@ -9179,9 +9634,7 @@ export interface CanvasRegistryChangedCanvasAction { /** * JSON Schema for action input */ - inputSchema?: { - [k: string]: unknown | undefined; - }; + inputSchema?: JsonValue; /** * Action name */ @@ -9332,9 +9785,7 @@ export interface CanvasRecordedData { /** * Input supplied when the instance was opened */ - input?: { - [k: string]: unknown | undefined; - }; + input?: JsonValue; /** * Stable caller-supplied canvas instance identifier */ @@ -9470,7 +9921,7 @@ export interface McpAppToolCallCompleteData { * Arguments passed to the tool by the app view, if any */ arguments?: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }; /** * Wall-clock duration of the underlying tools/call in milliseconds @@ -9481,7 +9932,7 @@ export interface McpAppToolCallCompleteData { * Standard MCP CallToolResult returned by the server. Present whether or not the call set isError. */ result?: { - [k: string]: unknown | undefined; + [k: string]: JsonValue | undefined; }; /** * Name of the MCP server hosting the tool diff --git a/nodejs/src/index.ts b/nodejs/src/index.ts index c1ab28915..f91e351d3 100644 --- a/nodejs/src/index.ts +++ b/nodejs/src/index.ts @@ -12,6 +12,7 @@ export { CopilotClient } from "./client.js"; export { RuntimeConnection } from "./types.js"; export { BuiltInTools, ToolSet } from "./toolSet.js"; export { CopilotSession, type AssistantMessageEvent } from "./session.js"; +export { defineFactory, FactoryResumeError, isFactoryRunTerminal } from "./factory.js"; export { Canvas, CanvasError, @@ -26,12 +27,14 @@ export { export { defineTool, approveAll, + createAttributedPermissionResult, convertMcpCallToolResult, createSessionFsAdapter, CopilotRequestHandler, CopilotWebSocketHandler, CopilotWebSocketCloseStatus, CopilotWebSocketForwarder, + SessionFsSqliteTransactionFailure, SYSTEM_MESSAGE_SECTIONS, } from "./types.js"; // Re-export the generated session-event types (every *Event interface and @@ -39,13 +42,15 @@ export { // consumers can import them directly from "@github/copilot-sdk" instead of // reaching into the package's internal dist layout. See issue #1156. // -// Three names from this file are also explicitly exported elsewhere in this +// Six names from this file are also explicitly exported elsewhere in this // module — `SessionEvent` (re-exported below from `./types.js`), -// `PermissionRequest` (re-exported below from `./types.js`), and -// `AssistantMessageEvent` (re-exported above from `./session.js`). Per the -// ECMAScript module spec, the explicit named re-exports shadow the names -// arriving via `export type *`, so the hand-authored public API surface for -// those three identifiers is preserved unchanged. +// `PermissionRequest` (re-exported below from `./types.js`), +// `PermissionRequestedData`/`PermissionRequestedEvent` (also re-exported below +// from `./types.js`), `AssistantMessageEvent` (re-exported above from +// `./session.js`), and `JsonValue` (re-exported below from `./factory.js`). +// Per the ECMAScript module spec, the explicit named re-exports +// shadow the names arriving via `export type *`, so the hand-authored public API +// surface for those six identifiers is preserved unchanged. export type * from "./generated/session-events.js"; export type { CommandContext, @@ -57,6 +62,12 @@ export type { AutoModeSwitchHandler, AutoModeSwitchRequest, AutoModeSwitchResponse, + AgentStopHandler, + AgentStopHookInput, + AgentStopHookOutput, + UserPromptTransformedHandler, + UserPromptTransformedHookInput, + UserPromptTransformedHookOutput, CopilotClientMode, CopilotClientOptions, CopilotExpAssignmentResponse, @@ -82,6 +93,7 @@ export type { ForegroundSessionInfo, GetAuthStatusResponse, GetStatusResponse, + GitHubMcpToolConfig, GitHubTelemetryNotification, GitHubTelemetryEvent, GitHubTelemetryClientInfo, @@ -89,12 +101,16 @@ export type { LargeToolOutputConfig, MemoryConfiguration, UiInputOptions, + FactoryLimits, + FactoryMeta, MCPStdioServerConfig, MCPHTTPServerConfig, MCPServerConfig, DefaultAgentConfig, BearerTokenProvider, MessageOptions, + ManagedSettings, + ManagedSettingsPermissions, ModelBilling, ModelBillingTokenPrices, ModelBillingTokenPricesLongContext, @@ -106,7 +122,14 @@ export type { NamedProviderConfig, PermissionHandler, PermissionRequest, + PermissionRequestedData, + PermissionRequestedEvent, PermissionRequestResult, + AttributedPermissionResult, + PermissionDecisionContext, + PermissionDecisionOutcome, + PermissionDecisionSource, + PermissionDecisionSurface, ProviderConfig, ProviderModelConfig, ProviderTokenArgs, @@ -126,6 +149,7 @@ export type { SessionLifecycleEventMetadata, SessionLifecycleEventType, SessionLifecycleHandler, + SessionHooks, SessionCreatedEvent, SessionDeletedEvent, SessionUpdatedEvent, @@ -141,6 +165,8 @@ export type { SessionFsSqliteQueryResult, SessionFsSqliteQueryType, SessionFsSqliteProvider, + SessionFsSqliteStatement, + SessionFsSqliteTransactionErrorClass, CopilotRequestContext, SystemMessageAppendConfig, SystemMessageConfig, @@ -161,3 +187,26 @@ export type { TypedSessionLifecycleHandler, ZodSchema, } from "./types.js"; +export type { + RunOptions, + ResumeOptions, + FactoryResumeErrorCode, + SessionFactoryApi, + FactoryAgentOptions, + FactoryContext, + FactoryDefinition, + FactoryHandle, + FactoryJsonSchema, + JsonValue, + FactoryPipelineStage, + FactoryStepOptions, + FactoryRunResult, + FactoryRunStatus, + FactoryRunSummary, + FactoryRunDetail, + FactoryProgressPage, + FactoryProgressLine, + FactoryPhaseObservation, + FactoryPhaseStatus, + FactoryAgentSummary, +} from "./factory.js"; diff --git a/nodejs/src/session.ts b/nodejs/src/session.ts index 1f71209de..48e41483b 100644 --- a/nodejs/src/session.ts +++ b/nodejs/src/session.ts @@ -7,6 +7,7 @@ * @module session */ +import { AsyncLocalStorage } from "node:async_hooks"; import type { MessageConnection } from "vscode-jsonrpc/node.js"; import { ConnectionError, ErrorCodes, ResponseError } from "vscode-jsonrpc/node.js"; import { createSessionRpc } from "./generated/rpc.js"; @@ -15,10 +16,13 @@ import type { CanvasActionInvokeResult, CurrentToolMetadata, McpOauthPendingRequestResponse, + FactoryLogLine, + FactoryRunResult as WireFactoryRunResult, } from "./generated/rpc.js"; import { type Canvas, CanvasError } from "./canvas.js"; import type { OpenCanvasInstance } from "./generated/rpc.js"; import { getTraceContext } from "./telemetry.js"; +import { isAttributedPermissionResult } from "./types.js"; import type { CommandHandler, AutoModeSwitchHandler, @@ -38,6 +42,7 @@ import type { McpAuthRequest, PermissionHandler, PermissionRequest, + PermissionRequestResult, ContextTier, ReasoningEffort, ReasoningSummary, @@ -60,6 +65,55 @@ import type { UserInputRequest, UserInputResponse, } from "./types.js"; +import { + FACTORY_AGENT_OPTION_KEYS, + getFactoryDefinition, + FactoryResumeError, + isFactoryRunTerminal, + type FactoryResumeErrorCode, + type FactoryRunResult, + type FactoryAgentOptions, + type RunOptions, + type SessionFactoryApi, + type FactoryContext, + type FactoryHandle, + type JsonValue, + type FactoryStepOptions, +} from "./factory.js"; + +function isFactoryResumeErrorCode(value: unknown): value is FactoryResumeErrorCode { + return ( + value === "not_found" || + value === "non_resumable" || + value === "already_active" || + value === "factory_already_running" || + value === "factory_limits_invalid" || + value === "factory_session_disposed" || + value === "factory_storage_unavailable" || + value === "factory_storage_corrupt" + ); +} + +function copyDefinedFactoryAgentOption( + source: FactoryAgentOptions, + target: FactoryAgentOptions, + key: TKey +): void { + const value = source[key]; + if (value !== undefined) { + target[key] = value; + } +} + +const factoryExecutionStore = new AsyncLocalStorage<{ active: boolean }>(); + +function throwIfFactoryExecutionIsActive(): void { + if (factoryExecutionStore.getStore()?.active) { + throw new Error( + "factory.run and factory.resume are not allowed while a factory body is running on this call path." + ); + } +} /** * Convert a raw hook input received over the wire into its public-facing shape. @@ -74,9 +128,18 @@ function deserializeHookInput(raw: unknown): unknown { ) { return raw; } - const obj = raw as Record & { timestamp: number; cwd?: string }; - const { cwd, ...rest } = obj; - return { ...rest, timestamp: new Date(obj.timestamp), workingDirectory: cwd }; + const obj = raw as Record & { + timestamp: number; + cwd?: string; + stop_hook_active?: boolean; + }; + const { cwd, stop_hook_active, ...rest } = obj; + return { + ...rest, + timestamp: new Date(obj.timestamp), + workingDirectory: cwd, + ...(stop_hook_active === undefined ? {} : { stopHookActive: stop_hook_active }), + }; } function isOpenCanvasInstance(value: unknown): value is OpenCanvasInstance { @@ -94,6 +157,227 @@ function isOpenCanvasInstance(value: unknown): value is OpenCanvasInstance { ); } +const FACTORY_LOG_FLUSH_DELAY_MS = 10; +const MAX_FACTORY_FANOUT_ITEMS = 4096; + +function assertFactoryFanoutSize(kind: "parallel" | "pipeline", size: number): void { + if (size > MAX_FACTORY_FANOUT_ITEMS) { + throw new Error( + `${kind}() accepts at most ${MAX_FACTORY_FANOUT_ITEMS} items; got ${size}.` + ); + } +} + +async function runFactoryParallel( + thunks: Array<() => Promise | TResult> +): Promise> { + if (!Array.isArray(thunks)) { + throw new Error( + "parallel() expects an array of functions, not promises. Wrap each call: () => agent(...)" + ); + } + assertFactoryFanoutSize("parallel", thunks.length); + if (thunks.some((thunk) => typeof thunk !== "function")) { + throw new Error( + "parallel() expects an array of functions, not promises. Wrap each call: () => agent(...)" + ); + } + return Promise.all( + thunks.map((thunk) => + Promise.resolve() + .then(() => thunk()) + .catch((error) => { + // Cancellation and hard runtime failures must propagate out + // of the combinator rather than be mapped to a successful + // `null`; otherwise an aborted run, or one that hit a + // resource ceiling or durable-state failure, could be + // reported as completed. An ordinary subagent failure never + // rejects — it already resolves `null`. + if (isFactoryFatalError(error)) { + throw error; + } + return null; + }) + ) + ); +} + +async function runFactoryPipeline( + items: unknown[], + ...stages: Array< + (previous: unknown, item: unknown, index: number) => Promise | unknown + > +): Promise { + if (!Array.isArray(items)) { + throw new Error("pipeline(items, ...stages): items must be an array"); + } + assertFactoryFanoutSize("pipeline", items.length); + return Promise.all( + items.map(async (item, index) => { + let previous = item; + for (const stage of stages) { + try { + previous = await stage(previous, item, index); + } catch (error) { + // Propagate cancellation and hard runtime failures instead + // of mapping them to `null`, so an aborted stage — or one + // that hit a resource ceiling or durable-state failure — + // does not let the run report success. + if (isFactoryFatalError(error)) { + throw error; + } + return null; + } + } + return previous; + }) + ); +} + +class FactoryProgressBuffer { + private nextSeq = 0; + private pending: FactoryLogLine[] = []; + private flushTimer?: ReturnType; + private flushTail: Promise = Promise.resolve(); + private flushError: unknown; + private flushFailed = false; + private closed = false; + + constructor(private readonly send: (lines: FactoryLogLine[]) => Promise) {} + + enqueue(kind: FactoryLogLine["kind"], text: string): void { + if (this.closed) { + throw new Error("Cannot log after the factory run has settled"); + } + + this.pending.push({ seq: this.nextSeq++, kind, text }); + this.scheduleFlush(); + } + + async flush(): Promise { + this.clearFlushTimer(); + const lines = this.pending.splice(0); + if (lines.length > 0) { + this.flushTail = this.flushTail.then(async () => { + try { + await this.send(lines); + } catch (error) { + if (!this.flushFailed) { + this.flushFailed = true; + this.flushError = error; + } + } + }); + } + await this.flushTail; + if (this.flushFailed) { + throw this.flushError; + } + } + + async close(): Promise { + this.closed = true; + this.clearFlushTimer(); + const lines = this.pending.splice(0); + await this.flushTail; + if (this.flushFailed) { + console.warn( + "Ignoring a background factory progress flush failure after the factory body settled", + this.flushError + ); + } + if (lines.length > 0) { + try { + await this.send(lines); + } catch (error) { + console.warn( + "Failed to flush final factory progress after the factory body settled", + error + ); + } + } + } + + private scheduleFlush(): void { + if (this.flushTimer !== undefined) { + return; + } + this.flushTimer = setTimeout(() => { + this.flushTimer = undefined; + void this.flush().catch(() => {}); + }, FACTORY_LOG_FLUSH_DELAY_MS); + this.flushTimer.unref?.(); + } + + private clearFlushTimer(): void { + if (this.flushTimer !== undefined) { + clearTimeout(this.flushTimer); + this.flushTimer = undefined; + } + } +} + +async function awaitFactoryOperation( + operation: () => Promise, + signal: AbortSignal +): Promise { + // The operation is a thunk so an already-aborted run never dispatches the + // RPC at all, rather than sending it and rejecting locally afterwards. + let rejectAbort: ((reason?: unknown) => void) | undefined; + const abortPromise = new Promise((_resolve, reject) => { + rejectAbort = reject; + }); + const onAbort = () => + rejectAbort?.(signal.reason ?? new DOMException("Factory run was aborted", "AbortError")); + // Register before the abort check and before dispatching, so an abort can + // neither be missed by a not-yet-attached listener nor start work on an + // already-cancelled run. + signal.addEventListener("abort", onAbort, { once: true }); + try { + throwIfFactoryAborted(signal); + return await Promise.race([operation(), abortPromise]); + } finally { + signal.removeEventListener("abort", onAbort); + } +} + +function throwIfFactoryAborted(signal: AbortSignal): void { + if (signal.aborted) { + throw signal.reason ?? new DOMException("Factory run was aborted", "AbortError"); + } +} + +/** + * Whether an error represents factory run cancellation (an `AbortError`-shaped + * rejection from {@link awaitFactoryOperation}). Cancellation must bubble out of + * `parallel`/`pipeline` rather than being flattened into a `null` result. + */ +function isFactoryAbortError(error: unknown): boolean { + return ( + typeof error === "object" && + error !== null && + "name" in error && + (error as { name?: unknown }).name === "AbortError" + ); +} + +/** + * Errors a factory combinator must never swallow into a `null` item. + * + * Cooperative cancellation aborts the run, and a rejected RPC is a hard + * runtime failure — a reached limit, a durable-state failure, or a dropped + * transport — that must terminate the run rather than be reported as a + * successfully-`null` item. An ordinary subagent failure does not reject; the + * runtime already resolves it as `null`. + */ +function isFactoryFatalError(error: unknown): boolean { + return ( + isFactoryAbortError(error) || + error instanceof ResponseError || + error instanceof ConnectionError + ); +} + /** Assistant message event - the final response from the assistant. */ export type AssistantMessageEvent = Extract; @@ -138,6 +422,8 @@ export class CopilotSession { private canvases: Map = new Map(); private bearerTokenProviders: Map = new Map(); private commandHandlers: Map = new Map(); + private factories = new Map>(); + private factoryAbortControllers = new Map>(); private permissionHandler?: PermissionHandler; private mcpAuthHandler?: McpAuthHandler; private userInputHandler?: UserInputHandler; @@ -148,6 +434,7 @@ export class CopilotSession { private transformCallbacks?: Map; private _rpc: ReturnType | null = null; private traceContextProvider?: TraceContextProvider; + private readonly managedSettingsEnabled: boolean; private _capabilities: SessionCapabilities = {}; private openCanvasInstances: OpenCanvasInstance[] = []; private disconnected = false; @@ -155,6 +442,167 @@ export class CopilotSession { /** @internal Client session API handlers, populated by CopilotClient during create/resume. */ clientSessionApis: ClientSessionApiHandlers = {}; + /** + * Friendly factory API for running registered factories by name or handle. + * + * @experimental Part of the experimental Agent Factories surface and may + * change or be removed in future SDK or CLI releases. + */ + readonly factory: SessionFactoryApi = { + run: (async ( + nameOrHandle: string | FactoryHandle, + options?: RunOptions + ): Promise => { + throwIfFactoryExecutionIsActive(); + const name = + typeof nameOrHandle === "string" + ? nameOrHandle + : getFactoryDefinition(nameOrHandle).meta.name; + if (options?.resumeFromRunId !== undefined) { + return this.factory.resume(options.resumeFromRunId, { + limits: options.limits, + }); + } + const envelope = await this.rpc.factory.run({ + name, + args: options?.args === undefined ? {} : options.args, + options: { + limits: options?.limits, + }, + }); + + return this.settleFactoryRun(envelope); + }) as SessionFactoryApi["run"], + resume: (async (runId: string, options?: Parameters[1]) => { + throwIfFactoryExecutionIsActive(); + let response; + try { + response = await this.rpc.factory.resume({ + runId, + limits: options?.limits, + }); + } catch (error) { + if ( + error instanceof ResponseError && + typeof error.data === "object" && + error.data !== null + ) { + const code = (error.data as { code?: unknown }).code; + if (isFactoryResumeErrorCode(code)) { + throw new FactoryResumeError(code, error.message); + } + } + throw error; + } + return this.settleFactoryRun(response.run); + }) as SessionFactoryApi["resume"], + getRun: async (runId) => this.rpc.factory.getRun({ runId }), + waitForRun: (runId, options) => this.waitForFactoryRun(runId, options?.signal), + listRuns: async () => (await this.rpc.factory.listRuns({})).runs, + getRunDetail: (runId) => this.rpc.factory.getRunDetail({ runId }), + getRunProgress: (runId, options = {}) => + this.rpc.factory.getRunProgress({ runId, ...options }), + cancel: async (runId) => this.rpc.factory.cancel({ runId }), + }; + + /** + * Resolve a start/resume envelope into the terminal envelope callers expect. + * + * The CLI may answer `session.factory.run` and `session.factory.resume` + * before the run settles, so a non-terminal envelope is followed by a wait + * on the run's terminal state. + */ + private settleFactoryRun(envelope: WireFactoryRunResult): Promise { + if (isFactoryRunTerminal(envelope.status)) { + return Promise.resolve(envelope); + } + return this.waitForFactoryRun(envelope.runId); + } + + /** + * Resolve when a factory run reaches a terminal status. + * + * The subscription is installed *before* the first read so a transition + * landing between the two cannot be missed, and re-reads are serialized so + * overlapping invalidation events cannot interleave — the run's revision + * advances once per operation, so a burst of events is common and must + * collapse into a single in-flight read. A bounded periodic re-read keeps a + * dropped invalidation from leaving the wait pending forever. + */ + private waitForFactoryRun(runId: string, signal?: AbortSignal): Promise { + const abortError = (): unknown => + signal?.reason ?? new DOMException("Factory run wait was aborted", "AbortError"); + if (signal?.aborted === true) { + return Promise.reject(abortError()); + } + + return new Promise((resolve, reject) => { + let settled = false; + let reading = false; + let rereadRequested = false; + let pollHandle: ReturnType | undefined; + let unsubscribe: (() => void) | undefined; + let onAbort: (() => void) | undefined; + + const finish = (complete: () => void): void => { + if (settled) { + return; + } + settled = true; + if (pollHandle !== undefined) { + clearInterval(pollHandle); + } + unsubscribe?.(); + if (onAbort !== undefined) { + signal?.removeEventListener("abort", onAbort); + } + complete(); + }; + + const read = async (): Promise => { + if (settled) { + return; + } + if (reading) { + rereadRequested = true; + return; + } + reading = true; + try { + do { + rereadRequested = false; + const envelope = await this.rpc.factory.getRun({ runId }); + if (isFactoryRunTerminal(envelope.status)) { + finish(() => resolve(envelope)); + return; + } + } while (rereadRequested && !settled); + } catch (error) { + finish(() => reject(error)); + } finally { + reading = false; + } + }; + + if (signal !== undefined) { + onAbort = (): void => finish(() => reject(abortError())); + signal.addEventListener("abort", onAbort, { once: true }); + } + + unsubscribe = this.on("factory.run_updated", (event) => { + if (event.data.runId === runId) { + void read(); + } + }); + + pollHandle = setInterval(() => void read(), 5_000); + // The re-read is a safety net, not work the process owes anyone: an + // outstanding wait must never keep Node alive on its own. + pollHandle.unref?.(); + void read(); + }); + } + /** * Creates a new CopilotSession instance. * @@ -169,10 +617,11 @@ export class CopilotSession { private connection: MessageConnection, private _workspacePath?: string, traceContextProvider?: TraceContextProvider, - options?: { mcpAuthHandler?: McpAuthHandler } + options?: { mcpAuthHandler?: McpAuthHandler; managedSettingsEnabled?: boolean } ) { this.traceContextProvider = traceContextProvider; this.mcpAuthHandler = options?.mcpAuthHandler; + this.managedSettingsEnabled = options?.managedSettingsEnabled === true; } /** @@ -296,11 +745,10 @@ export class CopilotSession { typeof optionsOrPrompt === "string" ? { prompt: optionsOrPrompt } : optionsOrPrompt; const effectiveTimeout = timeout ?? 60_000; - let resolveIdle: () => void; - let rejectWithError: (error: Error) => void; - const idlePromise = new Promise((resolve, reject) => { - resolveIdle = resolve; - rejectWithError = reject; + type SessionOutcome = { kind: "idle" } | { kind: "error"; error: Error }; + let resolveOutcome: (outcome: SessionOutcome) => void; + const outcomePromise = new Promise((resolve) => { + resolveOutcome = resolve; }); let lastAssistantMessage: AssistantMessageEvent | undefined; @@ -311,11 +759,11 @@ export class CopilotSession { if (event.type === "assistant.message") { lastAssistantMessage = event; } else if (event.type === "session.idle") { - resolveIdle(); + resolveOutcome({ kind: "idle" }); } else if (event.type === "session.error") { const error = new Error(event.data.message); error.stack = event.data.stack; - rejectWithError(error); + resolveOutcome({ kind: "error", error }); } }); @@ -334,7 +782,10 @@ export class CopilotSession { effectiveTimeout ); }); - await Promise.race([idlePromise, timeoutPromise]); + const outcome = await Promise.race([outcomePromise, timeoutPromise]); + if (outcome.kind === "error") { + throw outcome.error; + } return lastAssistantMessage; } finally { @@ -358,6 +809,13 @@ export class CopilotSession { this.autoModeSwitchHandler = undefined; this.commandHandlers.clear(); this.canvases.clear(); + this.factories.clear(); + for (const controllersForRun of this.factoryAbortControllers.values()) { + for (const controller of controllersForRun.values()) { + controller.abort(); + } + } + this.factoryAbortControllers.clear(); this.transformCallbacks?.clear(); } @@ -677,20 +1135,35 @@ export class CopilotSession { permissionRequest: PermissionRequest ): Promise { try { - const result = await this.permissionHandler!(permissionRequest, { + const handlerResult = await this.permissionHandler!(permissionRequest, { sessionId: this.sessionId, + managedSettingsEnabled: this.managedSettingsEnabled, }); + const isAttributed = isAttributedPermissionResult(handlerResult); + const result: PermissionRequestResult = isAttributed + ? handlerResult.result + : handlerResult; + const decisionContext = isAttributed ? handlerResult.decisionContext : undefined; if (result.kind === "no-result") { return; } if (this.disconnected) { return; } - await this.rpc.permissions.handlePendingPermissionRequest({ requestId, result }); - } catch (_error) { + await this.rpc.permissions.handlePendingPermissionRequest( + decisionContext === undefined + ? { requestId, result } + : { requestId, result, decisionContext } + ); + } catch (error) { if (this.disconnected) { return; } + console.error("Permission handler or response delivery failed", { + sessionId: this.sessionId, + requestId, + error, + }); try { await this.rpc.permissions.handlePendingPermissionRequest({ requestId, @@ -872,6 +1345,185 @@ export class CopilotSession { }; } + /** + * Registers factory closures and reverse-RPC handlers for this session. + * + * @param factories - Factory handles declared by the joining extension. + * @internal Called by the SDK when an extension joins a session. + */ + registerFactories(factories?: FactoryHandle[]): void { + this.factories.clear(); + if (!factories || factories.length === 0) { + delete this.clientSessionApis.factory; + return; + } + + for (const handle of factories) { + const definition = getFactoryDefinition(handle); + if (this.factories.has(definition.meta.name)) { + throw new Error( + `Duplicate factory name "${definition.meta.name}". Factory names must be unique within a joinSession call.` + ); + } + this.factories.set(definition.meta.name, definition); + } + + const self = this; + this.clientSessionApis.factory = { + async execute(params) { + const definition = self.factories.get(params.name); + if (!definition) { + const message = `No factory registered with name "${params.name}"`; + throw new ResponseError(ErrorCodes.InvalidParams, message, { + code: "factory_not_found", + name: params.name, + }); + } + + const controller = new AbortController(); + // Keyed by execution token as well as run ID so overlapping + // attempts for one run stay individually addressable. + let controllersForRun = self.factoryAbortControllers.get(params.runId); + if (controllersForRun === undefined) { + controllersForRun = new Map(); + self.factoryAbortControllers.set(params.runId, controllersForRun); + } + controllersForRun.set(params.executionToken, controller); + const progress = new FactoryProgressBuffer(async (lines) => { + await self.rpc.factory.log({ + runId: params.runId, + executionToken: params.executionToken, + lines, + }); + }); + try { + const context: FactoryContext = { + runId: params.runId, + args: params.args, + session: self, + signal: controller.signal, + phase: (title: string) => { + throwIfFactoryAborted(controller.signal); + progress.enqueue("phase", title); + }, + log: (message: string) => { + throwIfFactoryAborted(controller.signal); + progress.enqueue("log", message); + }, + agent: async (prompt, options = {}) => { + await progress.flush(); + const opts: FactoryAgentOptions = {}; + for (const key of FACTORY_AGENT_OPTION_KEYS) { + copyDefinedFactoryAgentOption(options, opts, key); + } + const response = await awaitFactoryOperation( + () => + self.rpc.factory.agent({ + factoryRunId: params.runId, + executionToken: params.executionToken, + prompt, + opts, + }), + controller.signal + ); + return response.result ?? null; + }, + step: async ( + key: string, + producer: () => Promise | JsonValue, + options: FactoryStepOptions = {} + ): Promise => { + await progress.flush(); + if (options.volatile) { + // The flush above is an await point, so an abort can land + // between entering step() and running the producer. The + // journaled branch is covered by awaitFactoryOperation; + // this one has to check for itself, or a cancelled run + // would still start new extension work. + throwIfFactoryAborted(controller.signal); + return producer(); + } + const cached = await awaitFactoryOperation( + () => + self.rpc.factory.journal.get({ + runId: params.runId, + executionToken: params.executionToken, + key, + }), + controller.signal + ); + if (cached.hit) { + if (cached.resultJson === undefined) { + throw new Error( + `step("${key}") journal returned a hit without a result` + ); + } + assertFactoryStepResult(cached.resultJson, key); + return cached.resultJson; + } + + // Producers are best-effort at-least-once across crashes or + // concurrent callers, so authors must make side effects idempotent. + const result = await producer(); + assertFactoryStepResult(result, key); + await awaitFactoryOperation( + () => + self.rpc.factory.journal.put({ + runId: params.runId, + executionToken: params.executionToken, + key, + resultJson: result, + }), + controller.signal + ); + return result; + }, + parallel: runFactoryParallel, + pipeline: runFactoryPipeline, + factory: async () => { + throw new Error("nested factories are not supported"); + }, + }; + const execution = { active: true }; + const result = await factoryExecutionStore.run(execution, async () => { + try { + return await definition.run(context); + } finally { + execution.active = false; + } + }); + if (result === undefined) { + return {}; + } + assertFactoryResult(result); + return { result }; + } finally { + try { + await progress.close(); + } finally { + const controllersForRun = self.factoryAbortControllers.get(params.runId); + if (controllersForRun?.get(params.executionToken) === controller) { + controllersForRun.delete(params.executionToken); + if (controllersForRun.size === 0) { + self.factoryAbortControllers.delete(params.runId); + } + } + } + } + }, + async abort(params) { + const controllersForRun = self.factoryAbortControllers.get(params.runId); + if (controllersForRun !== undefined) { + const reason = new DOMException("Factory run was aborted", "AbortError"); + for (const controller of controllersForRun.values()) { + controller.abort(reason); + } + } + return {}; + }, + }; + } + /** * Registers per-provider {@link BearerTokenProvider} callbacks for BYOK providers * configured with managed-identity / on-demand bearer-token auth. @@ -1255,9 +1907,11 @@ export class CopilotSession { postToolUse: this.hooks.onPostToolUse as GenericHandler | undefined, postToolUseFailure: this.hooks.onPostToolUseFailure as GenericHandler | undefined, userPromptSubmitted: this.hooks.onUserPromptSubmitted as GenericHandler | undefined, + userPromptTransformed: this.hooks.onUserPromptTransformed as GenericHandler | undefined, sessionStart: this.hooks.onSessionStart as GenericHandler | undefined, sessionEnd: this.hooks.onSessionEnd as GenericHandler | undefined, errorOccurred: this.hooks.onErrorOccurred as GenericHandler | undefined, + agentStop: this.hooks.onAgentStop as GenericHandler | undefined, }; const handler = handlerMap[hookType]; @@ -1450,3 +2104,201 @@ function toCanvasRpcError(error: unknown): ResponseError { const message = error instanceof Error ? error.message : String(error); return new ResponseError(ErrorCodes.InternalError, message, { code, message }); } + +type FactoryResultValidationCategory = + | "unsupported_type" + | "non_finite_number" + | "negative_zero" + | "cyclic_value" + | "nested_undefined" + | "unsupported_object"; + +interface StrictJsonValidationContext { + code: "factory_result_not_json" | "factory_step_not_json"; + label: string; + allowTopLevelUndefined: boolean; +} + +function strictJsonValidationError( + context: StrictJsonValidationContext, + category: FactoryResultValidationCategory, + message: string, + path: string +): ResponseError<{ code: string; category: FactoryResultValidationCategory; path: string }> { + return new ResponseError(ErrorCodes.InternalError, message, { + code: context.code, + category, + path, + }); +} + +function assertStrictJson( + value: unknown, + context: StrictJsonValidationContext +): asserts value is JsonValue | undefined { + const ancestors = new Set(); + + const visit = (current: unknown, path: string, allowUndefined: boolean): void => { + if (current === undefined) { + if (allowUndefined) { + return; + } + throw strictJsonValidationError( + context, + "nested_undefined", + `${context.label} contains nested undefined at ${path}`, + path + ); + } + if (current === null || typeof current === "boolean" || typeof current === "string") { + return; + } + if (typeof current === "number") { + if (!Number.isFinite(current)) { + throw strictJsonValidationError( + context, + "non_finite_number", + `${context.label} contains a non-finite number at ${path}`, + path + ); + } + // JSON serializes -0 as "0", so a journaled -0 would come back as 0 + // after a resume and break the lossless replay guarantee. + if (Object.is(current, -0)) { + throw strictJsonValidationError( + context, + "negative_zero", + `${context.label} contains negative zero at ${path}; normalize it to 0`, + path + ); + } + return; + } + if ( + typeof current === "function" || + typeof current === "symbol" || + typeof current === "bigint" + ) { + throw strictJsonValidationError( + context, + "unsupported_type", + `${context.label} contains a function, symbol, or BigInt at ${path}`, + path + ); + } + if (typeof current !== "object") { + throw strictJsonValidationError( + context, + "unsupported_type", + `${context.label} contains a function, symbol, or BigInt at ${path}`, + path + ); + } + if (ancestors.has(current)) { + throw strictJsonValidationError( + context, + "cyclic_value", + `${context.label} contains a cyclic reference at ${path}`, + path + ); + } + + ancestors.add(current); + try { + if (Array.isArray(current)) { + const keys = Reflect.ownKeys(current); + if ( + keys.length !== current.length + 1 || + keys.some( + (key) => + key !== "length" && + (typeof key !== "string" || + !/^(0|[1-9]\d*)$/.test(key) || + Number(key) >= current.length) + ) + ) { + throw strictJsonValidationError( + context, + "unsupported_object", + `${context.label} contains a non-JSON array property at ${path}`, + path + ); + } + for (let index = 0; index < current.length; index++) { + const descriptor = Object.getOwnPropertyDescriptor(current, String(index)); + if ( + descriptor === undefined || + !descriptor.enumerable || + !("value" in descriptor) + ) { + throw strictJsonValidationError( + context, + "unsupported_object", + `${context.label} contains a non-JSON array property at ${path}[${index}]`, + `${path}[${index}]` + ); + } + visit(descriptor.value, `${path}[${index}]`, false); + } + return; + } + + const prototype = Object.getPrototypeOf(current); + if (prototype !== Object.prototype && prototype !== null) { + throw strictJsonValidationError( + context, + "unsupported_object", + `${context.label} contains a non-JSON object at ${path}`, + path + ); + } + for (const key of Reflect.ownKeys(current)) { + if (typeof key === "symbol") { + throw strictJsonValidationError( + context, + "unsupported_type", + `${context.label} contains a function, symbol, or BigInt at ${path}`, + path + ); + } + const propertyPath = /^[A-Za-z_$][\w$]*$/.test(key) + ? `${path}.${key}` + : `${path}[${JSON.stringify(key)}]`; + const descriptor = Object.getOwnPropertyDescriptor(current, key); + if ( + descriptor === undefined || + !descriptor.enumerable || + !("value" in descriptor) + ) { + throw strictJsonValidationError( + context, + "unsupported_object", + `${context.label} contains a non-JSON property at ${propertyPath}`, + propertyPath + ); + } + visit(descriptor.value, propertyPath, false); + } + } finally { + ancestors.delete(current); + } + }; + + visit(value, "$", context.allowTopLevelUndefined); +} + +function assertFactoryResult(value: unknown): asserts value is JsonValue | undefined { + assertStrictJson(value, { + code: "factory_result_not_json", + label: "Factory result", + allowTopLevelUndefined: true, + }); +} + +function assertFactoryStepResult(value: unknown, key: string): asserts value is JsonValue { + assertStrictJson(value, { + code: "factory_step_not_json", + label: `Factory step "${key}" result`, + allowTopLevelUndefined: false, + }); +} diff --git a/nodejs/src/sessionFsProvider.ts b/nodejs/src/sessionFsProvider.ts index 0bc198750..ecb18a570 100644 --- a/nodejs/src/sessionFsProvider.ts +++ b/nodejs/src/sessionFsProvider.ts @@ -8,10 +8,12 @@ import type { SessionFsStatResult, SessionFsReaddirWithTypesEntry, SessionFsSqliteQueryResult as GeneratedSqliteQueryResult, + SessionFsSqliteTransactionError as GeneratedSqliteTransactionError, + SessionFsSqliteTransactionErrorClass, SessionFsSqliteQueryType, } from "./generated/rpc.js"; -export type { SessionFsSqliteQueryType }; +export type { SessionFsSqliteQueryType, SessionFsSqliteTransactionErrorClass }; /** * File metadata returned by {@link SessionFsProvider.stat}. @@ -27,6 +29,40 @@ export type SessionFsFileInfo = Omit; */ export type SessionFsSqliteQueryResult = Omit; +/** + * One statement in an atomic SQLite transaction passed to + * {@link SessionFsSqliteProvider.transaction}. + */ +export interface SessionFsSqliteStatement { + /** How to execute: `"exec"` for DDL/multi-statement, `"query"` for SELECT, `"run"` for INSERT/UPDATE/DELETE. */ + queryType: SessionFsSqliteQueryType; + + /** SQL statement to execute. */ + query: string; + + /** Optional named bind parameters. */ + params?: Record; +} + +/** + * Error thrown by {@link SessionFsSqliteProvider.transaction} to classify a + * transaction failure for the runtime. + * + * Any other thrown value is reported as `"fatal"`. Throw this with + * `"busyOrLocked"` when SQLite reported BUSY/LOCKED before commit and the + * transaction was rolled back, so the runtime knows the call is safe to retry. + */ +export class SessionFsSqliteTransactionFailure extends Error { + /** Failure classification reported to the runtime. */ + readonly errorClass: SessionFsSqliteTransactionErrorClass; + + constructor(message: string, errorClass: SessionFsSqliteTransactionErrorClass = "fatal") { + super(message); + this.name = "SessionFsSqliteTransactionFailure"; + this.errorClass = errorClass; + } +} + /** * SQLite operations for the per-session database. * Implementers provide query execution and existence checking. @@ -45,6 +81,18 @@ export interface SessionFsSqliteProvider { params?: Record ): Promise; + /** + * Execute `statements` atomically against the per-session database. + * + * Apply busy handling to every statement and roll back the whole batch if + * any statement fails. Throw {@link SessionFsSqliteTransactionFailure} to + * classify the failure; any other thrown value is reported as `"fatal"`. + * + * @param statements - Statements to execute in order inside a single transaction. + * @returns One result per statement, in the same order. + */ + transaction?(statements: SessionFsSqliteStatement[]): Promise; + /** * Check whether the per-session database already exists, without creating it. */ @@ -219,6 +267,32 @@ export function createSessionFsAdapter(provider: SessionFsProvider): SessionFsHa ); return result ?? { rows: [], columns: [], rowsAffected: 0 }; }, + sqliteTransaction: async ({ statements }) => { + if (!provider.sqlite?.transaction) { + return { + results: [], + error: { + errorClass: "fatal", + message: "SQLite transactions are not supported by this provider", + }, + }; + } + try { + const results = await provider.sqlite.transaction( + statements.map((statement) => ({ + queryType: statement.queryType, + query: statement.query, + params: normalizeSqliteParams(statement.params), + })) + ); + return { results: results.map((result) => ({ ...result })) }; + } catch (err) { + // Unlike sqliteQuery, transaction failures carry a classification the + // runtime uses to decide whether a retry is safe, so they are reported + // as a result-level error instead of a JSON-RPC error. + return { results: [], error: toSqliteTransactionError(err) }; + } + }, sqliteExists: async () => { if (!provider.sqlite) { throw new Error("SQLite is not supported by this provider"); @@ -233,3 +307,13 @@ function toSessionFsError(err: unknown): SessionFsError { const code = e.code === "ENOENT" ? "ENOENT" : "UNKNOWN"; return { code, message: e.message ?? String(err) }; } + +function toSqliteTransactionError(err: unknown): GeneratedSqliteTransactionError { + if (err instanceof SessionFsSqliteTransactionFailure) { + return { errorClass: err.errorClass, message: err.message }; + } + return { + errorClass: "fatal", + message: err instanceof Error ? err.message : String(err), + }; +} diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index e48c9064c..06d6bf7eb 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -11,11 +11,15 @@ import type { Canvas } from "./canvas.js"; import type { SessionFsProvider } from "./sessionFsProvider.js"; import type { CopilotRequestHandler } from "./copilotRequestHandler.js"; import type { + PermissionRequest as GeneratedPermissionRequest, + PermissionRequestedData as GeneratedPermissionRequestedData, + PermissionRequestedEvent as GeneratedPermissionRequestedEvent, ReasoningSummary, SessionLimitsConfig, SessionEvent as GeneratedSessionEvent, } from "./generated/session-events.js"; import type { CopilotSession } from "./session.js"; +import type { FactoryJsonSchema, JsonValue } from "./factory.js"; import type { GitHubTelemetryNotification, ModelBillingTokenPrices, @@ -35,7 +39,9 @@ export type { ModelBillingTokenPrices, ModelBillingTokenPricesLongContext, } from "./generated/rpc.js"; -export type SessionEvent = GeneratedSessionEvent; +export type SessionEvent = + | Exclude + | PermissionRequestedEvent; export type { ReasoningSummary } from "./generated/session-events.js"; export type { SessionFsProvider } from "./sessionFsProvider.js"; export { createSessionFsAdapter } from "./sessionFsProvider.js"; @@ -43,7 +49,16 @@ export type { SessionFsFileInfo } from "./sessionFsProvider.js"; export type { SessionFsSqliteQueryResult } from "./sessionFsProvider.js"; export type { SessionFsSqliteQueryType } from "./sessionFsProvider.js"; export type { SessionFsSqliteProvider } from "./sessionFsProvider.js"; +export type { SessionFsSqliteStatement } from "./sessionFsProvider.js"; +export type { SessionFsSqliteTransactionErrorClass } from "./sessionFsProvider.js"; +export { SessionFsSqliteTransactionFailure } from "./sessionFsProvider.js"; export type { LlmInferenceHeaders } from "./generated/rpc.js"; +export type { + PermissionDecisionContext, + PermissionDecisionOutcome, + PermissionDecisionSource, + PermissionDecisionSurface, +} from "./generated/rpc.js"; export type { CopilotRequestContext } from "./copilotRequestHandler.js"; export { CopilotRequestHandler, @@ -295,6 +310,13 @@ export interface CopilotClientOptions { */ baseDirectory?: string; + /** + * Absolute paths to trusted plugin directories bundled by the host. + * When non-empty, the complete set is registered with the runtime during + * startup before any sessions can be created. + */ + builtinPluginDirectories?: readonly string[]; + /** * Log level for the Copilot runtime. When omitted, the runtime uses its * own default (currently `"info"`). @@ -443,7 +465,7 @@ export type ToolBinaryResult = { description?: string; }; -export type ToolTelemetry = Record | undefined>; +export type ToolTelemetry = Record | undefined>; export type ToolResultObject = { textResultForLlm: string; @@ -648,6 +670,17 @@ export interface Tool { * Unknown keys are preserved and round-tripped untouched. */ metadata?: Record; + /** + * When true, a successful call to this tool ends the agent turn: the runtime's + * tool phase halts instead of feeding the tool result back to the model for + * another round. A failed call (for example input validation) leaves the loop + * running so the model can read the error and retry. + * + * Use this for tools whose whole purpose is to terminate the turn, such as a + * context clear that replaces the conversation the model would otherwise + * continue from. + */ + isTerminal?: boolean; } /** @@ -664,6 +697,7 @@ export function defineTool( skipPermission?: boolean; defer?: "auto" | "never"; metadata?: Record; + isTerminal?: boolean; } ): Tool { return { name, ...config }; @@ -1092,16 +1126,33 @@ export type SystemMessageConfig = | SystemMessageReplaceConfig | SystemMessageCustomizeConfig; +import type { PermissionDecisionRequest, PermissionDecisionContext } from "./generated/rpc.js"; + /** * Permission request types from the server. This is the generated * discriminated union from the runtime schema — switch on `kind` to * access the variant-specific fields (e.g. shell `commands`, write * `fileName`/`diff`, mcp `toolName`/`args`). + * + * `managedApprovalRequired` indicates that managed policy requires an explicit + * user decision. Hosts should bypass automatic approval and present their + * normal confirmation UI. The runtime currently emits it for managed Shell, + * Read, Edit, and Domain selector asks. */ -export type { PermissionRequest } from "./generated/session-events.js"; -import type { PermissionRequest } from "./generated/session-events.js"; +export type PermissionRequest = GeneratedPermissionRequest & { + readonly managedApprovalRequired?: boolean; +}; + +export type PermissionRequestedData = Omit< + GeneratedPermissionRequestedData, + "permissionRequest" +> & { + permissionRequest: PermissionRequest; +}; -import type { PermissionDecisionRequest } from "./generated/rpc.js"; +export type PermissionRequestedEvent = Omit & { + data: PermissionRequestedData; +}; /** * Permission decision result returned from a {@link PermissionHandler}. @@ -1111,12 +1162,67 @@ import type { PermissionDecisionRequest } from "./generated/rpc.js"; */ export type PermissionRequestResult = PermissionDecisionRequest["result"] | { kind: "no-result" }; +/** + * A {@link PermissionRequestResult} annotated with the + * {@link PermissionDecisionContext} describing how and where the decision was + * reached. The context is informational only — it never changes permission + * behavior. Supplying it lets the runtime attribute auto-approval telemetry to + * the responding surface. + */ +export interface AttributedPermissionResult { + kind: "attributed"; + result: PermissionRequestResult; + decisionContext: PermissionDecisionContext; +} + +/** + * Narrows a {@link PermissionHandler} return value to an attributed result. + */ +export function isAttributedPermissionResult( + result: PermissionRequestResult | AttributedPermissionResult +): result is AttributedPermissionResult { + return result.kind === "attributed"; +} + +/** + * Pair a permission decision with the context describing how and where it was + * made, so the runtime can attribute auto-approval telemetry. + * + * Passing an already-attributed result replaces the previous context rather + * than nesting it. The context is informational only and never changes + * permission behavior. + */ +export function createAttributedPermissionResult( + result: PermissionRequestResult | AttributedPermissionResult, + decisionContext: PermissionDecisionContext +): AttributedPermissionResult { + const inner = isAttributedPermissionResult(result) ? result.result : result; + return { kind: "attributed", result: inner, decisionContext }; +} + export type PermissionHandler = ( request: PermissionRequest, - invocation: { sessionId: string } -) => Promise | PermissionRequestResult; + invocation: { sessionId: string; managedSettingsEnabled?: boolean } +) => + | Promise + | PermissionRequestResult + | AttributedPermissionResult; -export const approveAll: PermissionHandler = () => ({ kind: "approve-once" }); +/** + * Approves permission requests when managed settings are disabled. + */ +export const approveAll: PermissionHandler = (request, invocation) => { + if (invocation.managedSettingsEnabled) { + throw new Error("approveAll cannot be used when managed settings are enabled"); + } + if ("managedApprovalRequired" in request) { + const managedApprovalRequired = request.managedApprovalRequired; + if (managedApprovalRequired !== undefined && managedApprovalRequired !== false) { + return { kind: "no-result" }; + } + } + return { kind: "approve-once" }; +}; export const defaultJoinSessionPermissionHandler: PermissionHandler = (): PermissionRequestResult => ({ @@ -1398,6 +1504,33 @@ export type UserPromptSubmittedHandler = ( invocation: { sessionId: string } ) => Promise | UserPromptSubmittedHookOutput | void; +/** + * Input for the user-prompt-transformed hook. + * + * This hook runs after the runtime has transformed the submitted prompt with + * generated context, but before it is persisted to session history or sent to + * the model. + */ +export interface UserPromptTransformedHookInput extends BaseHookInput { + prompt: string; + transformedPrompt: string; +} + +/** + * Output for the user-prompt-transformed hook. + */ +export interface UserPromptTransformedHookOutput { + modifiedTransformedPrompt?: string; +} + +/** + * Handler for the user-prompt-transformed hook. + */ +export type UserPromptTransformedHandler = ( + input: UserPromptTransformedHookInput, + invocation: { sessionId: string } +) => Promise | UserPromptTransformedHookOutput | void; + /** * Input for session-start hook */ @@ -1475,6 +1608,49 @@ export type ErrorOccurredHandler = ( invocation: { sessionId: string } ) => Promise | ErrorOccurredHookOutput | void; +/** + * Input for the agent-stop hook. + * + * Fires for the top-level (main) agent when it reaches a natural terminal stop + * — i.e. the agent has gone idle without a pending non-terminal tool call and + * was not aborted or blocked by a rejected tool. (For sub-agents, the runtime + * fires a separate sub-agent stop lifecycle.) + */ +export interface AgentStopHookInput extends BaseHookInput { + /** Why the agent stopped (for example, `"end_turn"`). */ + stopReason?: string; + /** Path to the on-disk session transcript, when available. */ + transcriptPath?: string; + /** + * True when this stop is a re-entry triggered by a previous agent-stop + * `block` decision (Claude-compatible `stop_hook_active` semantics). Lets a + * handler avoid blocking indefinitely. + */ + stopHookActive?: boolean; +} + +/** + * Output for the agent-stop hook. + * + * Return `{ decision: "block", reason }` to keep the agent running: the + * `reason` is enqueued as a follow-up user message so the agent continues + * working (for example, to remediate findings surfaced by the hook). The + * runtime caps consecutive blocks to prevent runaway loops. Returning nothing + * (or omitting `decision`) lets the agent stop normally. + */ +export interface AgentStopHookOutput { + decision?: "block"; + reason?: string; +} + +/** + * Handler for the agent-stop hook. + */ +export type AgentStopHandler = ( + input: AgentStopHookInput, + invocation: { sessionId: string } +) => Promise | AgentStopHookOutput | void; + /** * Configuration for session hooks */ @@ -1511,6 +1687,11 @@ export interface SessionHooks { */ onUserPromptSubmitted?: UserPromptSubmittedHandler; + /** + * Called after the runtime transforms a submitted prompt and before it is stored. + */ + onUserPromptTransformed?: UserPromptTransformedHandler; + /** * Called when a session starts */ @@ -1525,6 +1706,16 @@ export interface SessionHooks { * Called when an error occurs */ onErrorOccurred?: ErrorOccurredHandler; + + /** + * Called when the top-level agent reaches a natural terminal stop (it went + * idle without pending work and was not aborted). Return + * `{ decision: "block", reason }` to keep the agent running with `reason` + * enqueued as a follow-up message — for example, to have the agent + * remediate findings the handler surfaced. Returning nothing lets the + * agent stop. + */ + onAgentStop?: AgentStopHandler; } // ============================================================================ @@ -1643,8 +1834,8 @@ export interface CustomAgentConfig { model?: string; /** * Reasoning effort level for this agent's model. - * When omitted, no per-agent override is sent and the backend chooses its - * default. The parent session effort is not inherited. + * When omitted, the runtime resolves the effort from model configuration, + * then inherits the parent effort only if this agent uses the same model. */ reasoningEffort?: ReasoningEffort; } @@ -1731,7 +1922,7 @@ export interface LargeToolOutputConfig { /** * Valid reasoning effort levels for models that support it. */ -export type ReasoningEffort = "low" | "medium" | "high" | "xhigh"; +export type ReasoningEffort = "low" | "medium" | "high" | "xhigh" | "max"; /** * Context window tier for the session. "long_context" pins the session to the @@ -1836,6 +2027,68 @@ export interface CanvasProviderIdentity { name?: string; } +/** + * Static resource ceilings declared by a factory before it runs. + * + * @experimental Part of the experimental Agent Factories surface and may + * change or be removed in future SDK or CLI releases. + */ +export interface FactoryLimits { + /** Maximum number of factory subagents that may run concurrently. Must be positive when present. */ + maxConcurrentSubagents?: number; + /** Maximum total number of factory subagents that may be spawned. Must be positive when present. */ + maxTotalSubagents?: number; + /** Maximum AI credits consumed by factory subagents and descendants. This post-paid ceiling is soft. */ + maxAiCredits?: number; + /** + * Maximum accumulated active-execution time, in seconds. Active execution includes the entire extension body, + * subprocess waits, queued-agent waits, and sleeps. The limit is armed from the remaining headroom when a run + * resumes; time between attempts is not counted. Must be finite and positive when present. + */ + timeoutSeconds?: number; +} + +/** + * Registration metadata for an extension-authored factory. + * + * @experimental Part of the experimental Agent Factories surface and may + * change or be removed in future SDK or CLI releases. + */ +export interface FactoryMeta { + /** Stable factory name used for invocation. */ + name: string; + /** Human-readable factory description. */ + description: string; + /** Display metadata for the progress phases the factory may report. */ + phases: Array<{ title: string; detail?: string }>; + /** + * Optional declared shape of the arguments this factory expects as `ctx.args`. + * + * Declaring one is strongly recommended for any factory that reads `ctx.args`. + * When the model invokes the factory through the `run_factory` tool, the CLI + * validates `args` against this declaration **before** the run starts, so a + * malformed call is rejected with a correction hint and retried without ever + * creating a run row, prompting the user for permission, or spending credits. A + * factory that declares nothing is never validated: a malformed call starts, + * takes an approval, spends credits, and then fails inside the factory body. + * `factories_manage` with `operation: "inspect"` reports the declared shape so an + * agent can read it before invoking. + * + * This covers the model's `run_factory` path only. `session.factory.run(...)` is + * not validated against the declaration, so a factory should still check + * `ctx.args` rather than assume the declared shape held. + * + * Enforcement covers structure — types, required properties, and enum/const + * values. Finer constraints such as `minLength`, `pattern`, and + * `additionalProperties` are recorded in the declaration but not enforced. See + * {@link FactoryJsonSchema} for the accepted subset. A declaration outside that + * subset is rejected at registration. + */ + argsSchema?: FactoryJsonSchema; + /** Optional resource ceilings presented to the user before execution. */ + limits?: FactoryLimits; +} + /** * Provider-scoped options for the Copilot API (CAPI). * @@ -1900,6 +2153,59 @@ export interface CopilotExpAssignmentResponse { AssignmentContext: string; } +/** + * Configuration for the built-in GitHub MCP server. + * + * `disableFormDeferral` only applies to the built-in GitHub MCP server and + * only has an effect when MCP Apps and form-backed GitHub tools are enabled. + */ +export interface GitHubMcpToolConfig { + enableAllTools?: boolean; + additionalToolsets?: string[]; + additionalTools?: string[]; + enableInsidersMode?: boolean; + disableFormDeferral?: boolean; +} + +/** + * Permissions-only managed policy injected by the host via + * {@link SessionConfigBase.managedSettings}. + * + * Rule strings use the same vocabulary the runtime accepts for fetched managed + * policy (e.g. `"Read(**)"`, `"Shell(git push *)"`); malformed rules are + * rejected at session creation. + */ +export interface ManagedSettingsPermissions { + /** + * When set to `"disable"`, bypass-permissions ("yolo") mode is turned off + * for the session. This is deny-wins: it cannot be re-enabled by any other + * layer. + */ + disableBypassPermissionsMode?: "disable"; + /** Operations that must always be denied. Unioned across managed layers. */ + deny?: string[]; + /** + * Operations that must prompt for approval. Unioned across managed layers. + */ + ask?: string[]; + /** + * Operations permitted without prompting. Every declared `allow` list + * (across managed layers) must admit an operation for it to be allowed. + */ + allow?: string[]; +} + +/** + * Host-injected enterprise managed settings. The first supported contract is + * permissions-only; unknown sibling keys are rejected by the runtime. + * + * @see {@link SessionConfigBase.managedSettings} + */ +export interface ManagedSettings { + /** Managed permission policy for the session. */ + permissions?: ManagedSettingsPermissions; +} + /** * Shared configuration fields used by both {@link SessionConfig} (for * creating a new session) and {@link ResumeSessionConfig} (for resuming @@ -1930,6 +2236,12 @@ export interface SessionConfigBase { */ reasoningSummary?: ReasoningSummary; + /** + * Controls whether the session enables experimental features. + * Defaults to `false` in `"empty"` mode; otherwise the runtime decides when unset. + */ + enableExperimentalMode?: boolean; + /** * Context window tier for models that support it. Use "long_context" to pin * the session to the long-context tier; omit or use "default" otherwise. @@ -1954,13 +2266,8 @@ export interface SessionConfigBase { configDirectory?: string; /** - * When true, automatically discovers MCP server configurations (e.g. `.mcp.json`, - * `.vscode/mcp.json`) and skill directories from the working directory and merges - * them with any explicitly provided `mcpServers` and `skillDirectories`, with - * explicit values taking precedence on name collision. - * - * Note: custom instruction files (`.github/copilot-instructions.md`, `AGENTS.md`, etc.) - * are always loaded from the working directory regardless of this setting. + * Enables runtime discovery of supported configuration. Explicitly supplied + * configuration takes precedence over discovered values. * * @default false */ @@ -2135,6 +2442,14 @@ export interface SessionConfigBase { */ enableCitations?: boolean; + /** + * Opt in to capturing file changes for session rewind and cumulative session + * diff. On create, capture starts with the first turn. On resume, this can + * enable tracking only when the session still has a valid baseline; it cannot + * reconstruct changes from earlier untracked turns. + */ + enableFileChangeTracking?: boolean; + /** * Limits applied to this session's current accounting window. * @@ -2234,6 +2549,14 @@ export interface SessionConfigBase { */ enableMcpApps?: boolean; + /** + * Configuration for the built-in GitHub MCP server. + * + * `disableFormDeferral` only applies to the built-in GitHub MCP server and + * only has an effect when MCP Apps and form-backed GitHub tools are enabled. + */ + githubMcpToolConfig?: GitHubMcpToolConfig; + /** * Handler for exit-plan-mode requests from the agent. * When provided, enables `exitPlanMode.request` callbacks. @@ -2258,6 +2581,13 @@ export interface SessionConfigBase { */ workingDirectory?: string; + /** + * Additional directories the agent may access beyond the working directory. + * Relative paths are resolved against the session's working directory. + * Re-supply these directories when resuming a session. + */ + additionalDirectories?: string[]; + /** * Enable streaming of assistant message and reasoning chunks. * When true, ephemeral assistant.message_delta and assistant.reasoning_delta @@ -2343,6 +2673,13 @@ export interface SessionConfigBase { */ disabledSkills?: string[]; + /** + * Exact MCP server names to disable for this session. Disabled servers are not + * started or authenticated when creating or cold-resuming a session. Supplying + * this on a resident resume cannot stop servers that are already running. + */ + disabledMcpServers?: string[]; + /** * Infinite session configuration for persistent workspaces and automatic compaction. * When enabled (default), sessions automatically manage context limits and persist state. @@ -2375,6 +2712,31 @@ export interface SessionConfigBase { */ enableManagedSettings?: boolean; + /** + * Host-injected enterprise managed settings for this session. + * + * Unlike {@link SessionConfigBase.enableManagedSettings} — which asks the + * runtime to *self-fetch* account/org and device policy — this field lets + * the host supply the managed policy directly. The runtime validates it + * with the same managed-permission parser it uses for fetched policy and + * composes it restrictively with any self-fetched (server) and + * device-managed (MDM) layers: `deny`/`ask` rules are unioned, every + * declared `allow` list must admit an operation, and + * `disableBypassPermissionsMode: "disable"` is deny-wins. + * + * This is startup-only. It is **not** persisted: it must be re-supplied on + * {@link CopilotClient.resumeSession | resume}, where it replaces the prior + * injected layer (omitting it clears the layer, so warm and cold resume + * behave identically). It may be combined with `enableManagedSettings`; + * when both are supplied the injected, server, and device restrictions all + * apply. + * + * Requires a Copilot runtime whose RPC schema includes `managedSettings`. + * Older runtimes may ignore this additive field, so hosts must not rely on + * injected policy until they ship a compatible runtime. + */ + managedSettings?: ManagedSettings; + /** * When true, skips embedding-based retrieval for this session. * Use in multitenant deployments to prevent cross-session information leakage @@ -2619,7 +2981,7 @@ export interface ProviderConfig { */ azure?: { /** - * API version. Defaults to "2024-10-21". + * API version. When omitted, the runtime uses the GA versionless v1 route. */ apiVersion?: string; }; diff --git a/nodejs/test/client.test.ts b/nodejs/test/client.test.ts index 85d49fc32..841aa599d 100644 --- a/nodejs/test/client.test.ts +++ b/nodejs/test/client.test.ts @@ -1,9 +1,13 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { EventEmitter } from "node:events"; import { PassThrough } from "stream"; +import { mkdtempSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join, resolve } from "node:path"; import { describe, expect, it, onTestFinished, vi } from "vitest"; import { approveAll, + createAttributedPermissionResult, CopilotClient, createCanvas, RuntimeConnection, @@ -19,7 +23,86 @@ async function stopClient(client: CopilotClient): Promise { await client.stop(); } +describe("approveAll", () => { + const request = { + kind: "url" as const, + url: "https://api.example.com/data", + intention: "Fetch domain data", + }; + const invocation = { sessionId: "session-1", managedSettingsEnabled: false }; + + it("approves ordinary permission requests", () => { + expect(approveAll(request, invocation)).toEqual({ kind: "approve-once" }); + }); + + it("rejects managed settings sessions", () => { + expect(() => approveAll(request, { ...invocation, managedSettingsEnabled: true })).toThrow( + "approveAll cannot be used when managed settings are enabled" + ); + }); + + it("leaves managed requests pending when managed settings are disabled", () => { + expect(approveAll({ ...request, managedApprovalRequired: true }, invocation)).toEqual({ + kind: "no-result", + }); + }); + + it("fails closed when managed approval metadata is malformed", () => { + const malformedRequest = { + ...request, + managedApprovalRequired: "yes", + } as unknown as Parameters[0]; + + expect(approveAll(malformedRequest, invocation)).toEqual({ kind: "no-result" }); + }); +}); + describe("CopilotClient", () => { + async function startWithMockConnection( + builtinPluginDirectories?: readonly string[] + ): Promise> { + const client = new CopilotClient({ + connection: RuntimeConnection.forUri("localhost:1234"), + builtinPluginDirectories, + }); + const sendRequest = vi.fn(async () => ({})); + vi.spyOn(client as any, "connectToServer").mockImplementation(async () => { + (client as any).connection = { sendRequest }; + }); + vi.spyOn(client as any, "verifyProtocolVersion").mockResolvedValue(undefined); + + await client.start(); + return sendRequest; + } + + it.each([undefined, []])( + "does not configure built-in plugin directories when unset or empty", + async (builtinPluginDirectories) => { + const sendRequest = await startWithMockConnection(builtinPluginDirectories); + + expect(sendRequest).not.toHaveBeenCalledWith("plugins.builtin.set", expect.anything()); + } + ); + + it("configures built-in plugin directories before start completes", async () => { + const paths = [resolve("plugins/core"), resolve("plugins/github")]; + + const sendRequest = await startWithMockConnection(paths); + + expect(sendRequest).toHaveBeenCalledTimes(1); + expect(sendRequest).toHaveBeenCalledWith("plugins.builtin.set", { paths }); + }); + + it("rejects relative built-in plugin directories", () => { + expect( + () => + new CopilotClient({ + connection: RuntimeConnection.forUri("localhost:1234"), + builtinPluginDirectories: ["plugins/core"], + }) + ).toThrow(/builtinPluginDirectories.*absolute paths.*plugins\/core/); + }); + it("disposes the stdio connection when child stdin emits an error", async () => { const client = new CopilotClient(); onTestFinished(() => client.forceStop()); @@ -46,6 +129,91 @@ describe("CopilotClient", () => { expect(spy).not.toHaveBeenCalled(); }); + it("forwards decisionContext as a top-level sibling of result", async () => { + const session = new CopilotSession("session-1", {} as any); + const decisionContext = { + outcome: "auto_approved" as const, + source: "host_policy" as const, + surface: "sdk" as const, + }; + session.registerPermissionHandler(() => + createAttributedPermissionResult({ kind: "approve-once" }, decisionContext) + ); + const spy = vi + .spyOn(session.rpc.permissions, "handlePendingPermissionRequest") + .mockResolvedValue({ kind: "approve-once" } as any); + + await (session as any)._executePermissionAndRespond("request-1", { kind: "write" }); + + expect(spy).toHaveBeenCalledOnce(); + const params = spy.mock.calls[0][0] as any; + expect(params).toEqual({ + requestId: "request-1", + result: { kind: "approve-once" }, + decisionContext, + }); + // decisionContext is a sibling of result, never nested inside it. + expect(params.result.decisionContext).toBeUndefined(); + }); + + it("emits exactly requestId and result with no decisionContext key when unattributed", async () => { + const session = new CopilotSession("session-1", {} as any); + session.registerPermissionHandler(() => ({ kind: "approve-once" })); + const spy = vi + .spyOn(session.rpc.permissions, "handlePendingPermissionRequest") + .mockResolvedValue({ kind: "approve-once" } as any); + + await (session as any)._executePermissionAndRespond("request-1", { kind: "write" }); + + expect(spy).toHaveBeenCalledOnce(); + const params = spy.mock.calls[0][0] as any; + expect(params).toEqual({ requestId: "request-1", result: { kind: "approve-once" } }); + expect(Object.keys(params).sort()).toEqual(["requestId", "result"]); + expect("decisionContext" in params).toBe(false); + }); + + it("does not respond when a no-result decision is wrapped with a context", async () => { + const session = new CopilotSession("session-1", {} as any); + const decisionContext = { + outcome: "auto_approved" as const, + source: "host_policy" as const, + surface: "sdk" as const, + }; + session.registerPermissionHandler(() => + createAttributedPermissionResult({ kind: "no-result" }, decisionContext) + ); + const spy = vi.spyOn(session.rpc.permissions, "handlePendingPermissionRequest"); + + await (session as any)._executePermissionAndRespond("request-1", { kind: "write" }); + + expect(spy).not.toHaveBeenCalled(); + }); + + it("replaces the context when applied twice", () => { + const first = { + outcome: "auto_approved" as const, + source: "judge_recommendation" as const, + surface: "sdk" as const, + }; + const second = { + outcome: "prompted_user" as const, + source: "human_response" as const, + surface: "tui" as const, + }; + + const once = createAttributedPermissionResult({ kind: "approve-once" }, first); + const twice = createAttributedPermissionResult(once, second); + + expect(twice).toEqual({ + kind: "attributed", + result: { kind: "approve-once" }, + decisionContext: second, + }); + // The result stays unwrapped rather than nesting an AttributedPermissionResult. + expect((twice.result as any).result).toBeUndefined(); + expect((twice.result as any).decisionContext).toBeUndefined(); + }); + it("responds to MCP OAuth requests with host token data", async () => { const sendRequest = vi.fn(async () => ({ success: true })); let observedRequest: any; @@ -102,6 +270,55 @@ describe("CopilotClient", () => { }); }); + it("forwards GitHub MCP tool config on create and resume", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => stopClient(client)); + + const spy = vi + .spyOn((client as any).connection!, "sendRequest") + .mockImplementation(async (method: string, params: any) => { + if (method === "session.create") return { sessionId: params.sessionId }; + if (method === "session.resume") return { sessionId: params.sessionId }; + throw new Error(`Unexpected method: ${method}`); + }); + const githubMcpToolConfig = { + enableAllTools: true, + additionalToolsets: ["repos"], + additionalTools: ["get_issue"], + enableInsidersMode: true, + disableFormDeferral: true, + }; + + const session = await client.createSession({ githubMcpToolConfig }); + await client.resumeSession(session.sessionId, { githubMcpToolConfig }); + + expect(spy.mock.calls.find(([method]) => method === "session.create")![1]).toMatchObject({ + githubMcpToolConfig, + }); + expect(spy.mock.calls.find(([method]) => method === "session.resume")![1]).toMatchObject({ + githubMcpToolConfig, + }); + }); + + it("omits GitHub MCP tool config when unset", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => stopClient(client)); + + const spy = vi + .spyOn((client as any).connection!, "sendRequest") + .mockImplementation(async (method: string, params: any) => { + if (method === "session.create") return { sessionId: params.sessionId }; + throw new Error(`Unexpected method: ${method}`); + }); + await client.createSession({}); + + expect( + spy.mock.calls.find(([method]) => method === "session.create")![1] + ).not.toHaveProperty("githubMcpToolConfig"); + }); + it("passes MCP OAuth requests through when optional metadata is absent", async () => { let observedRequest: any; const session = new CopilotSession( @@ -184,6 +401,42 @@ describe("CopilotClient", () => { ); }); + it("forwards additional directories when creating and resuming sessions", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => stopClient(client)); + + const spy = vi + .spyOn((client as any).connection!, "sendRequest") + .mockImplementation(async (method: string, params: any) => { + if (method === "session.create" || method === "session.resume") { + return { sessionId: params.sessionId, workspacePath: "/workspace" }; + } + throw new Error(`Unexpected method: ${method}`); + }); + + await client.createSession({ + sessionId: "create-with-additional-directories", + additionalDirectories: ["/repo/shared", "/repo/generated"], + onPermissionRequest: approveAll, + }); + await client.resumeSession("resume-with-additional-directories", { + additionalDirectories: ["/repo/resumed"], + onPermissionRequest: approveAll, + }); + + expect(spy).toHaveBeenCalledWith( + "session.create", + expect.objectContaining({ + additionalDirectories: ["/repo/shared", "/repo/generated"], + }) + ); + expect(spy).toHaveBeenCalledWith( + "session.resume", + expect.objectContaining({ additionalDirectories: ["/repo/resumed"] }) + ); + }); + it("registers MCP OAuth interest after cloud create only when an auth handler is configured", async () => { const client = new CopilotClient(); await client.start(); @@ -401,6 +654,100 @@ describe("CopilotClient", () => { expect(resumePayload.reasoningSummary).toBe("none"); }); + it("forwards enableExperimentalMode in session.create and session.resume", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => client.forceStop()); + + const spy = vi + .spyOn((client as any).connection!, "sendRequest") + .mockImplementation(async (method: string, params: any) => { + if (method === "session.create") return { sessionId: params.sessionId }; + if (method === "session.resume") return { sessionId: params.sessionId }; + throw new Error(`Unexpected method: ${method}`); + }); + + const session = await client.createSession({ + onPermissionRequest: approveAll, + enableExperimentalMode: false, + }); + await client.resumeSession(session.sessionId, { + onPermissionRequest: approveAll, + enableExperimentalMode: true, + }); + + const createPayload = spy.mock.calls.find( + ([method]) => method === "session.create" + )![1] as any; + const resumePayload = spy.mock.calls.find( + ([method]) => method === "session.resume" + )![1] as any; + expect(createPayload.isExperimentalMode).toBe(false); + expect(resumePayload.isExperimentalMode).toBe(true); + }); + + it("defaults enableExperimentalMode by client mode", async () => { + const baseDirectory = mkdtempSync(join(tmpdir(), "copilot-sdk-node-empty-")); + const emptyClient = new CopilotClient({ mode: "empty", baseDirectory }); + await emptyClient.start(); + onTestFinished(() => emptyClient.forceStop()); + + const emptySpy = vi + .spyOn((emptyClient as any).connection!, "sendRequest") + .mockImplementation(async (method: string, params: any) => { + if (method === "session.create") return { sessionId: params.sessionId }; + if (method === "session.resume") return { sessionId: params.sessionId }; + if (method === "session.options.update") return {}; + throw new Error(`Unexpected method: ${method}`); + }); + + const emptySession = await emptyClient.createSession({ + onPermissionRequest: approveAll, + availableTools: [], + }); + await emptyClient.resumeSession(emptySession.sessionId, { + onPermissionRequest: approveAll, + availableTools: [], + }); + + const emptyCreatePayload = emptySpy.mock.calls.find( + ([method]) => method === "session.create" + )![1] as any; + const emptyResumePayload = emptySpy.mock.calls.find( + ([method]) => method === "session.resume" + )![1] as any; + expect(emptyCreatePayload.isExperimentalMode).toBe(false); + expect(emptyResumePayload.isExperimentalMode).toBe(false); + + const cliClient = new CopilotClient(); + await cliClient.start(); + onTestFinished(() => cliClient.forceStop()); + + const cliSpy = vi + .spyOn((cliClient as any).connection!, "sendRequest") + .mockImplementation(async (method: string, params: any) => { + if (method === "session.create") return { sessionId: params.sessionId }; + if (method === "session.resume") return { sessionId: params.sessionId }; + throw new Error(`Unexpected method: ${method}`); + }); + + const cliSession = await cliClient.createSession({ + onPermissionRequest: approveAll, + }); + await cliClient.resumeSession(cliSession.sessionId, { + onPermissionRequest: approveAll, + }); + + const cliCreatePayload = cliSpy.mock.calls.find( + ([method]) => method === "session.create" + )![1] as any; + const cliResumePayload = cliSpy.mock.calls.find( + ([method]) => method === "session.resume" + )![1] as any; + expect(cliCreatePayload.isExperimentalMode).toBeUndefined(); + expect(cliResumePayload.isExperimentalMode).toBeUndefined(); + }); + it("forwards contextTier in session.create and session.resume", async () => { const client = new CopilotClient(); await client.start(); @@ -498,6 +845,68 @@ describe("CopilotClient", () => { expect(createPayload.tools[0].metadata).toBeUndefined(); }); + it("forwards tool isTerminal in session.create and session.resume", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => client.forceStop()); + + const spy = vi + .spyOn((client as any).connection!, "sendRequest") + .mockImplementation(async (method: string, params: any) => { + if (method === "session.create") return { sessionId: params.sessionId }; + if (method === "session.resume") return { sessionId: params.sessionId }; + throw new Error(`Unexpected method: ${method}`); + }); + + const tool = { + name: "clear_context", + description: "Clears the conversation", + parameters: { type: "object", properties: {} }, + isTerminal: true, + }; + + const session = await client.createSession({ + onPermissionRequest: approveAll, + tools: [tool], + }); + await client.resumeSession(session.sessionId, { + onPermissionRequest: approveAll, + tools: [tool], + }); + + const createPayload = spy.mock.calls.find( + ([method]) => method === "session.create" + )![1] as any; + const resumePayload = spy.mock.calls.find( + ([method]) => method === "session.resume" + )![1] as any; + expect(createPayload.tools[0].isTerminal).toBe(true); + expect(resumePayload.tools[0].isTerminal).toBe(true); + }); + + it("omits tool isTerminal from session.create when unset", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => client.forceStop()); + + const spy = vi + .spyOn((client as any).connection!, "sendRequest") + .mockImplementation(async (method: string, params: any) => { + if (method === "session.create") return { sessionId: params.sessionId }; + throw new Error(`Unexpected method: ${method}`); + }); + + await client.createSession({ + onPermissionRequest: approveAll, + tools: [{ name: "my_tool", description: "a tool" }], + }); + + const createPayload = spy.mock.calls.find( + ([method]) => method === "session.create" + )![1] as any; + expect(createPayload.tools[0].isTerminal).toBeUndefined(); + }); + it("forwards new session options in session.create and session.resume", async () => { const client = new CopilotClient(); await client.start(); @@ -514,12 +923,14 @@ describe("CopilotClient", () => { const session = await client.createSession({ onPermissionRequest: approveAll, enableCitations: true, + enableFileChangeTracking: true, excludedBuiltinAgents: ["explore"], sessionLimits: { maxAiCredits: 30 }, }); await client.resumeSession(session.sessionId, { onPermissionRequest: approveAll, enableCitations: false, + enableFileChangeTracking: false, excludedBuiltinAgents: ["task"], sessionLimits: { maxAiCredits: 15 }, }); @@ -531,9 +942,11 @@ describe("CopilotClient", () => { ([method]) => method === "session.resume" )![1] as any; expect(createPayload.enableCitations).toBe(true); + expect(createPayload.enableFileChangeTracking).toBe(true); expect(createPayload.excludedBuiltinAgents).toEqual(["explore"]); expect(createPayload.sessionLimits).toEqual({ maxAiCredits: 30 }); expect(resumePayload.enableCitations).toBe(false); + expect(resumePayload.enableFileChangeTracking).toBe(false); expect(resumePayload.excludedBuiltinAgents).toEqual(["task"]); expect(resumePayload.sessionLimits).toEqual({ maxAiCredits: 15 }); }); @@ -814,6 +1227,7 @@ describe("CopilotClient", () => { }); const pluginDirs = ["/tmp/plugins/a", "/tmp/plugins/b"]; + const disabledMcpServers = ["local-files", "remote-github"]; const largeOutput = { enabled: true, maxSizeBytes: 1024, @@ -828,11 +1242,13 @@ describe("CopilotClient", () => { const session = await client.createSession({ onPermissionRequest: approveAll, pluginDirectories: pluginDirs, + disabledMcpServers, largeOutput, }); await client.resumeSession(session.sessionId, { onPermissionRequest: approveAll, pluginDirectories: pluginDirs, + disabledMcpServers, largeOutput, }); @@ -843,8 +1259,10 @@ describe("CopilotClient", () => { ([method]) => method === "session.resume" )![1] as any; expect(createPayload.pluginDirectories).toEqual(pluginDirs); + expect(createPayload.disabledMcpServers).toEqual(disabledMcpServers); expect(createPayload.largeOutput).toEqual(expectedWireLargeOutput); expect(resumePayload.pluginDirectories).toEqual(pluginDirs); + expect(resumePayload.disabledMcpServers).toEqual(disabledMcpServers); expect(resumePayload.largeOutput).toEqual(expectedWireLargeOutput); }); @@ -3139,16 +3557,47 @@ describe("CopilotClient", () => { expect(failureCalls).toEqual(["fail-tool"]); }); + it("registers hooks.invoke on the JSON-RPC connection and routes it to handleHooksInvoke", async () => { + const client = new CopilotClient(); + const handleHooksInvoke = vi + .spyOn(client as any, "handleHooksInvoke") + .mockResolvedValue({ output: { additionalContext: "ok" } }); + + const fakeConnection = { + onNotification: vi.fn(), + onRequest: vi.fn(), + onClose: vi.fn(), + onError: vi.fn(), + }; + + (client as any).connection = fakeConnection; + (client as any).attachConnectionHandlers(); + + const hooksRegistration = fakeConnection.onRequest.mock.calls.find( + ([method]: [string, unknown]) => method === "hooks.invoke" + ); + expect(hooksRegistration).toBeDefined(); + + const handler = hooksRegistration![1] as (params: { + sessionId: string; + hookType: string; + input: unknown; + }) => Promise<{ output?: unknown }>; + const payload = { + sessionId: "session-1", + hookType: "postToolUseFailure", + input: { toolName: "shell" }, + }; + + await expect(handler(payload)).resolves.toEqual({ + output: { additionalContext: "ok" }, + }); + expect(handleHooksInvoke).toHaveBeenCalledWith(payload); + }); + it("routes hooks.invoke JSON-RPC requests to the SessionHooks handler", async () => { - // Validates the full JSON-RPC entry point used by the CLI: - // clientGlobalHandlers.hooks.invoke({sessionId, hookType, input}) - // → CopilotSession._handleHooksInvoke(hookType, input) - // → SessionHooks.onPostToolUseFailure(normalizedInput, {sessionId}) - // - // This guards the wire-format contract that the bundled Copilot - // CLI relies on: the hookType string "postToolUseFailure" and the - // input shape `{toolName, toolArgs, error, timestamp, cwd}`. - // The SDK maps that to public `{..., timestamp: Date, workingDirectory}`. + // Validates the dispatch behavior for the internal `hooks.invoke` + // payload after the JSON-RPC connection hands it to the SDK. const client = new CopilotClient(); await client.start(); onTestFinished(() => stopClient(client)); @@ -3172,7 +3621,7 @@ describe("CopilotClient", () => { cwd: "/tmp", }; - const response = await (client as any).clientGlobalHandlers.hooks.invoke({ + const response = await (client as any).handleHooksInvoke({ sessionId: session.sessionId, hookType: "postToolUseFailure", input: failureInput, @@ -3193,6 +3642,83 @@ describe("CopilotClient", () => { output: { additionalContext: "context from failure hook" }, }); }); + + it("dispatches agentStop to onAgentStop and returns a block decision", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => stopClient(client)); + + const received: { input: any; invocation: any }[] = []; + const session = await client.createSession({ + onPermissionRequest: approveAll, + hooks: { + onAgentStop: async (input, invocation) => { + received.push({ input, invocation }); + return { decision: "block", reason: "2 vulnerabilities found; please fix" }; + }, + }, + }); + + const result = await (session as any)._handleHooksInvoke("agentStop", { + stopReason: "end_turn", + transcriptPath: "/tmp/transcript.jsonl", + stop_hook_active: true, + timestamp: 1700000000000, + cwd: "/repo", + }); + + expect(received).toHaveLength(1); + expect(received[0].input).toEqual({ + stopReason: "end_turn", + transcriptPath: "/tmp/transcript.jsonl", + stopHookActive: true, + timestamp: new Date(1700000000000), + workingDirectory: "/repo", + }); + expect(received[0].invocation.sessionId).toBe(session.sessionId); + expect(result).toEqual({ + decision: "block", + reason: "2 vulnerabilities found; please fix", + }); + }); + + it("routes agentStop hooks.invoke JSON-RPC requests to onAgentStop", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => stopClient(client)); + + const received: { input: any }[] = []; + const session = await client.createSession({ + onPermissionRequest: approveAll, + hooks: { + onAgentStop: async (input) => { + received.push({ input }); + // Returning nothing lets the agent stop normally. + }, + }, + }); + + const response = await (client as any).handleHooksInvoke({ + sessionId: session.sessionId, + hookType: "agentStop", + input: { + stopReason: "end_turn", + stop_hook_active: true, + timestamp: 1700000000000, + cwd: "/repo", + }, + }); + + expect(received).toHaveLength(1); + expect(received[0].input).toEqual({ + stopReason: "end_turn", + stopHookActive: true, + timestamp: new Date(1700000000000), + workingDirectory: "/repo", + }); + // No decision returned — the SDK forwards an empty output envelope. + expect(response).toEqual({ output: undefined }); + }); }); describe("shutdown", () => { @@ -3268,3 +3794,101 @@ describe("CopilotClient", () => { }); }); }); + +describe("managedSettings serialization", () => { + async function captureCreateParams(config: Record): Promise { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => stopClient(client)); + const spy = vi + .spyOn((client as any).connection!, "sendRequest") + .mockImplementation(async (method: string, params: any) => { + if (method === "session.create") return { sessionId: params.sessionId }; + throw new Error(`Unexpected method: ${method}`); + }); + await client.createSession({ onPermissionRequest: approveAll, ...config }); + const call = spy.mock.calls.find(([method]) => method === "session.create"); + return call![1]; + } + + it("forwards the full permissions object on session.create", async () => { + const params = await captureCreateParams({ + managedSettings: { + permissions: { + disableBypassPermissionsMode: "disable", + deny: ["Shell(git push)"], + ask: ["Domain(publish.example)"], + allow: ["Read(**)"], + }, + }, + }); + expect(params.managedSettings).toEqual({ + permissions: { + disableBypassPermissionsMode: "disable", + deny: ["Shell(git push)"], + ask: ["Domain(publish.example)"], + allow: ["Read(**)"], + }, + }); + }); + + it("marks directly injected sessions as managed", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => stopClient(client)); + vi.spyOn((client as any).connection!, "sendRequest").mockImplementation( + async (method: string, params: any) => { + if (method === "session.create") return { sessionId: params.sessionId }; + throw new Error(`Unexpected method: ${method}`); + } + ); + + const session = await client.createSession({ + onPermissionRequest: approveAll, + managedSettings: { permissions: { deny: ["Edit(/secrets/**)"] } }, + }); + + expect((session as any).managedSettingsEnabled).toBe(true); + }); + + it("omits managedSettings when not supplied", async () => { + const params = await captureCreateParams({}); + expect(params.managedSettings).toBeUndefined(); + }); + + it("coexists with enableManagedSettings", async () => { + const params = await captureCreateParams({ + enableManagedSettings: true, + managedSettings: { permissions: { deny: ["Edit(/secrets/**)"] } }, + }); + expect(params.enableManagedSettings).toBe(true); + expect(params.managedSettings).toEqual({ permissions: { deny: ["Edit(/secrets/**)"] } }); + }); + + it("preserves empty arrays in the permissions object", async () => { + const params = await captureCreateParams({ + managedSettings: { permissions: { deny: [], ask: [], allow: [] } }, + }); + expect(params.managedSettings).toEqual({ permissions: { deny: [], ask: [], allow: [] } }); + }); + + it("forwards managedSettings on session.resume", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => stopClient(client)); + const spy = vi + .spyOn((client as any).connection!, "sendRequest") + .mockImplementation(async (method: string, params: any) => { + if (method === "session.resume") return { sessionId: params.sessionId }; + throw new Error(`Unexpected method: ${method}`); + }); + await client.resumeSession("session-1", { + onPermissionRequest: approveAll, + managedSettings: { permissions: { ask: ["Domain(publish.example)"] } }, + }); + const call = spy.mock.calls.find(([method]) => method === "session.resume"); + expect(call![1].managedSettings).toEqual({ + permissions: { ask: ["Domain(publish.example)"] }, + }); + }); +}); diff --git a/nodejs/test/e2e/client.e2e.test.ts b/nodejs/test/e2e/client.e2e.test.ts index 89489f78e..35e744076 100644 --- a/nodejs/test/e2e/client.e2e.test.ts +++ b/nodejs/test/e2e/client.e2e.test.ts @@ -1,5 +1,5 @@ import { ChildProcess } from "child_process"; -import { describe, expect, it, onTestFinished } from "vitest"; +import { describe, expect, it, onTestFinished, vi } from "vitest"; import { approveAll, CopilotClient, RuntimeConnection } from "../../src/index.js"; import { isInProcessTransport } from "./harness/sdkTestContext.js"; @@ -95,7 +95,12 @@ describe("Client", () => { const cliProcess = (client as any).cliProcess as ChildProcess; expect(cliProcess).toBeDefined(); cliProcess.kill("SIGKILL"); - await new Promise((resolve) => setTimeout(resolve, 100)); + await vi.waitFor( + () => { + expect((client as unknown as { state: string }).state).toBe("disconnected"); + }, + { timeout: 10_000 } + ); const errors = await client.stop(); if (errors.length > 0) { diff --git a/nodejs/test/e2e/client_options.e2e.test.ts b/nodejs/test/e2e/client_options.e2e.test.ts index e207f275a..e3dc41343 100644 --- a/nodejs/test/e2e/client_options.e2e.test.ts +++ b/nodejs/test/e2e/client_options.e2e.test.ts @@ -93,7 +93,7 @@ function handleMessage(message) { return; } - if (message.method === "session.create") { + if (message.method === "session.create" || message.method === "session.resume") { const sessionId = message.params?.sessionId ?? message.params?.[0]?.sessionId ?? "fake-session"; writeResponse(message.id, { sessionId, workspacePath: null, capabilities: null }); return; @@ -329,6 +329,7 @@ describe("Client options", async () => { enableConfigDiscovery: true, enableOnDemandInstructionDiscovery: true, includeSubAgentStreamingEvents: false, + customAgentsLocalOnly: false, }); const updatedRaw = fs.readFileSync(capturePath, "utf8"); @@ -339,6 +340,7 @@ describe("Client options", async () => { enableConfigDiscovery?: boolean; enableOnDemandInstructionDiscovery?: boolean; includeSubAgentStreamingEvents?: boolean; + customAgentsLocalOnly?: boolean; }; }[]; }; @@ -347,8 +349,83 @@ describe("Client options", async () => { expect(createRequests[0].params.enableConfigDiscovery).toBe(true); expect(createRequests[0].params.enableOnDemandInstructionDiscovery).toBe(true); expect(createRequests[0].params.includeSubAgentStreamingEvents).toBe(false); + expect(createRequests[0].params.customAgentsLocalOnly).toBe(false); + const sessionId = session.sessionId; await session.disconnect(); + + const resumed = await client.resumeSession(sessionId, { + onPermissionRequest: approveAll, + customAgentsLocalOnly: false, + }); + const resumedCapture = JSON.parse(fs.readFileSync(capturePath, "utf8")) as { + requests: { + method: string; + params: { customAgentsLocalOnly?: boolean }; + }[]; + }; + const resumeRequests = resumedCapture.requests.filter((r) => r.method === "session.resume"); + expect(resumeRequests).toHaveLength(1); + expect(resumeRequests[0].params.customAgentsLocalOnly).toBe(false); + await resumed.disconnect(); + }); + + it("should send empty-mode custom agent locality defaults in initial requests", async () => { + const cliPath = path.join( + workDir, + `fake-cli-empty-${Date.now()}-${Math.random().toString(36).slice(2)}.js` + ); + const capturePath = path.join( + workDir, + `fake-cli-empty-capture-${Date.now()}-${Math.random().toString(36).slice(2)}.json` + ); + fs.writeFileSync(cliPath, FAKE_STDIO_CLI_SCRIPT); + + const client = new CopilotClient({ + mode: "empty", + baseDirectory: workDir, + workingDirectory: workDir, + env, + connection: RuntimeConnection.forStdio({ + path: cliPath, + args: ["--capture-file", capturePath], + }), + useLoggedInUser: false, + }); + onTestFinished(async () => { + try { + await client.forceStop(); + } catch { + // Ignore cleanup errors + } + }); + + const session = await client.createSession({ + availableTools: ["builtin:ask_user"], + customAgentsLocalOnly: undefined, + onPermissionRequest: approveAll, + }); + const sessionId = session.sessionId; + await session.disconnect(); + + const resumed = await client.resumeSession(sessionId, { + availableTools: ["builtin:ask_user"], + customAgentsLocalOnly: undefined, + onPermissionRequest: approveAll, + }); + + const capture = JSON.parse(fs.readFileSync(capturePath, "utf8")) as { + requests: { + method: string; + params: { customAgentsLocalOnly?: boolean }; + }[]; + }; + const createRequest = capture.requests.find((r) => r.method === "session.create"); + const resumeRequest = capture.requests.find((r) => r.method === "session.resume"); + expect(createRequest?.params.customAgentsLocalOnly).toBe(true); + expect(resumeRequest?.params.customAgentsLocalOnly).toBe(true); + + await resumed.disconnect(); }); it("should forward advanced session options in create wire request", async () => { diff --git a/nodejs/test/e2e/disabled_mcp_servers.e2e.test.ts b/nodejs/test/e2e/disabled_mcp_servers.e2e.test.ts new file mode 100644 index 000000000..ce1a504e8 --- /dev/null +++ b/nodejs/test/e2e/disabled_mcp_servers.e2e.test.ts @@ -0,0 +1,485 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { randomUUID } from "node:crypto"; +import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; +import { + approveAll, + CopilotRequestHandler, + RuntimeConnection, + type CopilotSession, +} from "../../src/index.js"; +import { createSdkTestContext, isInProcessTransport } from "./harness/sdkTestContext.js"; +import { waitForCondition } from "./harness/sdkTestHelper.js"; + +const __dirname = resolve(fileURLToPath(new URL(".", import.meta.url))); +const TEST_MCP_SERVER = resolve(__dirname, "../../../test/harness/test-mcp-server.mjs"); +const SYNTHETIC_RESPONSE = "PERSISTED_SESSION_READY"; +const MCP_TRIGGER_PROMPT = "Reply with the configured MCP test completion marker."; + +class PersistingRequestHandler extends CopilotRequestHandler { + protected override async sendRequest(request: Request): Promise { + const body = request.body ? await request.text() : ""; + const wantsStream = /"stream"\s*:\s*true/.test(body); + const url = request.url.toLowerCase(); + + if (url.endsWith("/models")) { + return new Response(MODEL_CATALOG_JSON, { + status: 200, + headers: { "content-type": "application/json" }, + }); + } + + if (url.includes("/responses")) { + return new Response(wantsStream ? RESPONSE_STREAM : RESPONSE_JSON, { + status: 200, + headers: { + "content-type": wantsStream ? "text/event-stream" : "application/json", + }, + }); + } + + if (url.includes("/chat/completions")) { + return new Response( + wantsStream ? CHAT_COMPLETION_STREAM : CHAT_COMPLETION_RESPONSE_JSON, + { + status: 200, + headers: { + "content-type": wantsStream ? "text/event-stream" : "application/json", + }, + } + ); + } + + return new Response("{}", { + status: 200, + headers: { "content-type": "application/json" }, + }); + } +} + +const RESPONSE_STREAM = [ + { + event: "response.created", + data: { + type: "response.created", + response: { + id: "persisted-session", + object: "response", + status: "in_progress", + output: [], + }, + }, + }, + { + event: "response.output_item.added", + data: { + type: "response.output_item.added", + output_index: 0, + item: { id: "message-1", type: "message", role: "assistant", content: [] }, + }, + }, + { + event: "response.content_part.added", + data: { + type: "response.content_part.added", + output_index: 0, + content_index: 0, + part: { type: "output_text", text: "" }, + }, + }, + { + event: "response.output_text.delta", + data: { + type: "response.output_text.delta", + output_index: 0, + content_index: 0, + delta: SYNTHETIC_RESPONSE, + }, + }, + { + event: "response.output_text.done", + data: { + type: "response.output_text.done", + output_index: 0, + content_index: 0, + text: SYNTHETIC_RESPONSE, + }, + }, + { + event: "response.completed", + data: { + type: "response.completed", + response: { + id: "persisted-session", + object: "response", + status: "completed", + output: [ + { + id: "message-1", + type: "message", + role: "assistant", + content: [{ type: "output_text", text: SYNTHETIC_RESPONSE }], + }, + ], + usage: { input_tokens: 1, output_tokens: 1, total_tokens: 2 }, + }, + }, + }, +] + .map(({ event, data }) => `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`) + .join(""); + +const RESPONSE_JSON = JSON.stringify({ + id: "persisted-session", + object: "response", + status: "completed", + output: [ + { + id: "message-1", + type: "message", + role: "assistant", + content: [{ type: "output_text", text: SYNTHETIC_RESPONSE }], + }, + ], + usage: { input_tokens: 1, output_tokens: 1, total_tokens: 2 }, +}); + +const CHAT_COMPLETION_STREAM = [ + { + id: "persisted-session", + object: "chat.completion.chunk", + created: 1, + model: "claude-sonnet-4.5", + choices: [ + { + index: 0, + delta: { role: "assistant", content: SYNTHETIC_RESPONSE }, + finish_reason: null, + }, + ], + }, + { + id: "persisted-session", + object: "chat.completion.chunk", + created: 1, + model: "claude-sonnet-4.5", + choices: [{ index: 0, delta: {}, finish_reason: "stop" }], + }, +] + .map((data) => `data: ${JSON.stringify(data)}\n\n`) + .concat("data: [DONE]\n\n") + .join(""); + +const CHAT_COMPLETION_RESPONSE_JSON = JSON.stringify({ + id: "persisted-session", + object: "chat.completion", + created: 1, + model: "claude-sonnet-4.5", + choices: [ + { + index: 0, + message: { role: "assistant", content: SYNTHETIC_RESPONSE }, + finish_reason: "stop", + }, + ], + usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }, +}); + +const MODEL_CATALOG_JSON = JSON.stringify({ + data: [ + { + id: "claude-sonnet-4.5", + name: "Claude Sonnet 4.5", + object: "model", + vendor: "Anthropic", + version: "1", + preview: false, + model_picker_enabled: true, + capabilities: { + type: "chat", + family: "claude-sonnet-4.5", + tokenizer: "o200k_base", + limits: { max_context_window_tokens: 200000, max_output_tokens: 8192 }, + supports: { streaming: true, tool_calls: true, parallel_tool_calls: true }, + }, + }, + ], +}); + +describe("disabled MCP servers", async () => { + const { + copilotClient: client, + createClient, + openAiEndpoint, + workDir, + } = await createSdkTestContext({ + copilotClientOptions: { + requestHandler: new PersistingRequestHandler(), + }, + }); + + function createPluginDirectory(prefix: string): { + pluginDirectory: string; + controlMarker: string; + disabledMarker: string; + } { + const pluginDirectory = join(workDir, `${prefix}-${randomUUID()}`); + mkdirSync(pluginDirectory, { recursive: true }); + const controlMarker = join(pluginDirectory, "control-started.log"); + const disabledMarker = join(pluginDirectory, "disabled-started.log"); + + writeFileSync( + join(pluginDirectory, "plugin.json"), + JSON.stringify({ + name: `${prefix}-${randomUUID()}`, + version: "1.0.0", + }) + ); + writeFileSync( + join(pluginDirectory, ".mcp.json"), + JSON.stringify({ + mcpServers: { + control: { + type: "stdio", + command: process.execPath, + args: [ + TEST_MCP_SERVER, + "--startup-marker", + controlMarker, + "--server-name", + "control", + ], + }, + disabled: { + type: "stdio", + command: process.execPath, + args: [ + TEST_MCP_SERVER, + "--startup-marker", + disabledMarker, + "--server-name", + "disabled", + ], + }, + }, + }) + ); + + return { pluginDirectory, controlMarker, disabledMarker }; + } + + function markerCount(markerPath: string): number { + if (!existsSync(markerPath)) { + return 0; + } + return readFileSync(markerPath, "utf8").trim().split("\n").filter(Boolean).length; + } + + async function waitForMarkerCount(markerPath: string, expectedCount: number): Promise { + await waitForCondition(() => markerCount(markerPath) >= expectedCount, { + timeoutMs: 60_000, + intervalMs: 100, + timeoutMessage: `Timed out waiting for ${markerPath} to be written ${expectedCount} time(s).`, + }); + } + + async function waitForMcpStatus( + session: CopilotSession, + serverName: string, + expectedStatus: string + ): Promise { + let lastStatus = ""; + await waitForCondition( + async () => { + const result = await session.rpc.mcp.list(); + const server = result.servers.find((candidate) => candidate.name === serverName); + lastStatus = server?.status ?? ""; + return lastStatus === expectedStatus; + }, + { + timeoutMs: 60_000, + intervalMs: 100, + timeoutMessage: `${serverName} did not reach ${expectedStatus}; last status was ${lastStatus}.`, + } + ); + } + + function expectSyntheticResponse(response: Awaited>) { + expect(response?.data.content).toBe(SYNTHETIC_RESPONSE); + } + + async function drainPostCreateRpc(session: CopilotSession): Promise { + // Drain a non-MCP post-create RPC without initializing MCP before the first model turn. + await session.rpc.metadata.snapshot(); + } + + async function mcpRequestCount(): Promise { + const requests = await openAiEndpoint.getRequests(); + return requests.filter((request) => request.method === "POST" && request.url === "/mcp") + .length; + } + + async function waitForMcpRequestCount(expectedCount: number): Promise { + let lastCount = 0; + await waitForCondition( + async () => { + lastCount = await mcpRequestCount(); + return lastCount >= expectedCount; + }, + { + timeoutMs: 60_000, + intervalMs: 100, + timeoutMessage: `Timed out waiting for ${expectedCount} /mcp request(s); saw ${lastCount}.`, + } + ); + } + + it( + "keeps disabled plugin MCP servers per-session on create", + { timeout: 120_000 }, + async () => { + const { + pluginDirectory: disabledPluginDirectory, + controlMarker: disabledControlMarker, + disabledMarker, + } = createPluginDirectory("disabled-mcp-create"); + + await using disabledSession = await client.createSession({ + onPermissionRequest: approveAll, + pluginDirectories: [disabledPluginDirectory], + disabledMcpServers: ["disabled"], + }); + + await drainPostCreateRpc(disabledSession); + expect(existsSync(disabledControlMarker)).toBe(false); + expect(existsSync(disabledMarker)).toBe(false); + expectSyntheticResponse( + await disabledSession.sendAndWait({ prompt: MCP_TRIGGER_PROMPT }) + ); + await waitForMarkerCount(disabledControlMarker, 1); + expect(existsSync(disabledMarker)).toBe(false); + await waitForMcpStatus(disabledSession, "control", "connected"); + await waitForMcpStatus(disabledSession, "disabled", "disabled"); + + const { + pluginDirectory: enabledPluginDirectory, + controlMarker: enabledControlMarker, + disabledMarker: enabledDisabledMarker, + } = createPluginDirectory("enabled-mcp-create"); + await using enabledSession = await client.createSession({ + onPermissionRequest: approveAll, + pluginDirectories: [enabledPluginDirectory], + }); + await drainPostCreateRpc(enabledSession); + expect(existsSync(enabledControlMarker)).toBe(false); + expect(existsSync(enabledDisabledMarker)).toBe(false); + expectSyntheticResponse( + await enabledSession.sendAndWait({ prompt: MCP_TRIGGER_PROMPT }) + ); + await waitForMarkerCount(enabledControlMarker, 1); + await waitForMarkerCount(enabledDisabledMarker, 1); + await waitForMcpStatus(enabledSession, "control", "connected"); + await waitForMcpStatus(enabledSession, "disabled", "connected"); + } + ); + + it( + "keeps the built-in GitHub MCP server disabled on the first message", + { timeout: 120_000 }, + async () => { + const disabledSession = await client.createSession({ + onPermissionRequest: approveAll, + enableConfigDiscovery: true, + enableMcpApps: true, + githubMcpToolConfig: { enableAllTools: true }, + disabledMcpServers: ["github-mcp-server"], + }); + + let disabledRequestsBeforeFirstMessage: number; + try { + await drainPostCreateRpc(disabledSession); + disabledRequestsBeforeFirstMessage = await mcpRequestCount(); + expect(disabledRequestsBeforeFirstMessage).toBe(0); + expectSyntheticResponse( + await disabledSession.sendAndWait({ prompt: MCP_TRIGGER_PROMPT }) + ); + expect(await mcpRequestCount()).toBe(disabledRequestsBeforeFirstMessage); + await waitForMcpStatus(disabledSession, "github-mcp-server", "disabled"); + expect(await mcpRequestCount()).toBe(disabledRequestsBeforeFirstMessage); + } finally { + await disabledSession.disconnect(); + } + + expect(await mcpRequestCount()).toBe(disabledRequestsBeforeFirstMessage); + + await using enabledSession = await client.createSession({ + onPermissionRequest: approveAll, + enableConfigDiscovery: true, + enableMcpApps: true, + githubMcpToolConfig: { enableAllTools: true }, + }); + await drainPostCreateRpc(enabledSession); + const requestsBeforeFirstMessage = await mcpRequestCount(); + expect(requestsBeforeFirstMessage).toBe(0); + expectSyntheticResponse( + await enabledSession.sendAndWait({ prompt: MCP_TRIGGER_PROMPT }) + ); + await waitForMcpRequestCount(requestsBeforeFirstMessage + 1); + await waitForMcpStatus(enabledSession, "github-mcp-server", "connected"); + } + ); + + it.skipIf(isInProcessTransport)( + "applies disabled plugin MCP servers on cold stdio resume", + async () => { + const { pluginDirectory, controlMarker, disabledMarker } = + createPluginDirectory("disabled-mcp-resume"); + const initialClient = createClient({ + connection: RuntimeConnection.forStdio({ path: process.env.COPILOT_CLI_PATH }), + requestHandler: new PersistingRequestHandler(), + }); + const resumeClient = createClient({ + connection: RuntimeConnection.forStdio({ path: process.env.COPILOT_CLI_PATH }), + }); + + try { + const originalSession = await initialClient.createSession({ + onPermissionRequest: approveAll, + enableSessionStore: true, + }); + const sessionId = originalSession.sessionId; + // A session.log entry alone does not materialize a session that a + // restarted runtime can resume. This self-contained model turn + // persists it without initializing MCP because no plugin directory + // is supplied until the resume request below. + const response = await originalSession.sendAndWait({ + prompt: "Return the configured persistence marker.", + }); + expectSyntheticResponse(response); + + expect(existsSync(controlMarker)).toBe(false); + expect(existsSync(disabledMarker)).toBe(false); + await initialClient.stop(); + + await using resumedSession = await resumeClient.resumeSession(sessionId, { + onPermissionRequest: approveAll, + enableSessionStore: true, + pluginDirectories: [pluginDirectory], + disabledMcpServers: ["disabled"], + }); + await waitForMcpStatus(resumedSession, "control", "connected"); + await waitForMcpStatus(resumedSession, "disabled", "disabled"); + await waitForMarkerCount(controlMarker, 1); + expect(existsSync(disabledMarker)).toBe(false); + } finally { + await initialClient.stop().catch(() => {}); + await resumeClient.stop().catch(() => {}); + } + } + ); +}); diff --git a/nodejs/test/e2e/factory.e2e.test.ts b/nodejs/test/e2e/factory.e2e.test.ts new file mode 100644 index 000000000..8c038d9de --- /dev/null +++ b/nodejs/test/e2e/factory.e2e.test.ts @@ -0,0 +1,312 @@ +import { execFileSync } from "node:child_process"; +import { existsSync, readFileSync, writeFileSync } from "node:fs"; +import { copyFile, mkdir, rm } from "node:fs/promises"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { expect, it, vi } from "vitest"; +import { approveAll, FactoryResumeError } from "../../src/index.js"; +import { + createSdkTestContext, + DEFAULT_GITHUB_TOKEN, + isInProcessTransport, +} from "./harness/sdkTestContext.js"; +import { retry } from "./harness/sdkTestHelper.js"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const factoryTestContext = isInProcessTransport + ? undefined + : await createSdkTestContext({ + copilotClientOptions: { + env: { + COPILOT_CLI_ENABLED_FEATURE_FLAGS: "EXTENSIONS,AGENT_FACTORIES", + }, + }, + }); + +async function setupFactoryExtension(workDir: string, onPermissionRequest = approveAll) { + if (!factoryTestContext) { + throw new Error("Factory E2E requires the stdio transport"); + } + + const { copilotClient, openAiEndpoint } = factoryTestContext; + const extensionDir = join(workDir, ".github", "extensions", "factory-smoke"); + const readyFile = join(extensionDir, "ready"); + await rm(join(workDir, ".github"), { recursive: true, force: true }); + await mkdir(extensionDir, { recursive: true }); + await copyFile( + join(__dirname, "fixtures", "factory-extension.mjs"), + join(extensionDir, "extension.mjs") + ); + execFileSync("git", ["init", "--quiet"], { cwd: workDir }); + + await openAiEndpoint.setCopilotUserByToken(DEFAULT_GITHUB_TOKEN, { + login: "factory-e2e-user", + copilot_plan: "individual_pro", + token_based_billing: true, + is_mcp_enabled: true, + endpoints: { + api: openAiEndpoint.url, + telemetry: "https://localhost:1/telemetry", + }, + analytics_tracking_id: "e2e-test-tracking-id", + }); + + const session = await copilotClient.createSession({ + requestExtensions: true, + extensionSdkPath: resolve(__dirname, "..", "..", "dist"), + onPermissionRequest, + onElicitationRequest: async () => ({ + action: "accept", + content: { action: "approve" }, + }), + }); + + await retry( + "wait for the factory extension to join the session", + async () => { + expect(existsSync(readyFile)).toBe(true); + }, + 300, + 100 + ); + + return session; +} + +it.skipIf(isInProcessTransport)( + "runs an extension-authored factory across the SDK process boundary", + async () => { + if (!factoryTestContext) { + throw new Error("Factory E2E requires the stdio transport"); + } + const { workDir } = factoryTestContext; + await using session = await setupFactoryExtension(workDir); + + const result = await session.factory.run("argument-echo", { + args: { source: "sdk-e2e", count: 11 }, + }); + + expect(result).toMatchObject({ + status: "completed", + result: { source: "sdk-e2e", count: 11 }, + }); + } +); + +it.skipIf(isInProcessTransport)( + "forwards every declared subagent option to the runtime", + async () => { + if (!factoryTestContext) { + throw new Error("Factory E2E requires the stdio transport"); + } + const { workDir } = factoryTestContext; + await using session = await setupFactoryExtension(workDir); + + const result = await session.factory.run("forwards-subagent-options"); + + expect(result).toMatchObject({ + status: "completed", + result: { didThrow: false }, + }); + }, + // The factory abandons its subagent once the runtime has accepted the + // request, so the run settles only after the runtime drains that work. + 60_000 +); + +it.skipIf(isInProcessTransport)( + "throws FactoryResumeError with not_found for an unknown run", + async () => { + if (!factoryTestContext) { + throw new Error("Factory E2E requires the stdio transport"); + } + const { workDir } = factoryTestContext; + await using session = await setupFactoryExtension(workDir); + + const error = await session.factory + .resume("00000000-0000-0000-0000-000000000000") + .catch((caught: unknown) => caught); + + expect(error).toBeInstanceOf(FactoryResumeError); + expect((error as FactoryResumeError).code).toBe("not_found"); + } +); + +it.skipIf(isInProcessTransport)( + "throws FactoryResumeError with non_resumable for a completed run", + async () => { + if (!factoryTestContext) { + throw new Error("Factory E2E requires the stdio transport"); + } + const { workDir } = factoryTestContext; + await using session = await setupFactoryExtension(workDir); + + const run = await session.factory.run("argument-echo"); + const error = await session.factory.resume(run.runId).catch((caught: unknown) => caught); + + expect(error).toBeInstanceOf(FactoryResumeError); + expect((error as FactoryResumeError).code).toBe("non_resumable"); + } +); + +it.skipIf(isInProcessTransport)( + "runs a factory when its session denies every permission request", + async () => { + if (!factoryTestContext) { + throw new Error("Factory E2E requires the stdio transport"); + } + const { workDir } = factoryTestContext; + const denyPermissions = vi.fn(() => ({ kind: "reject" as const })); + await using session = await setupFactoryExtension(workDir, denyPermissions); + + await expect(session.factory.run("argument-echo")).resolves.toMatchObject({ + status: "completed", + }); + expect(denyPermissions).not.toHaveBeenCalled(); + } +); + +it.skipIf(isInProcessTransport)( + "resumes a failed factory when its session denies every permission request", + async () => { + if (!factoryTestContext) { + throw new Error("Factory E2E requires the stdio transport"); + } + const { workDir } = factoryTestContext; + const denyPermissions = vi.fn(() => ({ kind: "reject" as const })); + await using session = await setupFactoryExtension(workDir, denyPermissions); + + const failedRun = await session.factory.run("fails-once"); + expect(failedRun).toMatchObject({ + status: "error", + }); + + await expect(session.factory.resume(failedRun.runId)).resolves.toMatchObject({ + status: "completed", + result: "resumed", + }); + expect(denyPermissions).not.toHaveBeenCalled(); + } +); + +it.skipIf(isInProcessTransport)( + "refuses a factory started through the context session from a factory body", + async () => { + if (!factoryTestContext) { + throw new Error("Factory E2E requires the stdio transport"); + } + const { workDir } = factoryTestContext; + await using session = await setupFactoryExtension(workDir); + + const result = await session.factory.run("starts-from-context-session"); + + expect(result).toMatchObject({ + status: "completed", + result: expect.stringContaining("factory.run and factory.resume"), + }); + expect((result as { result: string }).result).toContain("factory body"); + } +); + +it.skipIf(isInProcessTransport)( + "refuses a factory started through the module session from a factory body", + async () => { + if (!factoryTestContext) { + throw new Error("Factory E2E requires the stdio transport"); + } + const { workDir } = factoryTestContext; + await using session = await setupFactoryExtension(workDir); + + const result = await session.factory.run("starts-from-module-session"); + + expect(result).toMatchObject({ + status: "completed", + result: expect.stringContaining("factory.run and factory.resume"), + }); + expect((result as { result: string }).result).toContain("factory body"); + } +); + +it.skipIf(isInProcessTransport)( + "allows a module-level extension watcher to start a factory while another body is parked", + async () => { + if (!factoryTestContext) { + throw new Error("Factory E2E requires the stdio transport"); + } + const { workDir } = factoryTestContext; + const extensionDir = join(workDir, ".github", "extensions", "factory-smoke"); + await using session = await setupFactoryExtension(workDir); + + const parked = session.factory.run("parked"); + await retry( + "wait for the parked factory to enter its body", + async () => { + expect(existsSync(join(extensionDir, "entered"))).toBe(true); + }, + 100, + 100 + ); + + writeFileSync(join(extensionDir, "start-b"), "start"); + const bResultFile = join(extensionDir, "b-result"); + await retry( + "wait for the module-level watcher factory run to succeed", + async () => { + expect(existsSync(bResultFile)).toBe(true); + expect(JSON.parse(readFileSync(bResultFile, "utf8"))).toMatchObject({ + status: "success", + result: { + status: "completed", + result: { source: "module-watcher" }, + }, + }); + }, + 100, + 100 + ); + + writeFileSync(join(extensionDir, "release"), "release"); + await expect(parked).resolves.toMatchObject({ + status: "completed", + result: "released", + }); + }, + 60_000 +); + +it.skipIf(isInProcessTransport)( + "returns an array result from an extension-authored factory", + async () => { + if (!factoryTestContext) { + throw new Error("Factory E2E requires the stdio transport"); + } + const { workDir } = factoryTestContext; + await using session = await setupFactoryExtension(workDir); + + const result = await session.factory.run("array-result"); + + expect(result).toMatchObject({ + status: "completed", + result: [1, "two", false], + }); + } +); + +it.skipIf(isInProcessTransport)( + "passes array factory arguments across the SDK process boundary", + async () => { + if (!factoryTestContext) { + throw new Error("Factory E2E requires the stdio transport"); + } + const { workDir } = factoryTestContext; + await using session = await setupFactoryExtension(workDir); + + const args = [1, "two", false]; + const result = await session.factory.run("argument-echo", { args }); + + expect(result).toMatchObject({ + status: "completed", + result: args, + }); + } +); diff --git a/nodejs/test/e2e/fixtures/factory-extension.mjs b/nodejs/test/e2e/fixtures/factory-extension.mjs new file mode 100644 index 000000000..45227a1be --- /dev/null +++ b/nodejs/test/e2e/fixtures/factory-extension.mjs @@ -0,0 +1,171 @@ +import { existsSync, writeFileSync } from "node:fs"; +import { defineFactory, joinSession } from "@github/copilot-sdk/extension"; + +const marker = (name) => new URL(`./${name}`, import.meta.url); + +async function waitForMarker(name, timeoutMs) { + const deadline = Date.now() + timeoutMs; + while (!existsSync(marker(name))) { + if (Date.now() >= deadline) { + throw new Error(`Timed out waiting for ${name}`); + } + await new Promise((resolve) => setTimeout(resolve, 50)); + } +} + +const argumentEcho = defineFactory({ + meta: { + name: "argument-echo", + description: "Return the invocation arguments verbatim.", + phases: [], + // Proves a declared shape survives the SDK boundary and registers against a + // real runtime. It does not exercise enforcement: `argsSchema` is checked by + // the model's `run_factory` tool, and these tests invoke `session.factory.run`, + // which does not validate. The declaration stays as wide as this factory's + // actual contract — it echoes any JsonValue, and is called with an array, an + // object, and nothing — so it cannot constrain the runs below. + argsSchema: { + type: ["object", "array", "string", "number", "integer", "boolean", "null"], + }, + }, + run: async ({ args }) => args, +}); + +const arrayResult = defineFactory({ + meta: { + name: "array-result", + description: "Return an array result.", + phases: [], + }, + run: async () => [1, "two", false], +}); + +const forwardsSubagentOptions = defineFactory({ + meta: { + name: "forwards-subagent-options", + description: "Send every declared subagent option to the runtime.", + phases: [], + }, + run: async ({ agent }) => { + // Only the runtime's acceptance of the payload is under test. A refused + // request rejects quickly, because the runtime parses the options before + // it starts a subagent. A subagent that is merely slow to reach a model + // proves the payload was accepted, so waiting for it adds nothing and + // hangs wherever no model is reachable. + const call = agent("Confirm that this request is accepted.", { + agent: "reviewer", + reasoningEffort: "high", + contextTier: "long_context", + }); + // A rejection that lands after the race still needs a handler. + call.catch(() => {}); + let settleTimer; + const stillPending = new Promise((resolve) => { + settleTimer = setTimeout(() => resolve(undefined), 3000); + settleTimer.unref?.(); + }); + try { + await Promise.race([call, stillPending]); + return { didThrow: false }; + } catch { + return { didThrow: true }; + } finally { + clearTimeout(settleTimer); + } + }, +}); + +const startsFromContextSession = defineFactory({ + meta: { + name: "starts-from-context-session", + description: "Try to start a factory through the context session.", + phases: [], + }, + run: async ({ session }) => { + try { + await session.factory.run("argument-echo"); + return "unexpectedly started a factory"; + } catch (error) { + return error instanceof Error ? error.message : String(error); + } + }, +}); + +let session; + +const startsFromModuleSession = defineFactory({ + meta: { + name: "starts-from-module-session", + description: "Try to start a factory through the module session.", + phases: [], + }, + run: async () => { + try { + await session.factory.run("argument-echo"); + return "unexpectedly started a factory"; + } catch (error) { + return error instanceof Error ? error.message : String(error); + } + }, +}); + +const parked = defineFactory({ + meta: { + name: "parked", + description: "Wait for a test-controlled release marker.", + phases: [], + }, + run: async () => { + writeFileSync(marker("entered"), "entered"); + await waitForMarker("release", 30_000); + return "released"; + }, +}); + +const failsOnce = defineFactory({ + meta: { + name: "fails-once", + description: "Fails its first attempt and succeeds when resumed.", + phases: [], + }, + run: async () => { + if (!existsSync(marker("fails-once-attempted"))) { + writeFileSync(marker("fails-once-attempted"), "attempted"); + throw new Error("first attempt failed"); + } + return "resumed"; + }, +}); + +session = await joinSession({ + factories: [ + argumentEcho, + arrayResult, + forwardsSubagentOptions, + startsFromContextSession, + startsFromModuleSession, + parked, + failsOnce, + ], +}); + +void waitForMarker("start-b", 30_000) + .then(async () => { + const result = await session.factory.run("argument-echo", { + args: { source: "module-watcher" }, + }); + writeFileSync(marker("b-result"), JSON.stringify({ status: "success", result })); + }) + .catch((error) => { + if (existsSync(marker("start-b"))) { + writeFileSync( + marker("b-result"), + JSON.stringify({ + status: "error", + error: error instanceof Error ? error.message : String(error), + }) + ); + } + }); + +writeFileSync(marker("ready"), "ready"); diff --git a/nodejs/test/e2e/harness/CapiProxy.ts b/nodejs/test/e2e/harness/CapiProxy.ts index a6232587e..c25d422f9 100644 --- a/nodejs/test/e2e/harness/CapiProxy.ts +++ b/nodejs/test/e2e/harness/CapiProxy.ts @@ -2,6 +2,7 @@ import { spawn } from "child_process"; import { resolve } from "path"; import { createInterface } from "readline"; import { expect } from "vitest"; +import type { CapturedRequest } from "../../../../test/harness/replayingCapiProxy"; import { CopilotUserResponse, ParsedHttpExchange, @@ -121,6 +122,11 @@ export class CapiProxy { return await response.json(); } + async getRequests(): Promise { + const response = await fetch(`${this.proxyUrl}/requests`, { method: "GET" }); + return await response.json(); + } + async stop(skipWritingCache?: boolean): Promise { const url = skipWritingCache ? `${this.proxyUrl}/stop?skipWritingCache=true` diff --git a/nodejs/test/e2e/hooks_extended.e2e.test.ts b/nodejs/test/e2e/hooks_extended.e2e.test.ts index 82e1812f1..3ac858650 100644 --- a/nodejs/test/e2e/hooks_extended.e2e.test.ts +++ b/nodejs/test/e2e/hooks_extended.e2e.test.ts @@ -6,6 +6,7 @@ import { describe, expect, it } from "vitest"; import { z } from "zod"; import { approveAll, defineTool } from "../../src/index.js"; import type { + AgentStopHookInput, ErrorOccurredHookInput, PostToolUseFailureHookInput, PostToolUseHookInput, @@ -13,6 +14,7 @@ import type { SessionEndHookInput, SessionStartHookInput, UserPromptSubmittedHookInput, + UserPromptTransformedHookInput, } from "../../src/types.js"; import { createSdkTestContext } from "./harness/sdkTestContext.js"; @@ -168,6 +170,36 @@ describe("Extended session hooks", async () => { await session.disconnect(); }); + it("should invoke userPromptTransformed hook and modify transformed prompt", async () => { + const inputs: UserPromptTransformedHookInput[] = []; + const session = await client.createSession({ + onPermissionRequest: approveAll, + hooks: { + onUserPromptTransformed: async (input, invocation) => { + inputs.push(input); + expect(invocation.sessionId).toBeTruthy(); + return { + modifiedTransformedPrompt: "Reply with exactly: HOOKED_TRANSFORMED_PROMPT", + }; + }, + }, + }); + + const response = await session.sendAndWait({ + prompt: "Answer the request above.", + }); + + expect(inputs.length).toBeGreaterThan(0); + expect(inputs[0].prompt).toContain("Answer the request above."); + expect(inputs[0].transformedPrompt).toContain("Answer the request above."); + expect(inputs[0].transformedPrompt).toContain(""); + expect(inputs[0].timestamp).toBeInstanceOf(Date); + expect(inputs[0].workingDirectory).toBeDefined(); + expect(response?.data.content ?? "").toContain("HOOKED_TRANSFORMED_PROMPT"); + + await session.disconnect(); + }); + it("should invoke sessionStart hook", async () => { const inputs: SessionStartHookInput[] = []; const invocationSessionIds: string[] = []; @@ -260,6 +292,38 @@ describe("Extended session hooks", async () => { await session.disconnect(); }); + it("should invoke agentStop hook and apply block response", async () => { + const inputs: AgentStopHookInput[] = []; + const session = await client.createSession({ + onPermissionRequest: approveAll, + hooks: { + onAgentStop: async (input, invocation) => { + expect(invocation.sessionId).toBe(session.sessionId); + inputs.push(input); + if (inputs.length === 1) { + return { + decision: "block", + reason: "Reply with exactly: AGENT_STOP_CONTINUED", + }; + } + }, + }, + }); + + const response = await session.sendAndWait({ + prompt: "Reply with exactly: AGENT_STOP_INITIAL", + }); + + expect(inputs).toHaveLength(2); + expect(inputs[0].stopHookActive).not.toBe(true); + expect(inputs[1].stopHookActive).toBe(true); + expect(inputs[0].stopReason).toBe("end_turn"); + expect(inputs[0].transcriptPath).toBeTruthy(); + expect(response?.data.content ?? "").toContain("AGENT_STOP_CONTINUED"); + + await session.disconnect(); + }); + it("should allow preToolUse to return modifiedArgs and suppressOutput", async () => { const inputs: PreToolUseHookInput[] = []; const session = await client.createSession({ diff --git a/nodejs/test/e2e/permissions.e2e.test.ts b/nodejs/test/e2e/permissions.e2e.test.ts index e7c26a293..b7fa6087a 100644 --- a/nodejs/test/e2e/permissions.e2e.test.ts +++ b/nodejs/test/e2e/permissions.e2e.test.ts @@ -5,14 +5,15 @@ import { realpathSync } from "fs"; import { mkdir, readFile, writeFile } from "fs/promises"; import { join } from "path"; -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import { z } from "zod"; import type { + PermissionDecisionContext, PermissionRequest, PermissionRequestResult, ToolResultObject, } from "../../src/index.js"; -import { approveAll, defineTool } from "../../src/index.js"; +import { approveAll, defineTool, createAttributedPermissionResult } from "../../src/index.js"; import { createSdkTestContext, isInProcessTransport } from "./harness/sdkTestContext.js"; import { getFinalAssistantMessage, getNextEventOfType } from "./harness/sdkTestHelper.js"; @@ -90,6 +91,61 @@ describe("Permission callbacks", async () => { await session.disconnect(); }); + it("should honor a decision annotated with decisionContext", async () => { + // End-to-end proof that decisionContext survives the real permission flow. + // The runtime only emits its auto_approval_decision telemetry when its own + // auto-approval judge metadata is also present (feature-flagged and model + // backed), so that event is not observable here. Instead we assert the exact + // params handed to the CLI: decisionContext must be a top-level sibling of + // `result`, never nested inside it. The CLI tolerates a nested key silently, + // so asserting the params shape is what actually gives this test teeth. + const decisionContext: PermissionDecisionContext = { + outcome: "prompted_user", + source: "human_response", + surface: "sdk", + }; + + const session = await client.createSession({ + onPermissionRequest: () => + createAttributedPermissionResult({ kind: "reject" }, decisionContext), + }); + + // Spies preserve the original implementation, so the decision still reaches + // the CLI and the assertions below observe a real, honored round-trip. + const respondSpy = vi.spyOn(session.rpc.permissions, "handlePendingPermissionRequest"); + + let userRejectedToolCall = false; + session.on((event) => { + if ( + event.type === "tool.execution_complete" && + !event.data.success && + event.data.error?.message.toLowerCase().includes("user rejected") + ) { + userRejectedToolCall = true; + } + }); + + const originalContent = "protected content"; + const testFile = join(workDir, "protected.txt"); + await writeFile(testFile, originalContent); + + await session.sendAndWait({ + prompt: "Edit protected.txt and replace 'protected' with 'hacked'.", + }); + + // The decision was applied by the CLI, not merely sent. + expect(userRejectedToolCall).toBe(true); + expect(await readFile(testFile, "utf-8")).toBe(originalContent); + + expect(respondSpy).toHaveBeenCalled(); + const params = respondSpy.mock.calls[0]![0]; + expect(params.decisionContext).toEqual(decisionContext); + expect(params.result).toEqual({ kind: "reject" }); + expect(Object.keys(params).sort()).toEqual(["decisionContext", "requestId", "result"]); + + await session.disconnect(); + }); + it("should deny tool operations when handler explicitly denies", async () => { let permissionDenied = false; diff --git a/nodejs/test/e2e/rewind.e2e.test.ts b/nodejs/test/e2e/rewind.e2e.test.ts new file mode 100644 index 000000000..920ffed19 --- /dev/null +++ b/nodejs/test/e2e/rewind.e2e.test.ts @@ -0,0 +1,81 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { existsSync, readFileSync } from "node:fs"; +import { join, resolve } from "node:path"; +import { describe, expect, it } from "vitest"; +import { approveAll } from "../../src/index.js"; +import { createSdkTestContext } from "./harness/sdkTestContext.js"; + +const FILE_NAME = "rewind-sdk.txt"; +const FILE_CONTENT = "SDK rewind content"; + +function expectSamePath(actual: string, expected: string): void { + const actualPath = resolve(actual); + const expectedPath = resolve(expected); + if (process.platform === "win32") { + expect(actualPath.toLowerCase()).toBe(expectedPath.toLowerCase()); + } else { + expect(actualPath).toBe(expectedPath); + } +} + +describe("Rewind", async () => { + const { copilotClient: client, workDir } = await createSdkTestContext(); + + it("should restore tracked file and conversation", async () => { + const filePath = join(workDir, FILE_NAME); + const session = await client.createSession({ + model: "claude-sonnet-4.5", + enableFileChangeTracking: true, + onPermissionRequest: approveAll, + }); + + try { + const response = await session.sendAndWait({ + prompt: `Use the create tool to create ${FILE_NAME} containing exactly ${FILE_CONTENT}. After the tool succeeds, reply with exactly SDK_REWIND_DONE.`, + }); + + expect(response?.data.content).toBe("SDK_REWIND_DONE"); + expect(existsSync(filePath)).toBe(true); + expect(readFileSync(filePath, "utf8")).toBe(FILE_CONTENT); + + let rewindPoints = await session.rpc.history.listRewindPoints(); + const deadline = Date.now() + 10_000; + while (rewindPoints.unavailableReason && Date.now() < deadline) { + await new Promise((resolveDelay) => setTimeout(resolveDelay, 100)); + rewindPoints = await session.rpc.history.listRewindPoints(); + } + + expect(rewindPoints.unavailableReason).toBeUndefined(); + expect(rewindPoints.fileChangeTrackingEnabled).toBe(true); + expect(rewindPoints.points).toHaveLength(1); + const rewindPoint = rewindPoints.points[0]; + expect(rewindPoint.canRestoreFiles).toBe(true); + expect(rewindPoint.fileCount).toBe(1); + + const preview = await session.rpc.history.previewRewind({ + eventId: rewindPoint.eventId, + }); + expect(preview.available).toBe(true); + expect(preview.files).toHaveLength(1); + expectSamePath(preview.files[0].path, filePath); + + const rewind = await session.rpc.history.rewind({ + eventId: rewindPoint.eventId, + mode: "conversation-and-files", + }); + expect(rewind.outcome).toBe("success"); + expect(rewind.eventsRemoved).toBeGreaterThan(0); + expect(rewind.restoredFiles).toHaveLength(1); + expectSamePath(rewind.restoredFiles[0], filePath); + expect(existsSync(filePath)).toBe(false); + + const events = await session.getEvents(); + expect(events.some((event) => event.id === rewindPoint.eventId)).toBe(false); + } finally { + await session.disconnect(); + } + }); +}); diff --git a/nodejs/test/e2e/session.e2e.test.ts b/nodejs/test/e2e/session.e2e.test.ts index 88fdf4c29..d99a3e392 100644 --- a/nodejs/test/e2e/session.e2e.test.ts +++ b/nodejs/test/e2e/session.e2e.test.ts @@ -964,15 +964,21 @@ describe("Send Blocking Behavior", async () => { expect(event.data.newModel).toBe("gpt-4.1"); }); - it("should set model with reasoningEffort", async () => { - await using session = await client.createSession({ onPermissionRequest: approveAll }); + describe("reasoning effort model switch (isolated to avoid models cache contamination)", async () => { + const { copilotClient: reasoningClient } = await createSdkTestContext(); - const modelChangePromise = getNextEventOfType(session, "session.model_change"); + it("should set model with reasoningEffort", async () => { + await using session = await reasoningClient.createSession({ + onPermissionRequest: approveAll, + }); - await session.setModel("gpt-4.1", { reasoningEffort: "high" }); + const modelChangePromise = getNextEventOfType(session, "session.model_change"); - const event = await modelChangePromise; - expect(event.data.newModel).toBe("gpt-4.1"); - expect(event.data.reasoningEffort).toBe("high"); + await session.setModel("gpt-5.4", { reasoningEffort: "high" }); + + const event = await modelChangePromise; + expect(event.data.newModel).toBe("gpt-5.4"); + expect(event.data.reasoningEffort).toBe("high"); + }); }); }); diff --git a/nodejs/test/e2e/session_config.e2e.test.ts b/nodejs/test/e2e/session_config.e2e.test.ts index 98b1a0bfa..85137e0ff 100644 --- a/nodejs/test/e2e/session_config.e2e.test.ts +++ b/nodejs/test/e2e/session_config.e2e.test.ts @@ -222,6 +222,23 @@ describe("Session Configuration", async () => { return (exchange.request.tools ?? []).map((t) => t.function.name); } + async function expectGitHubMcpConfigApplied(session: CopilotSession): Promise { + await session.rpc.mcp.list(); + await retry("capture configured GitHub MCP request", async () => { + const requests = await openAiEndpoint.getRequests(); + const request = requests.find( + (entry) => entry.method === "POST" && entry.url === "/mcp" + ); + expect( + request, + `captured requests: ${requests.map((entry) => `${entry.method} ${entry.url}`).join(", ")}` + ).toBeDefined(); + expect(request?.headers["x-mcp-toolsets"]).toBe("all"); + expect(request?.headers["x-mcp-insiders"]).toBe("true"); + expect(requests.some((entry) => entry.url === "/mcp/readonly")).toBe(false); + }); + } + async function sendAndGetNextExchange( session: { sendAndWait(options: { prompt: string }): Promise }, prompt: string @@ -848,4 +865,25 @@ describe("Session Configuration", async () => { await session2.disconnect(); } }); + + it("should apply GitHub MCP tool config on create", async () => { + const session = await client.createSession({ + onPermissionRequest: approveAll, + enableConfigDiscovery: true, + enableMcpApps: true, + githubMcpToolConfig: { + enableAllTools: true, + additionalToolsets: ["actions"], + additionalTools: ["get_me"], + enableInsidersMode: true, + disableFormDeferral: true, + }, + }); + + try { + await expectGitHubMcpConfigApplied(session); + } finally { + await session.disconnect(); + } + }); }); diff --git a/nodejs/test/e2e/session_fs.e2e.test.ts b/nodejs/test/e2e/session_fs.e2e.test.ts index 11de9582e..5726d39b5 100644 --- a/nodejs/test/e2e/session_fs.e2e.test.ts +++ b/nodejs/test/e2e/session_fs.e2e.test.ts @@ -299,6 +299,20 @@ describe("Session Fs Adapter", () => { rowsAffected: 0, }; }, + async transaction(statements) { + return statements.map((statement) => ({ + columns: ["sessionId", "query", "queryType", "answer"], + rows: [ + { + sessionId: "handler-session", + query: statement.query, + queryType: statement.queryType, + answer: statement.params?.answer, + }, + ], + rowsAffected: 0, + })); + }, async exists() { return true; }, @@ -433,6 +447,9 @@ describe("Session Fs Adapter", () => { query: async () => { throw enoent; }, + transaction: async () => { + throw enoent; + }, exists: async () => { throw enoent; }, @@ -589,6 +606,13 @@ function createTestSessionFsHandler( rowsAffected: 0, }; }, + async transaction(statements) { + return statements.map(() => ({ + columns: [], + rows: [], + rowsAffected: 0, + })); + }, async exists() { return true; }, diff --git a/nodejs/test/e2e/session_fs_sqlite.e2e.test.ts b/nodejs/test/e2e/session_fs_sqlite.e2e.test.ts index cea67c145..5bc944240 100644 --- a/nodejs/test/e2e/session_fs_sqlite.e2e.test.ts +++ b/nodejs/test/e2e/session_fs_sqlite.e2e.test.ts @@ -18,6 +18,8 @@ import { type SessionFsFileInfo, type SessionFsSqliteQueryResult, type SessionFsSqliteQueryType, + type SessionFsSqliteStatement, + SessionFsSqliteTransactionFailure, } from "../../src/index.js"; import { createSdkTestContext } from "./harness/sdkTestContext.js"; @@ -207,41 +209,56 @@ function createTestSessionFsHandlerWithSqlite( params?: Record ): Promise { sqliteCalls.push({ sessionId: session.sessionId, queryType, query }); - + return runStatement(getOrCreateDb(), queryType, query, params); + }, + async transaction( + statements: SessionFsSqliteStatement[] + ): Promise { const database = getOrCreateDb(); - const trimmed = query.trim(); - if (trimmed.length === 0) { - return undefined; - } - - switch (queryType) { - case "exec": - database.exec(trimmed); - return undefined; - - case "query": { - const stmt = database.prepare(trimmed); - const rows = (params ? stmt.all(params) : stmt.all()) as Record< - string, - unknown - >[]; - const columns = rows.length > 0 ? Object.keys(rows[0]) : []; - return { rows, columns, rowsAffected: 0 }; + let commitStarted = false; + try { + database.exec("BEGIN IMMEDIATE"); + const results = statements.map((statement) => { + sqliteCalls.push({ + sessionId: session.sessionId, + queryType: statement.queryType, + query: statement.query, + }); + return ( + runStatement( + database, + statement.queryType, + statement.query, + statement.params + ) ?? { rows: [], columns: [], rowsAffected: 0 } + ); + }); + commitStarted = true; + database.exec("COMMIT"); + return results; + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + if (commitStarted) { + throw new SessionFsSqliteTransactionFailure(message, "postCommitAmbiguous"); } - - case "run": { - const stmt = database.prepare(trimmed); - const result = params ? stmt.run(params) : stmt.run(); - return { - rows: [], - columns: [], - rowsAffected: Number(result.changes), - lastInsertRowid: - result.lastInsertRowid !== undefined - ? Number(result.lastInsertRowid) - : undefined, - }; + if (database.inTransaction) { + try { + database.exec("ROLLBACK"); + } catch (rollbackError) { + const rollbackMessage = + rollbackError instanceof Error + ? rollbackError.message + : String(rollbackError); + throw new SessionFsSqliteTransactionFailure( + `${message}; rollback failed: ${rollbackMessage}`, + "fatal" + ); + } } + throw new SessionFsSqliteTransactionFailure( + message, + /busy|locked/i.test(message) ? "busyOrLocked" : "fatal" + ); } }, async exists(): Promise { @@ -250,3 +267,42 @@ function createTestSessionFsHandlerWithSqlite( }, }; } + +function runStatement( + database: DatabaseSync, + queryType: SessionFsSqliteQueryType, + query: string, + params?: Record +): SessionFsSqliteQueryResult | undefined { + const trimmed = query.trim(); + if (trimmed.length === 0) { + return undefined; + } + + switch (queryType) { + case "exec": + database.exec(trimmed); + return undefined; + + case "query": { + const stmt = database.prepare(trimmed); + const rows = (params ? stmt.all(params) : stmt.all()) as Record[]; + const columns = rows.length > 0 ? Object.keys(rows[0]) : []; + return { rows, columns, rowsAffected: 0 }; + } + + case "run": { + const stmt = database.prepare(trimmed); + const result = params ? stmt.run(params) : stmt.run(); + return { + rows: [], + columns: [], + rowsAffected: Number(result.changes), + lastInsertRowid: + result.lastInsertRowid !== undefined + ? Number(result.lastInsertRowid) + : undefined, + }; + } + } +} diff --git a/nodejs/test/e2e/streaming_fidelity.e2e.test.ts b/nodejs/test/e2e/streaming_fidelity.e2e.test.ts index 17b522261..98b8eb188 100644 --- a/nodejs/test/e2e/streaming_fidelity.e2e.test.ts +++ b/nodejs/test/e2e/streaming_fidelity.e2e.test.ts @@ -145,32 +145,37 @@ describe("Streaming Fidelity", async () => { await session2.disconnect(); }); - it("should emit streaming deltas with reasoning effort configured", async () => { - const session = await client.createSession({ - onPermissionRequest: approveAll, - streaming: true, - reasoningEffort: "high", - }); + describe("reasoning effort (isolated to avoid models cache contamination)", async () => { + const { copilotClient: reasoningClient } = await createSdkTestContext(); - const events: SessionEvent[] = []; - session.on((event) => events.push(event)); + it("should emit streaming deltas with reasoning effort configured", async () => { + const session = await reasoningClient.createSession({ + onPermissionRequest: approveAll, + model: "gpt-5.4", + streaming: true, + reasoningEffort: "high", + }); - await session.sendAndWait({ prompt: "What is 15 * 17?" }); + const events: SessionEvent[] = []; + session.on((event) => events.push(event)); - const deltaEvents = events.filter((e) => e.type === "assistant.message_delta"); - expect(deltaEvents.length).toBeGreaterThanOrEqual(1); + await session.sendAndWait({ prompt: "What is 15 * 17?" }); - const assistantEvents = events.filter((e) => e.type === "assistant.message"); - expect(assistantEvents.length).toBeGreaterThanOrEqual(1); - const lastAssistant = assistantEvents[assistantEvents.length - 1]!; - expect(lastAssistant.data.content).toContain("255"); + const deltaEvents = events.filter((e) => e.type === "assistant.message_delta"); + expect(deltaEvents.length).toBeGreaterThanOrEqual(1); - // Verify the session was created with reasoning effort via getMessages - const messages = await session.getEvents(); - const startEvent = messages.find((m) => m.type === "session.start"); - expect(startEvent).toBeDefined(); - expect(startEvent!.data.reasoningEffort).toBe("high"); + const assistantEvents = events.filter((e) => e.type === "assistant.message"); + expect(assistantEvents.length).toBeGreaterThanOrEqual(1); + const lastAssistant = assistantEvents[assistantEvents.length - 1]!; + expect(lastAssistant.data.content).toContain("255"); - await session.disconnect(); + // Verify the session was created with reasoning effort via getMessages + const messages = await session.getEvents(); + const startEvent = messages.find((m) => m.type === "session.start"); + expect(startEvent).toBeDefined(); + expect(startEvent!.data.reasoningEffort).toBe("high"); + + await session.disconnect(); + }); }); }); diff --git a/nodejs/test/e2e/tool_results.e2e.test.ts b/nodejs/test/e2e/tool_results.e2e.test.ts index 6e8729c42..eb6ecf6f7 100644 --- a/nodejs/test/e2e/tool_results.e2e.test.ts +++ b/nodejs/test/e2e/tool_results.e2e.test.ts @@ -59,6 +59,7 @@ describe("Tool Results", async () => { tools: [ defineTool("check_status", { description: "Checks the status of a service", + isTerminal: true, handler: (): ToolResultObject => ({ textResultForLlm: "Service unavailable", resultType: "failure", @@ -74,6 +75,7 @@ describe("Tool Results", async () => { const failureContent = assistantMessage?.data.content ?? ""; expect(failureContent).toMatch(/service is down/i); + expect(await openAiEndpoint.getExchanges()).toHaveLength(2); await session.disconnect(); }); diff --git a/nodejs/test/e2e/tools.e2e.test.ts b/nodejs/test/e2e/tools.e2e.test.ts index c505f8aa8..7ca943aa7 100644 --- a/nodejs/test/e2e/tools.e2e.test.ts +++ b/nodejs/test/e2e/tools.e2e.test.ts @@ -7,7 +7,7 @@ import { join } from "path"; import { assert, describe, expect, it } from "vitest"; import { z } from "zod"; import { defineTool, approveAll, ToolSet } from "../../src/index.js"; -import type { PermissionRequest } from "../../src/index.js"; +import type { CopilotSession, PermissionRequest, SessionEvent } from "../../src/index.js"; import { createSdkTestContext } from "./harness/sdkTestContext"; describe("Custom tools", async () => { @@ -45,6 +45,46 @@ describe("Custom tools", async () => { expect(assistantMessage?.data.content).toContain("HELLO"); }); + it("clears context from a terminal tool and starts the seeded turn", async () => { + const seedPrompt = "Reply with exactly FRESH_CONTEXT."; + const events: SessionEvent[] = []; + let session: CopilotSession; + session = await client.createSession({ + onPermissionRequest: approveAll, + onEvent: (event) => events.push(event), + tools: [ + defineTool("clear_context", { + description: "Clears the conversation and starts a fresh context window", + parameters: z.object({ prompt: z.string() }), + isTerminal: true, + defer: "never", + handler: async () => { + const result = await session.rpc.history.clearContext({ + prompt: seedPrompt, + }); + return `Cleared ${result.messagesCleared} messages.`; + }, + }), + ], + }); + + const assistantMessage = await session.sendAndWait({ + prompt: `Call clear_context with prompt "${seedPrompt}" now.`, + }); + + expect(assistantMessage?.data.content).toContain("FRESH_CONTEXT"); + const contextCleared = events.find((event) => event.type === "session.context_cleared"); + expect(contextCleared).toBeDefined(); + if (contextCleared?.type === "session.context_cleared") { + expect(contextCleared.data.messagesCleared).toBeGreaterThan(0); + expect(contextCleared.data.initialMessage).toBe(seedPrompt); + } + + const traffic = await openAiEndpoint.getExchanges(); + expect(traffic).toHaveLength(2); + expect(JSON.stringify(traffic[1]?.request.messages)).toContain(seedPrompt); + }); + it("low_level_tool_definition", async () => { let currentPhase = ""; const session = await client.createSession({ diff --git a/nodejs/test/extension.test.ts b/nodejs/test/extension.test.ts index 1baa83a3a..e94ad2204 100644 --- a/nodejs/test/extension.test.ts +++ b/nodejs/test/extension.test.ts @@ -18,13 +18,13 @@ describe("joinSession", () => { it("defaults onPermissionRequest to no-result", async () => { process.env.SESSION_ID = "session-123"; - const resumeSession = vi - .spyOn(CopilotClient.prototype, "resumeSession") + const resumeForExtension = vi + .spyOn(CopilotClient.prototype, "resumeSessionForExtension") .mockResolvedValue({} as any); await joinSession({ tools: [] }); - const [, config] = resumeSession.mock.calls[0]!; + const [, config] = resumeForExtension.mock.calls[0]!; expect(config.onPermissionRequest).toBeDefined(); expect(config.onPermissionRequest).toBe(defaultJoinSessionPermissionHandler); const result = await Promise.resolve( @@ -36,13 +36,13 @@ describe("joinSession", () => { it("preserves an explicit onPermissionRequest handler", async () => { process.env.SESSION_ID = "session-123"; - const resumeSession = vi - .spyOn(CopilotClient.prototype, "resumeSession") + const resumeForExtension = vi + .spyOn(CopilotClient.prototype, "resumeSessionForExtension") .mockResolvedValue({} as any); await joinSession({ onPermissionRequest: approveAll, suppressResumeEvent: false }); - const [, config] = resumeSession.mock.calls[0]!; + const [, config] = resumeForExtension.mock.calls[0]!; expect(config.onPermissionRequest).toBe(approveAll); expect(config.suppressResumeEvent).toBe(false); }); diff --git a/nodejs/test/factory.test.ts b/nodejs/test/factory.test.ts new file mode 100644 index 000000000..3d85b972d --- /dev/null +++ b/nodejs/test/factory.test.ts @@ -0,0 +1,2396 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { readFileSync } from "node:fs"; +import { afterEach, describe, expect, it, onTestFinished, vi } from "vitest"; +import { ResponseError } from "vscode-jsonrpc/node.js"; +import { CopilotClient } from "../src/client.js"; +import { joinSession } from "../src/extension.js"; +import { CopilotSession } from "../src/session.js"; +import { + defineFactory, + FactoryResumeError, + isFactoryRunTerminal, + type FactoryAgentOptions, + type FactoryContext, + type FactoryDefinition, + type FactoryJsonSchema, + type JsonValue, +} from "../src/factory.js"; + +/** Builds a `factory.run_updated` invalidation event for a run. */ +function runUpdatedEvent(runId: string, revision: number): Record { + return { + type: "factory.run_updated", + id: `event-${runId}-${revision}`, + parentId: null, + timestamp: new Date().toISOString(), + ephemeral: true, + data: { runId, revision }, + }; +} + +async function stopClient(client: CopilotClient): Promise { + await client.stop(); +} + +describe("factories", () => { + const originalSessionId = process.env.SESSION_ID; + + afterEach(() => { + if (originalSessionId === undefined) { + delete process.env.SESSION_ID; + } else { + process.env.SESSION_ID = originalSessionId; + } + vi.restoreAllMocks(); + }); + + it("defines a stable handle and accepts omitted limits", async () => { + const meta = { + name: "no-limits", + description: "A factory without resource limits", + phases: [], + }; + const run = vi.fn(async ({ args }: { args: unknown }) => args); + const handle = defineFactory({ meta, run }); + + expect(handle.meta).toEqual(meta); + expect(handle.meta).not.toBe(meta); + expect(Object.isFrozen(handle)).toBe(true); + expect(Object.isFrozen(handle.meta)).toBe(true); + + // The handle holds a snapshot, so mutating the caller's object after + // registration cannot desynchronize the advertised metadata. + meta.name = "mutated"; + (meta.phases as string[]).push("late"); + expect(handle.meta.name).toBe("no-limits"); + expect(handle.meta.phases).toEqual([]); + meta.name = "no-limits"; + meta.phases.length = 0; + + // The stored metadata is deep-frozen, so the handle's view of it must be + // readonly all the way down. Assert both halves: the mutation is a type + // error, and it also throws at runtime. + expect(() => { + // @ts-expect-error handle.meta is deeply readonly. + handle.meta.name = "mutated"; + }).toThrow(TypeError); + expect(() => { + // @ts-expect-error handle.meta.phases is a readonly array. + handle.meta.phases.push({ title: "late" }); + }).toThrow(TypeError); + + const session = new CopilotSession("session-1", {} as never); + session.registerFactories([handle]); + const result = await session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: meta.name, + runId: "run-1", + executionToken: "execution-token", + args: { value: 42 }, + }); + + expect(run).toHaveBeenCalledOnce(); + expect(result).toEqual({ result: { value: 42 } }); + }); + + it.each([ + [[{ title: "" }], "must not be empty"], + [[{ title: "Inspect" }, { title: "Inspect" }], "declared more than once"], + ])("rejects invalid declared phase titles", (phases, message) => { + expect(() => + defineFactory({ + meta: { + name: "invalid-phases", + description: "Invalid phase metadata", + phases, + }, + run: async () => {}, + }) + ).toThrow(message); + }); + + it("returns an absent execute result for a void factory", async () => { + const factory = defineFactory({ + meta: { + name: "void-result", + description: "Returns no result", + phases: [], + }, + run: async () => {}, + }); + const session = new CopilotSession("session-void-result", {} as never); + session.registerFactories([factory]); + + await expect( + session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: "void-result", + runId: "run-void-result", + executionToken: "execution-token", + args: {}, + }) + ).resolves.toEqual({}); + }); + + it.each([42, "factory-result", [1, "two", false]])( + "returns non-object JSON factory result %j", + async (factoryResult) => { + const factory = defineFactory({ + meta: { + name: "json-result", + description: "Returns any JSON value", + phases: [], + }, + run: async () => factoryResult, + }); + const session = new CopilotSession("session-json-result", {} as never); + session.registerFactories([factory]); + + await expect( + session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: "json-result", + runId: "run-json-result", + executionToken: "execution-token", + args: {}, + }) + ).resolves.toEqual({ result: factoryResult }); + } + ); + + it.each([ + ["function", { nested: () => undefined }, "$.nested"], + ["symbol", [Symbol("invalid")], "$[0]"], + ["BigInt", { nested: 1n }, "$.nested"], + ])("rejects a %s anywhere in a factory result", async (_label, factoryResult, expectedPath) => { + const factory = defineFactory({ + meta: { + name: "unsupported-result", + description: "Returns an unsupported value", + phases: [], + }, + run: async () => factoryResult as never, + }); + const session = new CopilotSession("session-unsupported-result", {} as never); + session.registerFactories([factory]); + + await expect( + session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: "unsupported-result", + runId: "run-unsupported-result", + executionToken: "execution-token", + args: {}, + }) + ).rejects.toMatchObject({ + message: `Factory result contains a function, symbol, or BigInt at ${expectedPath}`, + data: { + code: "factory_result_not_json", + category: "unsupported_type", + }, + }); + }); + + it.each([ + ["NaN", Number.NaN], + ["Infinity", Number.POSITIVE_INFINITY], + ])("rejects the non-finite number %s in a factory result", async (_label, value) => { + const factory = defineFactory({ + meta: { + name: "non-finite-result", + description: "Returns a non-finite number", + phases: [], + }, + run: async () => ({ value }) as never, + }); + const session = new CopilotSession("session-non-finite-result", {} as never); + session.registerFactories([factory]); + + await expect( + session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: "non-finite-result", + runId: "run-non-finite-result", + executionToken: "execution-token", + args: {}, + }) + ).rejects.toMatchObject({ + message: "Factory result contains a non-finite number at $.value", + data: { + code: "factory_result_not_json", + category: "non_finite_number", + }, + }); + }); + + it("rejects a cyclic factory result", async () => { + const factoryResult: Record = {}; + factoryResult.self = factoryResult; + const factory = defineFactory({ + meta: { + name: "cyclic-result", + description: "Returns a cycle", + phases: [], + }, + run: async () => factoryResult as never, + }); + const session = new CopilotSession("session-cyclic-result", {} as never); + session.registerFactories([factory]); + + await expect( + session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: "cyclic-result", + runId: "run-cyclic-result", + executionToken: "execution-token", + args: {}, + }) + ).rejects.toMatchObject({ + message: "Factory result contains a cyclic reference at $.self", + data: { + code: "factory_result_not_json", + category: "cyclic_value", + }, + }); + }); + + it.each([ + ["object", { nested: undefined }, "$.nested"], + ["array", [undefined], "$[0]"], + ])( + "rejects nested undefined in a factory result %s", + async (_label, factoryResult, expectedPath) => { + const factory = defineFactory({ + meta: { + name: "nested-undefined-result", + description: "Returns nested undefined", + phases: [], + }, + run: async () => factoryResult as never, + }); + const session = new CopilotSession("session-nested-undefined-result", {} as never); + session.registerFactories([factory]); + + await expect( + session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: "nested-undefined-result", + runId: "run-nested-undefined-result", + executionToken: "execution-token", + args: {}, + }) + ).rejects.toMatchObject({ + message: `Factory result contains nested undefined at ${expectedPath}`, + data: { + code: "factory_result_not_json", + category: "nested_undefined", + }, + }); + } + ); + + it("rejects duplicate factory names within a single registration", () => { + const run = async () => null; + const first = defineFactory({ + meta: { name: "dup", description: "first", phases: [] }, + run, + }); + const second = defineFactory({ + meta: { name: "dup", description: "second", phases: [] }, + run, + }); + + const session = new CopilotSession("session-dup", {} as never); + expect(() => session.registerFactories([first, second])).toThrow( + /Duplicate factory name "dup"/ + ); + }); + + it.each([ + ["maxConcurrentSubagents", 0], + ["maxConcurrentSubagents", 1.5], + ["maxTotalSubagents", -1], + ["maxTotalSubagents", Number.POSITIVE_INFINITY], + ["timeoutSeconds", 0], + ["timeoutSeconds", Number.NaN], + ["timeoutSeconds", Number.POSITIVE_INFINITY], + ["maxAiCredits", 0], + ["maxAiCredits", Number.NaN], + ["maxAiCredits", Number.POSITIVE_INFINITY], + ["maxAiCredits", 0.000_000_000_4], + ["maxAiCredits", (Number.MAX_SAFE_INTEGER + 2) / 1_000_000_000], + ] as const)("rejects invalid %s limit %s", (field, value) => { + const definition = { + meta: { + name: `invalid-${field}-${String(value)}`, + description: "Invalid factory", + phases: [], + limits: { [field]: value }, + }, + run: async () => null, + } as FactoryDefinition; + + expect(() => defineFactory(definition)).toThrow(/must be a positive/); + }); + + it("accepts positive fractional timeoutSeconds through the Node timer ceiling", () => { + for (const timeoutSeconds of [0.001, 1.5, 2_147_483.647]) { + expect(() => + defineFactory({ + meta: { + name: `accepted-timeout-${timeoutSeconds}`, + description: "Factory with an accepted active-execution timeout", + phases: [], + limits: { timeoutSeconds }, + }, + run: async () => null, + }) + ).not.toThrow(); + } + }); + + it("accepts AI-credit ceilings that round to a positive safe nano-AIU integer", () => { + for (const maxAiCredits of [ + 0.000_000_000_5, + 1.25, + Number.MAX_SAFE_INTEGER / 1_000_000_000, + ]) { + expect(() => + defineFactory({ + meta: { + name: `accepted-credits-${maxAiCredits}`, + description: "Factory with an accepted AI-credit ceiling", + phases: [], + limits: { maxAiCredits }, + }, + run: async () => null, + }) + ).not.toThrow(); + } + }); + + it("rejects timeoutSeconds above the Node setTimeout ceiling", () => { + const definition = { + meta: { + name: "oversized-timeout", + description: "Factory with an out-of-range timeout", + phases: [], + limits: { timeoutSeconds: 2_147_483.648 }, + }, + run: async () => null, + } as FactoryDefinition; + + expect(() => defineFactory(definition)).toThrow( + 'Factory limit "timeoutSeconds" must not exceed 2147483.647 seconds' + ); + }); + + it("documents timeoutSeconds as accumulated active-execution time in public and generated types", () => { + const publicTypes = readFileSync(new URL("../src/types.ts", import.meta.url), "utf8"); + const generatedRpc = readFileSync( + new URL("../src/generated/rpc.ts", import.meta.url), + "utf8" + ); + + expect(publicTypes).toContain("Maximum accumulated active-execution time, in seconds."); + expect(publicTypes).toContain("subprocess waits, queued-agent waits, and sleeps"); + expect(publicTypes).toContain("timeoutSeconds?: number;"); + expect(generatedRpc).toContain("Maximum accumulated active-execution time in seconds."); + expect(generatedRpc).toContain("subprocess waits, queued-agent waits, and sleeps"); + expect(generatedRpc).toContain("timeoutSeconds?: number;"); + }); + + it("documents factory invocation and list paging behavior accurately", () => { + const guide = readFileSync(new URL("../docs/factories.md", import.meta.url), "utf8"); + const publicApi = readFileSync(new URL("../src/factory.ts", import.meta.url), "utf8"); + const listRunsPagingWording = "newest default page of this session's durable factory runs"; + const resumeCodes = [ + "not_found", + "non_resumable", + "already_active", + "factory_already_running", + "factory_limits_invalid", + "factory_session_disposed", + "factory_storage_unavailable", + "factory_storage_corrupt", + ]; + const normalizeJSDoc = (document: string) => + document.replace(/\r?\n\s*\* ?/g, " ").replace(/\s+/g, " "); + const normalizedGuide = normalizeJSDoc(guide); + const normalizedPublicApi = normalizeJSDoc(publicApi); + + for (const document of [guide, publicApi]) { + expect(document).not.toContain("reapproval_declined"); + expect(document).not.toContain("no_approval_provider"); + expect(document).not.toMatch(/declined fresh run[\s\S]*terminal `cancelled` envelope/i); + } + + for (const document of [normalizedGuide, normalizedPublicApi]) { + expect(document).toContain(listRunsPagingWording); + } + + expect(normalizedGuide).toContain( + "SDK-initiated `run` and `resume` do not request permission" + ); + expect(normalizedGuide).toContain( + "`run_factory` tool requests permission before the durable row exists" + ); + expect(normalizedGuide).toContain("declining it creates no run row"); + expect(normalizedGuide).toContain("its maximum number of active top-level runs"); + for (const code of resumeCodes) { + expect(guide).toContain(`\`${code}\``); + } + expect(guide).toContain( + "Options are exactly `label`, `schema`, `model`, `agent`, `reasoningEffort`, and `contextTier`" + ); + expect(normalizedGuide).toContain( + "session returned by `joinSession`. It refuses calls that start or resume a factory run" + ); + + expect(normalizedPublicApi).toContain("SDK-initiated runs do not request permission"); + expect(normalizedPublicApi).toContain("declining it creates no run row"); + expect(normalizedPublicApi).toContain( + "while the session is at its active top-level run limit" + ); + expect(normalizedPublicApi).toContain("SDK-initiated resumes do not request permission"); + expect(normalizedPublicApi).toContain("with a documented resume code rejects with"); + expect(normalizedPublicApi).toContain( + "session instance returned by `joinSession`. It refuses calls that start or resume a factory run" + ); + }); + + it("carries a declared argsSchema through defineFactory into the registration payload", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => stopClient(client)); + + const argsSchema = { + type: "object", + required: ["repoPath"], + properties: { + repoPath: { type: "string" }, + depth: { type: ["integer", "null"] }, + mode: { enum: ["fast", "thorough"] }, + }, + } satisfies FactoryJsonSchema; + const meta = { + name: "declares-args", + description: "Declares the argument shape it expects", + phases: [], + argsSchema, + }; + const factory = defineFactory({ meta, run: async () => ({ ok: true }) }); + + // The declaration is snapshotted and deep-frozen like the rest of the + // metadata, so it cannot be mutated after registration. + expect(factory.meta.argsSchema).toEqual(argsSchema); + expect(factory.meta.argsSchema).not.toBe(argsSchema); + expect(Object.isFrozen(factory.meta.argsSchema)).toBe(true); + expect(() => { + // @ts-expect-error handle.meta.argsSchema is deeply readonly. + factory.meta.argsSchema!.type = "array"; + }).toThrow(TypeError); + + const omitted = defineFactory({ + meta: { name: "omits-args", description: "Declares nothing", phases: [] }, + run: async () => ({ ok: true }), + }); + expect(omitted.meta.argsSchema).toBeUndefined(); + expect("argsSchema" in omitted.meta).toBe(false); + + const sendRequest = vi + .spyOn( + (client as never as { connection: { sendRequest: Function } }).connection, + "sendRequest" + ) + .mockImplementation(async (method: string, params: Record) => { + if (method === "session.resume") { + return { sessionId: params.sessionId }; + } + throw new Error(`Unexpected method: ${method}`); + }); + + await client.resumeSessionForExtension( + "session-args-schema", + { onPermissionRequest: () => ({ kind: "approved" }) }, + [factory, omitted] + ); + + const payload = sendRequest.mock.calls.find( + ([method]) => method === "session.resume" + )![1] as { factories: Array> }; + // The schema has to survive JSON serialization to reach the runtime, which + // validates `args` against it before a run row exists. + expect(JSON.parse(JSON.stringify(payload.factories))[0].argsSchema).toEqual(argsSchema); + expect(payload.factories[1]).not.toHaveProperty("argsSchema"); + }); + + it("documents argsSchema consistently with the runtime's enforced subset", () => { + const publicTypes = readFileSync(new URL("../src/types.ts", import.meta.url), "utf8"); + const publicApi = readFileSync(new URL("../src/factory.ts", import.meta.url), "utf8"); + const guide = readFileSync(new URL("../docs/factories.md", import.meta.url), "utf8"); + const normalizeJSDoc = (document: string) => + document.replace(/\r?\n\s*\* ?/g, " ").replace(/\s+/g, " "); + + expect(publicTypes).toContain("argsSchema?: FactoryJsonSchema;"); + + // The `run_factory` tool tells the model exactly this. The two surfaces + // have to agree about what a declaration does and does not enforce. + for (const document of [normalizeJSDoc(publicTypes), guide]) { + expect(document).toContain("types, required properties, and enum"); + expect(document).toMatch( + /`minLength`, `pattern`,? (?:and|or) `additionalProperties` are recorded/ + ); + } + expect(normalizeJSDoc(publicTypes)).toContain("before** the run starts"); + // Enforcement is tool-path only: `toolRunFactoryValidateArgs` is called from + // the runtime's runFactoryTool, and never from `session.factory.run`. Both + // surfaces must keep saying so, or authors will assume their own SDK-initiated + // runs are checked. + expect(normalizeJSDoc(publicTypes)).toContain( + "`session.factory.run(...)` is not validated against the declaration" + ); + expect(guide).toContain("Validation covers the model's `run_factory` path only"); + expect(normalizeJSDoc(publicApi)).toContain( + "`null`, `boolean`, `integer`, `number`, `string`, `array`, or `object`" + ); + expect(guide).toContain("no run row, permission prompt, or credit spend happens"); + }); + + it("serializes only factory metadata in the extension resume payload", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => stopClient(client)); + + const run = vi.fn(async () => ({ ok: true })); + const factory = defineFactory({ + meta: { + name: "registered", + description: "Registration test", + phases: [{ title: "Run" }], + limits: { maxTotalSubagents: 2 }, + }, + run, + }); + const sendRequest = vi + .spyOn( + (client as never as { connection: { sendRequest: Function } }).connection, + "sendRequest" + ) + .mockImplementation(async (method: string, params: Record) => { + if (method === "session.resume") { + const sessions = (client as never as { sessions: Map }) + .sessions; + expect( + sessions.get(params.sessionId as string)?.clientSessionApis.factory + ).toBeDefined(); + return { sessionId: params.sessionId }; + } + throw new Error(`Unexpected method: ${method}`); + }); + + await client.resumeSessionForExtension( + "session-registration", + { onPermissionRequest: () => ({ kind: "approved" }) }, + [factory] + ); + + const payload = sendRequest.mock.calls.find( + ([method]) => method === "session.resume" + )![1] as { + factories: unknown[]; + }; + expect(payload.factories).toEqual([factory.meta]); + expect(payload.factories[0]).not.toHaveProperty("run"); + expect(JSON.stringify(payload.factories)).not.toContain("async"); + }); + + it("passes factories only through the extension join path", async () => { + process.env.SESSION_ID = "session-extension"; + const factory = defineFactory({ + meta: { + name: "extension-only", + description: "Extension-only registration", + phases: [], + }, + run: async () => ({ ok: true }), + }); + const resumeSessionForExtension = vi + .spyOn(CopilotClient.prototype, "resumeSessionForExtension") + .mockResolvedValue({} as CopilotSession); + + await joinSession({ factories: [factory] }); + + expect(resumeSessionForExtension).toHaveBeenCalledWith( + "session-extension", + expect.objectContaining({ suppressResumeEvent: true }), + [factory] + ); + }); + + it("builds the factory context with the unrestricted joined session identity", async () => { + process.env.SESSION_ID = "session-context"; + const sendRequest = vi.fn(async (method: string) => { + if (method === "session.factory.log") { + return {}; + } + if (method === "session.tasks.list") { + return { tasks: [] }; + } + throw new Error(`Unexpected method: ${method}`); + }); + const joinedSession = new CopilotSession("session-context", { sendRequest } as never); + const contextSeen = Promise.withResolvers<{ + runId: string; + args: unknown; + session: CopilotSession; + signal: AbortSignal; + }>(); + const factory = defineFactory({ + meta: { + name: "context", + description: "Context test", + phases: [], + }, + run: async (context) => { + contextSeen.resolve(context); + context.phase("A"); + context.log("hi"); + const tasks = await context.session.rpc.tasks.list(); + return { ok: true, taskCount: tasks.tasks.length }; + }, + }); + vi.spyOn(CopilotClient.prototype, "resumeSessionForExtension").mockImplementation( + async (_sessionId, _config, factories) => { + joinedSession.registerFactories(factories); + return joinedSession; + } + ); + + const joinSessionResult = await joinSession({ factories: [factory] }); + const executeResult = await joinSessionResult.clientSessionApis.factory!.execute({ + sessionId: joinSessionResult.sessionId, + name: "context", + runId: "run-context", + executionToken: "execution-token", + args: { value: 42 }, + }); + const context = await contextSeen.promise; + + expect(context.runId).toBe("run-context"); + expect(context.args).toEqual({ value: 42 }); + expect(context.session).toBe(joinSessionResult); + expect(context.session.rpc).toBe(joinSessionResult.rpc); + expect(context.signal).toBeInstanceOf(AbortSignal); + expect(executeResult).toEqual({ result: { ok: true, taskCount: 0 } }); + expect(sendRequest).toHaveBeenCalledWith("session.tasks.list", { + sessionId: joinSessionResult.sessionId, + }); + expect(sendRequest).toHaveBeenCalledWith("session.factory.log", { + sessionId: joinSessionResult.sessionId, + runId: "run-context", + executionToken: "execution-token", + lines: [ + { seq: 0, kind: "phase", text: "A" }, + { seq: 1, kind: "log", text: "hi" }, + ], + }); + }); + + it("rejects nested factories without forwarding a runNested request", async () => { + const sendRequest = vi.fn(async () => { + throw new Error("Unexpected forward request"); + }); + const session = new CopilotSession("session-no-nesting", { sendRequest } as never); + const factory = defineFactory({ + meta: { + name: "no-nesting", + description: "Nested factory rejection test", + phases: [], + }, + run: async (context) => context.factory("nested", { value: 42 }), + }); + session.registerFactories([factory]); + + await expect( + session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: "no-nesting", + runId: "run-no-nesting", + executionToken: "execution-token", + args: {}, + }) + ).rejects.toThrow("nested factories are not supported"); + expect(sendRequest).not.toHaveBeenCalled(); + }); + + it("keeps factory reads and cancellation available inside a factory body", async () => { + const sendRequest = vi.fn(async (method: string) => { + switch (method) { + case "session.factory.getRun": + return { runId: "other-run", status: "completed" }; + case "session.factory.listRuns": + return { runs: [] }; + case "session.factory.cancel": + return {}; + default: + throw new Error(`Unexpected method: ${method}`); + } + }); + const session = new CopilotSession("session-factory-reads", { sendRequest } as never); + const factory = defineFactory({ + meta: { + name: "factory-reads", + description: "Read factory state from a factory body", + phases: [], + }, + run: async ({ session: contextSession }) => { + const [run, runs] = await Promise.all([ + contextSession.factory.getRun("other-run"), + contextSession.factory.listRuns(), + contextSession.factory.cancel("other-run"), + ]); + return { runId: run.runId, runCount: runs.length }; + }, + }); + session.registerFactories([factory]); + + await expect( + session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: "factory-reads", + runId: "run-factory-reads", + executionToken: "execution-token", + args: {}, + }) + ).resolves.toEqual({ result: { runId: "other-run", runCount: 0 } }); + expect(sendRequest).toHaveBeenCalledWith("session.factory.getRun", { + sessionId: session.sessionId, + runId: "other-run", + }); + expect(sendRequest).toHaveBeenCalledWith("session.factory.listRuns", { + sessionId: session.sessionId, + }); + expect(sendRequest).toHaveBeenCalledWith("session.factory.cancel", { + sessionId: session.sessionId, + runId: "other-run", + }); + }); + + it("allows factory.run after a factory body returns", async () => { + const sendRequest = vi.fn(async (method: string) => { + if (method === "session.factory.run") { + return { runId: "run-after-body", status: "completed", result: "started" }; + } + throw new Error(`Unexpected method: ${method}`); + }); + const session = new CopilotSession("session-after-body", { sendRequest } as never); + const factory = defineFactory({ + meta: { + name: "returns", + description: "Return before a separate factory run", + phases: [], + }, + run: async () => "finished", + }); + session.registerFactories([factory]); + + await expect( + session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: "returns", + runId: "run-returns", + executionToken: "execution-token", + args: {}, + }) + ).resolves.toEqual({ result: "finished" }); + await expect(session.factory.run("after-body")).resolves.toMatchObject({ + status: "completed", + result: "started", + }); + }); + + it("allows a factory-body timer to start a factory after the body settles", async () => { + const delayedRun = Promise.withResolvers(); + const sendRequest = vi.fn(async (method: string) => { + if (method === "session.factory.run") { + return { runId: "run-from-timer", status: "completed", result: "started" }; + } + throw new Error(`Unexpected method: ${method}`); + }); + const session = new CopilotSession("session-timer", { sendRequest } as never); + const factory = defineFactory({ + meta: { + name: "timer", + description: "Start a factory from an unawaited timer", + phases: [], + }, + run: async () => { + setTimeout(() => { + void session.factory + .run("from-timer") + .then(delayedRun.resolve, delayedRun.reject); + }, 0); + return "finished"; + }, + }); + session.registerFactories([factory]); + + await expect( + session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: "timer", + runId: "run-timer", + executionToken: "execution-token", + args: {}, + }) + ).resolves.toEqual({ result: "finished" }); + await expect(delayedRun.promise).resolves.toMatchObject({ + status: "completed", + result: "started", + }); + }); + + it("flushes progress incrementally while a factory body is awaiting", async () => { + const sendRequest = vi.fn(async () => ({})); + const session = new CopilotSession("session-live-progress", { sendRequest } as never); + const body = Promise.withResolvers(); + const factory = defineFactory({ + meta: { + name: "live-progress", + description: "Incremental progress test", + phases: [], + }, + run: async ({ log }) => { + log("before await"); + await body.promise; + return "done"; + }, + }); + session.registerFactories([factory]); + + const execution = session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: "live-progress", + runId: "run-live-progress", + executionToken: "execution-token", + args: {}, + }); + await vi.waitFor(() => { + expect(sendRequest).toHaveBeenCalledWith("session.factory.log", { + sessionId: session.sessionId, + runId: "run-live-progress", + executionToken: "execution-token", + lines: [{ seq: 0, kind: "log", text: "before await" }], + }); + }); + + body.resolve(); + await expect(execution).resolves.toEqual({ result: "done" }); + }); + + it("calls factory.agent with the current run id and returns its text", async () => { + const sendRequest = vi.fn(async (method: string) => { + if (method === "session.factory.agent") { + return { result: "pong" }; + } + throw new Error(`Unexpected method: ${method}`); + }); + const session = new CopilotSession("session-agent", { sendRequest } as never); + const factory = defineFactory({ + meta: { + name: "agent", + description: "Agent context test", + phases: [], + }, + run: async ({ agent }) => + agent("Reply with pong", { + label: "Pong helper", + model: "gpt-test", + schema: { type: "string" }, + effort: "high", + } as FactoryAgentOptions), + }); + session.registerFactories([factory]); + + await expect( + session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: "agent", + runId: "run-agent", + executionToken: "execution-token", + args: {}, + }) + ).resolves.toEqual({ result: "pong" }); + expect(sendRequest).toHaveBeenCalledWith("session.factory.agent", { + sessionId: session.sessionId, + factoryRunId: "run-agent", + executionToken: "execution-token", + prompt: "Reply with pong", + opts: { + label: "Pong helper", + model: "gpt-test", + schema: { type: "string" }, + }, + }); + }); + + it("forwards every declared factory.agent option", async () => { + const sendRequest = vi.fn(async (method: string) => { + if (method === "session.factory.agent") { + return { result: "pong" }; + } + throw new Error(`Unexpected method: ${method}`); + }); + const session = new CopilotSession("session-agent-options", { sendRequest } as never); + const factory = defineFactory({ + meta: { + name: "agent-options", + description: "Agent option forwarding test", + phases: [], + }, + run: async ({ agent }) => + agent("Reply with pong", { + label: "Pong helper", + model: "gpt-test", + schema: { type: "string" }, + agent: "reviewer", + reasoningEffort: "high", + contextTier: "long_context", + }), + }); + session.registerFactories([factory]); + + await expect( + session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: "agent-options", + runId: "run-agent-options", + executionToken: "execution-token", + args: {}, + }) + ).resolves.toEqual({ result: "pong" }); + expect(sendRequest).toHaveBeenCalledWith("session.factory.agent", { + sessionId: session.sessionId, + factoryRunId: "run-agent-options", + executionToken: "execution-token", + prompt: "Reply with pong", + opts: { + label: "Pong helper", + model: "gpt-test", + schema: { type: "string" }, + agent: "reviewer", + reasoningEffort: "high", + contextTier: "long_context", + }, + }); + }); + + it("sends empty factory.agent options when none are supplied", async () => { + const sendRequest = vi.fn(async (method: string) => { + if (method === "session.factory.agent") { + return { result: "pong" }; + } + throw new Error(`Unexpected method: ${method}`); + }); + const session = new CopilotSession("session-empty-agent-options", { + sendRequest, + } as never); + const factory = defineFactory({ + meta: { + name: "empty-agent-options", + description: "Empty agent option forwarding test", + phases: [], + }, + run: async ({ agent }) => agent("Reply with pong"), + }); + session.registerFactories([factory]); + + await expect( + session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: "empty-agent-options", + runId: "run-empty-agent-options", + executionToken: "execution-token", + args: {}, + }) + ).resolves.toEqual({ result: "pong" }); + expect(sendRequest).toHaveBeenCalledWith("session.factory.agent", { + sessionId: session.sessionId, + factoryRunId: "run-empty-agent-options", + executionToken: "execution-token", + prompt: "Reply with pong", + opts: {}, + }); + }); + + it("keeps each execution token on callbacks from overlapping contexts with the same run id", async () => { + const sendRequest = vi.fn(async (method: string) => { + if (method === "session.factory.agent") { + return { result: "agent result" }; + } + if (method === "session.factory.journal.get") { + return { hit: false }; + } + return {}; + }); + const session = new CopilotSession("session-overlapping-attempts", { + sendRequest, + } as never); + const contexts: FactoryContext[] = []; + const bodies = [Promise.withResolvers(), Promise.withResolvers()]; + const contextsReady = Promise.withResolvers(); + const factory = defineFactory({ + meta: { + name: "overlapping-attempts", + description: "Execution token capture test", + phases: [], + }, + run: async (context) => { + const invocation = contexts.length; + contexts.push(context); + if (contexts.length === 2) { + contextsReady.resolve(); + } + await bodies[invocation].promise; + return `attempt ${invocation + 1}`; + }, + }); + session.registerFactories([factory]); + const first = session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: "overlapping-attempts", + runId: "shared-run", + executionToken: "old-token", + args: {}, + }); + const second = session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: "overlapping-attempts", + runId: "shared-run", + executionToken: "current-token", + args: {}, + }); + await contextsReady.promise; + + contexts[0].log("stale log"); + await contexts[0].agent("stale agent"); + await contexts[0].step("stale journal", () => "stale result"); + await contexts[1].agent("current agent"); + + expect(sendRequest).toHaveBeenCalledWith( + "session.factory.log", + expect.objectContaining({ executionToken: "old-token" }) + ); + expect(sendRequest).toHaveBeenCalledWith( + "session.factory.agent", + expect.objectContaining({ executionToken: "old-token", prompt: "stale agent" }) + ); + expect(sendRequest).toHaveBeenCalledWith( + "session.factory.journal.get", + expect.objectContaining({ executionToken: "old-token", key: "stale journal" }) + ); + expect(sendRequest).toHaveBeenCalledWith( + "session.factory.journal.put", + expect.objectContaining({ executionToken: "old-token", key: "stale journal" }) + ); + expect(sendRequest).toHaveBeenCalledWith( + "session.factory.agent", + expect.objectContaining({ executionToken: "current-token", prompt: "current agent" }) + ); + + bodies[0].resolve(); + bodies[1].resolve(); + await expect(first).resolves.toEqual({ result: "attempt 1" }); + await expect(second).resolves.toEqual({ result: "attempt 2" }); + }); + + it("runs a durable step once, serves cached null, and does not cache failures", async () => { + const journal = new Map(); + const sendRequest = vi.fn( + async (method: string, params: { key?: string; resultJson?: unknown }) => { + if (method === "session.factory.journal.get") { + return journal.has(params.key!) + ? { hit: true, resultJson: journal.get(params.key!) } + : { hit: false }; + } + if (method === "session.factory.journal.put") { + journal.set(params.key!, params.resultJson); + return {}; + } + throw new Error(`Unexpected method: ${method}`); + } + ); + const session = new CopilotSession("session-step", { sendRequest } as never); + let cachedProducerCalls = 0; + let failingProducerCalls = 0; + const factory = defineFactory({ + meta: { + name: "step", + description: "Durable step context test", + phases: [], + }, + run: async ({ step }) => { + const first = await step("cached-null", async () => { + cachedProducerCalls++; + return null; + }); + const second = await step("cached-null", async () => { + cachedProducerCalls++; + return "wrong"; + }); + const failed = await step("retry", async () => { + failingProducerCalls++; + throw new Error("transient"); + }).catch(() => "failed"); + const retried = await step("retry", async () => { + failingProducerCalls++; + return "recovered"; + }); + return { first, second, failed, retried }; + }, + }); + session.registerFactories([factory]); + + await expect( + session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: "step", + runId: "run-step", + executionToken: "execution-token", + args: {}, + }) + ).resolves.toEqual({ + result: { first: null, second: null, failed: "failed", retried: "recovered" }, + }); + expect(cachedProducerCalls).toBe(1); + expect(failingProducerCalls).toBe(2); + expect( + sendRequest.mock.calls.filter(([method]) => method === "session.factory.journal.put") + ).toHaveLength(2); + }); + + it.each([ + ["undefined", () => undefined], + ["NaN", () => Number.NaN], + ["Infinity", () => Number.POSITIVE_INFINITY], + ["function", () => () => undefined], + ["symbol", () => Symbol("invalid")], + ["BigInt", () => 1n], + [ + "cycle", + () => { + const value: Record = {}; + value.self = value; + return value; + }, + ], + ["non-plain object", () => new Date()], + [ + "accessor property", + () => Object.defineProperty({}, "value", { enumerable: true, get: () => "hidden" }), + ], + [ + "non-enumerable property", + () => Object.defineProperty({}, "value", { enumerable: false, value: "hidden" }), + ], + ["array hole", () => new Array(1)], + [ + "array accessor", + () => Object.defineProperty([], "0", { enumerable: true, get: () => "hidden" }), + ], + ["array extra key", () => Object.assign([1], { extra: "dropped" })], + ])("rejects a journaled step %s result", async (_label, makeValue) => { + const sendRequest = vi.fn(async (method: string) => { + if (method === "session.factory.journal.get") { + return { hit: false }; + } + throw new Error(`Unexpected method: ${method}`); + }); + const session = new CopilotSession("session-invalid-step", { sendRequest } as never); + const factory = defineFactory({ + meta: { + name: "invalid-step", + description: "Rejects lossy step values", + phases: [], + }, + run: async ({ step }) => { + await step("invalid", async () => makeValue() as never); + return "must-not-complete"; + }, + }); + session.registerFactories([factory]); + + await expect( + session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: "invalid-step", + runId: "run-invalid-step", + executionToken: "execution-token", + args: {}, + }) + ).rejects.toMatchObject({ + data: { + code: "factory_step_not_json", + }, + }); + expect( + sendRequest.mock.calls.filter(([method]) => method === "session.factory.journal.put") + ).toHaveLength(0); + }); + + it("validates a journaled step cache hit before replay", async () => { + const cached = Object.assign([1], { extra: "dropped" }); + const producer = vi.fn(async () => "must-not-run"); + const sendRequest = vi.fn(async (method: string) => { + if (method === "session.factory.journal.get") { + return { hit: true, resultJson: cached }; + } + throw new Error(`Unexpected method: ${method}`); + }); + const session = new CopilotSession("session-invalid-step-cache", { sendRequest } as never); + const factory = defineFactory({ + meta: { + name: "invalid-step-cache", + description: "Rejects invalid cached values", + phases: [], + }, + run: async ({ step }) => step("cached", producer), + }); + session.registerFactories([factory]); + + await expect( + session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: "invalid-step-cache", + runId: "run-invalid-step-cache", + executionToken: "execution-token", + args: {}, + }) + ).rejects.toMatchObject({ + data: { + code: "factory_step_not_json", + category: "unsupported_object", + }, + }); + expect(producer).not.toHaveBeenCalled(); + }); + + it("replays a journaled step value identically on resume", async () => { + const journal = new Map(); + const sendRequest = vi.fn( + async (method: string, params: { key?: string; resultJson?: unknown }) => { + if (method === "session.factory.journal.get") { + return journal.has(params.key!) + ? { hit: true, resultJson: journal.get(params.key!) } + : { hit: false }; + } + if (method === "session.factory.journal.put") { + journal.set(params.key!, params.resultJson); + return {}; + } + throw new Error(`Unexpected method: ${method}`); + } + ); + const producer = vi.fn(async () => ({ nested: [1, null, "same"] })); + const session = new CopilotSession("session-step-replay", { sendRequest } as never); + const factory = defineFactory({ + meta: { + name: "step-replay", + description: "Replays strict JSON", + phases: [], + }, + run: async ({ step }) => step("same", producer), + }); + session.registerFactories([factory]); + + const first = await session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: "step-replay", + runId: "run-step-replay", + executionToken: "execution-token", + args: {}, + }); + const replay = await session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: "step-replay", + runId: "run-step-replay", + executionToken: "execution-token", + args: {}, + }); + + expect(replay).toEqual(first); + expect(producer).toHaveBeenCalledOnce(); + }); + + it("bypasses validation and journaling for a volatile step", async () => { + const sendRequest = vi.fn(); + const session = new CopilotSession("session-volatile-step", { sendRequest } as never); + const factory = defineFactory({ + meta: { + name: "volatile-step", + description: "Allows author-opted-out volatile values", + phases: [], + }, + run: async ({ step }) => { + const value = await step("volatile", async () => (() => "not JSON") as never, { + volatile: true, + }); + expect(typeof value).toBe("function"); + return "completed"; + }, + }); + session.registerFactories([factory]); + + await expect( + session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: "volatile-step", + runId: "run-volatile-step", + executionToken: "execution-token", + args: {}, + }) + ).resolves.toEqual({ result: "completed" }); + expect(sendRequest).not.toHaveBeenCalled(); + }); + + it("does not start a volatile step producer after the run is aborted", async () => { + const sendRequest = vi.fn(); + const session = new CopilotSession("session-volatile-abort", { sendRequest } as never); + let producerRan = false; + const factory = defineFactory({ + meta: { + name: "volatile-abort", + description: "Volatile steps honour cancellation", + phases: [], + }, + run: async ({ step, runId }) => { + // Abort mid-run, then attempt a volatile step. The producer must + // not run: cancellation has to stop new extension work starting, + // exactly as it does on the journaled path. + await session.clientSessionApis.factory!.abort({ + sessionId: session.sessionId, + runId, + }); + await step( + "volatile", + () => { + producerRan = true; + return "should not happen"; + }, + { volatile: true } + ); + return "completed"; + }, + }); + session.registerFactories([factory]); + + await expect( + session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: "volatile-abort", + runId: "run-volatile-abort", + executionToken: "execution-token", + args: {}, + }) + ).rejects.toThrow(); + expect(producerRan).toBe(false); + }); + + it("rejects a factory result array with an extra own key", async () => { + const factory = defineFactory({ + meta: { + name: "array-extra-result", + description: "Rejects lossy array keys", + phases: [], + }, + run: async () => Object.assign([1], { extra: 1n }) as never, + }); + const session = new CopilotSession("session-array-extra-result", {} as never); + session.registerFactories([factory]); + + await expect( + session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: "array-extra-result", + runId: "run-array-extra-result", + executionToken: "execution-token", + args: {}, + }) + ).rejects.toMatchObject({ + data: { + code: "factory_result_not_json", + category: "unsupported_object", + }, + }); + }); + + it("exposes factory getRun and forwards the run id", async () => { + const envelope = { runId: "run-read", status: "error", error: "failed" }; + const sendRequest = vi.fn(async () => envelope); + const session = new CopilotSession("session-read", { sendRequest } as never); + + await expect(session.factory.getRun("run-read")).resolves.toEqual(envelope); + expect(sendRequest).toHaveBeenCalledWith("session.factory.getRun", { + sessionId: session.sessionId, + runId: "run-read", + }); + }); + + it("exposes factory observability methods and forwards paging options", async () => { + const summary = { + runId: "run-observe", + factoryName: "observe", + description: "Observe", + status: "running" as const, + revision: 4, + createdAt: 1, + startedAt: 2, + updatedAt: 3, + completedAt: null, + currentPhase: { id: "p0", ordinal: 0 }, + declaredPhaseCount: 1, + liveAgentCount: 1, + totalSpawnedAgentCount: 1, + consumed: { activeMs: 10, subagents: 1, nanoAiu: 5 }, + declaredLimits: {}, + approved: {}, + observedAt: 4, + activeSegmentStartedAt: 2, + terminal: null, + }; + const progress = { + records: [], + oldestSeq: null, + newestSeq: null, + hasMoreOlder: false, + hasMoreNewer: false, + revision: 4, + }; + const detail = { ...summary, phases: [], agents: [], progress }; + const sendRequest = vi.fn(async (method: string) => { + if (method === "session.factory.listRuns") return { runs: [summary] }; + if (method === "session.factory.getRunDetail") return detail; + return progress; + }); + const session = new CopilotSession("session-observe", { sendRequest } as never); + + await expect(session.factory.listRuns()).resolves.toEqual([summary]); + await expect(session.factory.getRunDetail("run-observe")).resolves.toEqual(detail); + await expect( + session.factory.getRunProgress("run-observe", { + phaseId: "p0", + afterSeq: 10, + limit: 50, + }) + ).resolves.toEqual(progress); + expect(sendRequest).toHaveBeenNthCalledWith(1, "session.factory.listRuns", { + sessionId: session.sessionId, + }); + expect(sendRequest).toHaveBeenNthCalledWith(2, "session.factory.getRunDetail", { + sessionId: session.sessionId, + runId: "run-observe", + }); + expect(sendRequest).toHaveBeenNthCalledWith(3, "session.factory.getRunProgress", { + sessionId: session.sessionId, + runId: "run-observe", + phaseId: "p0", + afterSeq: 10, + limit: 50, + }); + }); + + it("exposes factory cancel and forwards the run id", async () => { + const envelope = { runId: "run-cancel", status: "cancelled", reason: "cancelled" }; + const sendRequest = vi.fn(async () => envelope); + const session = new CopilotSession("session-cancel", { sendRequest } as never); + + await expect(session.factory.cancel("run-cancel")).resolves.toEqual(envelope); + expect(sendRequest).toHaveBeenCalledWith("session.factory.cancel", { + sessionId: session.sessionId, + runId: "run-cancel", + }); + }); + + it("runs parallel as a barrier and maps a throwing thunk to null", async () => { + const first = Promise.withResolvers(); + const second = Promise.withResolvers(); + const started: string[] = []; + const session = new CopilotSession("session-parallel", {} as never); + const factory = defineFactory({ + meta: { + name: "parallel", + description: "Parallel combinator test", + phases: [], + }, + run: async ({ parallel }) => + parallel([ + async () => { + started.push("first"); + return first.promise; + }, + async () => { + started.push("second"); + return second.promise; + }, + async () => { + started.push("throwing"); + throw new Error("expected"); + }, + ]), + }); + session.registerFactories([factory]); + + let settled = false; + const execution = session.clientSessionApis + .factory!.execute({ + sessionId: session.sessionId, + name: "parallel", + runId: "run-parallel", + args: {}, + }) + .finally(() => { + settled = true; + }); + await vi.waitFor(() => expect(started).toEqual(["first", "second", "throwing"])); + + second.resolve("second"); + await Promise.resolve(); + expect(settled).toBe(false); + + first.resolve("first"); + await expect(execution).resolves.toEqual({ result: ["first", "second", null] }); + }); + + it("rejects already-invoked promises passed to parallel with a clear diagnostic", async () => { + const session = new CopilotSession("session-parallel-promises", {} as never); + const factory = defineFactory({ + meta: { + name: "parallel-promises", + description: "Parallel misuse diagnostic", + phases: [], + }, + run: async ({ parallel }) => + parallel([Promise.resolve("already running")] as unknown as Array< + () => Promise + >), + }); + session.registerFactories([factory]); + + await expect( + session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: "parallel-promises", + runId: "run-parallel-promises", + executionToken: "execution-token", + args: {}, + }) + ).rejects.toThrow( + "parallel() expects an array of functions, not promises. Wrap each call: () => agent(...)" + ); + }); + + it("flows pipeline items independently and drops only the item whose stage throws", async () => { + const releaseFirstItem = Promise.withResolvers(); + const secondStageStarted = Promise.withResolvers(); + const finalStageItems: string[] = []; + const session = new CopilotSession("session-pipeline", {} as never); + const factory = defineFactory({ + meta: { + name: "pipeline", + description: "Pipeline combinator test", + phases: [], + }, + run: async ({ pipeline }) => + pipeline( + ["slow", "fast", "throw"], + async (_previous, item) => { + if (item === "slow") { + await releaseFirstItem.promise; + } + if (item === "throw") { + throw new Error("expected"); + } + return `${item}-stage-1`; + }, + async (previous, item) => { + if (item === "fast") { + secondStageStarted.resolve(); + } + finalStageItems.push(item as string); + return `${previous}-stage-2`; + } + ), + }); + session.registerFactories([factory]); + + const execution = session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: "pipeline", + runId: "run-pipeline", + executionToken: "execution-token", + args: {}, + }); + await secondStageStarted.promise; + expect(finalStageItems).toEqual(["fast"]); + + releaseFirstItem.resolve(); + await expect(execution).resolves.toEqual({ + result: ["slow-stage-1-stage-2", "fast-stage-1-stage-2", null], + }); + expect(finalStageItems).toEqual(["fast", "slow"]); + }); + + it("enforces the 4096-item cap for parallel and pipeline", async () => { + const session = new CopilotSession("session-fanout-cap", {} as never); + const factory = defineFactory({ + meta: { + name: "fanout-cap", + description: "Fan-out cap test", + phases: [], + }, + run: async ({ parallel, pipeline }) => { + const tooManyItems = Array.from({ length: 4097 }, () => null); + const parallelError = await parallel( + tooManyItems.map(() => async () => null) + ).catch((error: unknown) => error); + const pipelineError = await pipeline(tooManyItems).catch((error: unknown) => error); + return { + parallel: (parallelError as Error).message, + pipeline: (pipelineError as Error).message, + }; + }, + }); + session.registerFactories([factory]); + + await expect( + session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: "fanout-cap", + runId: "run-fanout-cap", + executionToken: "execution-token", + args: {}, + }) + ).resolves.toEqual({ + result: { + parallel: "parallel() accepts at most 4096 items; got 4097.", + pipeline: "pipeline() accepts at most 4096 items; got 4097.", + }, + }); + }); + + it("does not deadlock nested combinators when only leaf agents use a one-slot limiter", async () => { + let active = 0; + let maxActive = 0; + let tail = Promise.resolve(); + const sendRequest = vi.fn( + async (method: string, params: { prompt: string }): Promise<{ result: string }> => { + if (method !== "session.factory.agent") { + throw new Error(`Unexpected method: ${method}`); + } + const previous = tail; + const done = Promise.withResolvers(); + tail = done.promise; + await previous; + active++; + maxActive = Math.max(maxActive, active); + await Promise.resolve(); + active--; + done.resolve(); + return { result: params.prompt }; + } + ); + const session = new CopilotSession("session-nested-combinators", { + sendRequest, + } as never); + const factory = defineFactory({ + meta: { + name: "nested-combinators", + description: "Nested combinator deadlock regression", + phases: [], + }, + run: async ({ agent, parallel, pipeline }) => + parallel([ + () => parallel([() => agent("a"), () => agent("b")]), + () => pipeline(["c"], (_previous, item) => agent(item as string)), + ]), + }); + session.registerFactories([factory]); + + await expect( + session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: "nested-combinators", + runId: "run-nested-combinators", + executionToken: "execution-token", + args: {}, + }) + ).resolves.toEqual({ result: [["a", "b"], ["c"]] }); + expect(maxActive).toBe(1); + expect(sendRequest).toHaveBeenCalledTimes(3); + }); + + it("flushes buffered progress in finally when the factory body throws", async () => { + const sendRequest = vi.fn(async () => ({})); + const session = new CopilotSession("session-throw-progress", { sendRequest } as never); + const factory = defineFactory({ + meta: { + name: "throw-progress", + description: "Throwing progress test", + phases: [], + }, + run: async ({ log }) => { + log("before throw"); + throw new Error("body failed"); + }, + }); + session.registerFactories([factory]); + + await expect( + session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: "throw-progress", + runId: "run-throw-progress", + executionToken: "execution-token", + args: {}, + }) + ).rejects.toThrow("body failed"); + expect(sendRequest).toHaveBeenCalledWith("session.factory.log", { + sessionId: session.sessionId, + runId: "run-throw-progress", + executionToken: "execution-token", + lines: [{ seq: 0, kind: "log", text: "before throw" }], + }); + }); + + it("keeps a completed execution successful when only the final progress flush fails", async () => { + const sendRequest = vi.fn(async (method: string) => { + if (method === "session.factory.log") { + throw new Error("final transport failure"); + } + return {}; + }); + const warning = vi.spyOn(console, "warn").mockImplementation(() => {}); + const session = new CopilotSession("session-final-flush-failure", { + sendRequest, + } as never); + const factory = defineFactory({ + meta: { + name: "final-flush-failure", + description: "Final flush failure regression test", + phases: [], + }, + run: async ({ log }) => { + log("final line"); + return "done"; + }, + }); + session.registerFactories([factory]); + + await expect( + session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: "final-flush-failure", + runId: "run-final-flush-failure", + executionToken: "execution-token", + args: {}, + }) + ).resolves.toEqual({ result: "done" }); + expect(warning).toHaveBeenCalledWith( + "Failed to flush final factory progress after the factory body settled", + expect.objectContaining({ message: "final transport failure" }) + ); + }); + + it("keeps a completed execution successful when a background progress flush fails", async () => { + vi.useFakeTimers(); + const release = Promise.withResolvers(); + const sendRequest = vi.fn(async (method: string) => { + if (method === "session.factory.log") { + throw new Error("background transport failure"); + } + return {}; + }); + const warning = vi.spyOn(console, "warn").mockImplementation(() => {}); + const session = new CopilotSession("session-background-flush-failure", { + sendRequest, + } as never); + const factory = defineFactory({ + meta: { + name: "background-flush-failure", + description: "Background flush failure regression test", + phases: [], + }, + run: async ({ log }) => { + log("background line"); + await release.promise; + return "done"; + }, + }); + session.registerFactories([factory]); + + try { + const execution = session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: "background-flush-failure", + runId: "run-background-flush-failure", + executionToken: "execution-token", + args: {}, + }); + await vi.advanceTimersByTimeAsync(10_000); + await Promise.resolve(); + + release.resolve(); + + await expect(execution).resolves.toEqual({ result: "done" }); + expect(warning).toHaveBeenCalledWith( + "Ignoring a background factory progress flush failure after the factory body settled", + expect.objectContaining({ message: "background transport failure" }) + ); + } finally { + vi.useRealTimers(); + } + }); + + it("keeps a mid-run progress flush failure fatal", async () => { + const sendRequest = vi.fn(async (method: string) => { + if (method === "session.factory.log") { + throw new Error("mid-run transport failure"); + } + if (method === "session.factory.agent") { + return { result: "must not complete" }; + } + return {}; + }); + const session = new CopilotSession("session-mid-run-flush-failure", { + sendRequest, + } as never); + const factory = defineFactory({ + meta: { + name: "mid-run-flush-failure", + description: "Mid-run flush failure regression test", + phases: [], + }, + run: async ({ agent, log }) => { + log("before agent"); + return agent("trigger a flush"); + }, + }); + session.registerFactories([factory]); + + await expect( + session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: "mid-run-flush-failure", + runId: "run-mid-run-flush-failure", + executionToken: "execution-token", + args: {}, + }) + ).rejects.toThrow("mid-run transport failure"); + expect(sendRequest).not.toHaveBeenCalledWith("session.factory.agent", expect.anything()); + }); + + it("surfaces the per-run abort signal on the factory context", async () => { + const session = new CopilotSession("session-abort-signal", {} as never); + const signalSeen = Promise.withResolvers(); + const factory = defineFactory({ + meta: { + name: "abort-signal", + description: "Abort signal test", + phases: [], + }, + run: async ({ signal }) => { + signalSeen.resolve(signal); + await new Promise((resolve) => + signal.addEventListener("abort", () => resolve(), { once: true }) + ); + return signal.aborted; + }, + }); + session.registerFactories([factory]); + + const execution = session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: "abort-signal", + runId: "run-abort-signal", + executionToken: "execution-token", + args: {}, + }); + const signal = await signalSeen.promise; + expect(signal.aborted).toBe(false); + + await session.clientSessionApis.factory!.abort({ + sessionId: session.sessionId, + runId: "run-abort-signal", + }); + + expect(signal.aborted).toBe(true); + await expect(execution).resolves.toEqual({ result: true }); + }); + + it("rejects an in-flight runtime-backed await when factory.abort trips the signal", async () => { + const agentResponse = Promise.withResolvers<{ result: string }>(); + const sendRequest = vi.fn(async (method: string) => { + if (method === "session.factory.agent") { + return agentResponse.promise; + } + return {}; + }); + const session = new CopilotSession("session-abort-await", { sendRequest } as never); + const factory = defineFactory({ + meta: { + name: "abort-await", + description: "Abort an in-flight factory await", + phases: [], + }, + run: async ({ agent }) => agent("wait forever"), + }); + session.registerFactories([factory]); + + const execution = session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: "abort-await", + runId: "run-abort-await", + executionToken: "execution-token", + args: {}, + }); + await vi.waitFor(() => + expect(sendRequest).toHaveBeenCalledWith("session.factory.agent", expect.anything()) + ); + + await session.clientSessionApis.factory!.abort({ + sessionId: session.sessionId, + runId: "run-abort-await", + }); + + await expect(execution).rejects.toMatchObject({ name: "AbortError" }); + agentResponse.resolve({ result: "late" }); + }); + + it.each(["parallel", "pipeline"] as const)( + "propagates cancellation out of %s instead of mapping it to null", + async (combinator) => { + const agentResponse = Promise.withResolvers<{ result: string }>(); + const sendRequest = vi.fn(async (method: string) => { + if (method === "session.factory.agent") { + return agentResponse.promise; + } + return {}; + }); + const session = new CopilotSession("session-abort-parallel", { sendRequest } as never); + const factory = defineFactory({ + meta: { + name: `abort-${combinator}`, + description: "Cancellation must bubble out of a combinator", + phases: [], + }, + // If the combinator swallowed the AbortError to null, this run would + // resolve successfully with [null] despite the run being cancelled. + run: async ({ agent, parallel, pipeline }) => + combinator === "parallel" + ? parallel([() => agent("wait forever")]) + : pipeline(["wait forever"], (_previous, item) => agent(item as string)), + }); + session.registerFactories([factory]); + + const execution = session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: `abort-${combinator}`, + runId: `run-abort-${combinator}`, + executionToken: "execution-token", + args: {}, + }); + await vi.waitFor(() => + expect(sendRequest).toHaveBeenCalledWith("session.factory.agent", expect.anything()) + ); + + await session.clientSessionApis.factory!.abort({ + sessionId: session.sessionId, + runId: `run-abort-${combinator}`, + }); + + await expect(execution).rejects.toMatchObject({ name: "AbortError" }); + agentResponse.resolve({ result: "late" }); + } + ); + + it("dispatches factory.execute to the registered factory selected by name", async () => { + const firstRun = vi.fn(async () => ({ selected: "first" })); + const secondRun = vi.fn(async ({ args, log }) => { + log("executing"); + return { selected: "second", echoed: args }; + }); + const firstFactory = defineFactory({ + meta: { + name: "first", + description: "First factory", + phases: [], + }, + run: firstRun, + }); + const secondFactory = defineFactory({ + meta: { + name: "second", + description: "Second factory", + phases: [], + }, + run: secondRun, + }); + const session = new CopilotSession("session-execute", { + sendRequest: vi.fn(async () => ({})), + } as never); + session.registerFactories([firstFactory, secondFactory]); + + await expect( + session.clientSessionApis.factory!.execute({ + sessionId: session.sessionId, + name: "second", + runId: "run-echo", + executionToken: "execution-token", + args: { message: "hello" }, + }) + ).resolves.toEqual({ + result: { selected: "second", echoed: { message: "hello" } }, + }); + expect(firstRun).not.toHaveBeenCalled(); + expect(secondRun).toHaveBeenCalledOnce(); + + const error = await session.clientSessionApis + .factory!.execute({ + sessionId: session.sessionId, + name: "missing", + runId: "run-missing", + args: {}, + }) + .catch((caught: unknown) => caught); + expect(error).toBeInstanceOf(ResponseError); + expect((error as ResponseError<{ code: string; name: string }>).data).toEqual({ + code: "factory_not_found", + name: "missing", + }); + }); + + it("runs fresh factories and routes direct and legacy resumes by ID without args", async () => { + const factory = defineFactory({ + meta: { + name: "friendly-run", + description: "Friendly run wrapper", + phases: [], + }, + run: async () => ({ unused: true }), + }); + const sendRequest = vi.fn(async (method: string, params: { name?: string }) => + method === "session.factory.resume" + ? { + factoryName: "stored-name", + run: { + runId: "run-prior", + status: "completed", + result: { name: "stored-name", persistedArgs: true }, + }, + } + : { + runId: "run-foreground", + status: "completed", + result: { name: params.name }, + } + ); + const session = new CopilotSession("session-run", { sendRequest } as never); + + await expect( + session.factory.resume("run-prior", { + limits: { maxTotalSubagents: 7 }, + }) + ).resolves.toMatchObject({ + status: "completed", + result: { name: "stored-name", persistedArgs: true }, + }); + await expect( + session.factory.run("by-name", { + args: { value: 1 }, + limits: { maxTotalSubagents: 7 }, + resumeFromRunId: "run-prior", + }) + ).resolves.toMatchObject({ + status: "completed", + result: { name: "stored-name", persistedArgs: true }, + }); + await expect(session.factory.run(factory)).resolves.toMatchObject({ + status: "completed", + result: { name: "friendly-run" }, + }); + expect(sendRequest).toHaveBeenNthCalledWith(1, "session.factory.resume", { + sessionId: session.sessionId, + runId: "run-prior", + limits: { maxTotalSubagents: 7 }, + }); + expect(sendRequest).toHaveBeenNthCalledWith(2, "session.factory.resume", { + sessionId: session.sessionId, + runId: "run-prior", + limits: { maxTotalSubagents: 7 }, + }); + expect(sendRequest).toHaveBeenNthCalledWith(3, "session.factory.run", { + sessionId: session.sessionId, + name: "friendly-run", + args: {}, + options: { limits: undefined }, + }); + }); + + it("returns the full envelope for a failed foreground run", async () => { + const envelope = { + runId: "run-error", + status: "error" as const, + error: "factory failed", + snapshot: { completed: 1 }, + }; + const session = new CopilotSession("session-error", { + sendRequest: vi.fn(async () => envelope), + } as never); + + // A run that exists resolves with its envelope; only pre-execution + // failures (no run id) reject. + await expect(session.factory.run("failing")).resolves.toEqual(envelope); + }); + + it.each([ + "not_found", + "non_resumable", + "already_active", + "factory_already_running", + "factory_limits_invalid", + "factory_session_disposed", + "factory_storage_unavailable", + "factory_storage_corrupt", + ] as const)( + "throws FactoryResumeError with code %s for pre-execution failures", + async (code) => { + const session = new CopilotSession("session-resume-error", { + sendRequest: vi.fn(async () => { + throw new ResponseError(-32602, `resume failed: ${code}`, { code }); + }), + } as never); + + const error = await session.factory + .resume("run-error") + .catch((caught: unknown) => caught); + expect(error).toBeInstanceOf(FactoryResumeError); + expect((error as FactoryResumeError).code).toBe(code); + } + ); + + it("leaves an unreachable permission_denied response as a raw ResponseError", async () => { + const session = new CopilotSession("session-resume-permission-denied", { + sendRequest: vi.fn(async () => { + throw new ResponseError(-32602, "resume failed: permission_denied", { + code: "permission_denied", + }); + }), + } as never); + + const error = await session.factory.resume("run-error").catch((caught: unknown) => caught); + expect(error).toBeInstanceOf(ResponseError); + expect(error).not.toBeInstanceOf(FactoryResumeError); + expect((error as ResponseError<{ code: string }>).data.code).toBe("permission_denied"); + }); + + it("returns resumed execution failures as envelopes", async () => { + const envelope = { + runId: "run-execution-error", + status: "error" as const, + error: "resumed body failed", + }; + const session = new CopilotSession("session-resumed-run-error", { + sendRequest: vi.fn(async () => ({ factoryName: "stored-name", run: envelope })), + } as never); + + await expect(session.factory.resume("run-execution-error")).resolves.toEqual(envelope); + }); +}); + +describe("factory run settlement", () => { + it.each([ + ["completed", true], + ["error", true], + ["halted", true], + ["cancelled", true], + ["pending", false], + ["running", false], + ] as const)("classifies %s as terminal=%s", (status, expected) => { + expect(isFactoryRunTerminal(status)).toBe(expected); + }); + + it("resolves immediately when the run has already settled", async () => { + const envelope = { runId: "run-settled", status: "completed" as const, result: 42 }; + const sendRequest = vi.fn(async () => envelope); + const session = new CopilotSession("session-wait-settled", { sendRequest } as never); + + await expect(session.factory.waitForRun("run-settled")).resolves.toEqual(envelope); + expect(sendRequest).toHaveBeenCalledTimes(1); + expect(sendRequest).toHaveBeenCalledWith("session.factory.getRun", { + sessionId: session.sessionId, + runId: "run-settled", + }); + }); + + it("waits for a running run to reach a terminal status", async () => { + const running = { runId: "run-wait", status: "running" as const }; + const terminal = { runId: "run-wait", status: "completed" as const, result: "done" }; + let current: unknown = running; + const sendRequest = vi.fn(async () => current); + const session = new CopilotSession("session-wait-running", { sendRequest } as never); + + const settled = session.factory.waitForRun("run-wait"); + // The first read observed a running envelope, so the wait is still pending. + await vi.waitFor(() => expect(sendRequest).toHaveBeenCalledTimes(1)); + + // An invalidation event for an unrelated run must not trigger a re-read. + (session as never as { _dispatchEvent(event: unknown): void })._dispatchEvent( + runUpdatedEvent("some-other-run", 2) + ); + expect(sendRequest).toHaveBeenCalledTimes(1); + + current = terminal; + (session as never as { _dispatchEvent(event: unknown): void })._dispatchEvent( + runUpdatedEvent("run-wait", 3) + ); + + await expect(settled).resolves.toEqual(terminal); + }); + + it("periodically re-reads when a terminal invalidation is missed", async () => { + vi.useFakeTimers(); + const running = { runId: "run-poll", status: "running" as const }; + const terminal = { runId: "run-poll", status: "completed" as const, result: "polled" }; + let current: unknown = running; + const sendRequest = vi.fn(async () => current); + const session = new CopilotSession("session-wait-poll", { sendRequest } as never); + + try { + const settled = session.factory.waitForRun("run-poll"); + await vi.waitFor(() => expect(sendRequest).toHaveBeenCalledTimes(1)); + + current = terminal; + await vi.advanceTimersByTimeAsync(5_000); + + await expect(settled).resolves.toEqual(terminal); + expect(sendRequest).toHaveBeenCalledTimes(2); + } finally { + vi.useRealTimers(); + } + }); + + it("stops watching once the run settles", async () => { + const running = { runId: "run-unsub", status: "running" as const }; + const terminal = { runId: "run-unsub", status: "error" as const, error: "body failed" }; + let current: unknown = running; + const sendRequest = vi.fn(async () => current); + const session = new CopilotSession("session-wait-unsub", { sendRequest } as never); + const handlersFor = (): Set | undefined => + ( + session as never as { + typedEventHandlers: Map>; + } + ).typedEventHandlers.get("factory.run_updated"); + + const settled = session.factory.waitForRun("run-unsub"); + await vi.waitFor(() => expect(sendRequest).toHaveBeenCalledTimes(1)); + expect(handlersFor()?.size ?? 0).toBe(1); + + current = terminal; + (session as never as { _dispatchEvent(event: unknown): void })._dispatchEvent( + runUpdatedEvent("run-unsub", 2) + ); + await expect(settled).resolves.toEqual(terminal); + + // The subscription must be released, or every completed wait leaks a + // listener for the lifetime of the session. + expect(handlersFor()?.size ?? 0).toBe(0); + + const callsAtSettlement = sendRequest.mock.calls.length; + // A late event for a settled run must not provoke another read. + (session as never as { _dispatchEvent(event: unknown): void })._dispatchEvent( + runUpdatedEvent("run-unsub", 3) + ); + expect(sendRequest).toHaveBeenCalledTimes(callsAtSettlement); + }); + + it("rejects when the signal is already aborted and never reads", async () => { + const sendRequest = vi.fn(async () => ({ runId: "run-pre", status: "running" })); + const session = new CopilotSession("session-wait-pre-abort", { sendRequest } as never); + + await expect( + session.factory.waitForRun("run-pre", { signal: AbortSignal.abort() }) + ).rejects.toThrow(); + expect(sendRequest).not.toHaveBeenCalled(); + }); + + it("rejects when aborted while waiting, leaving the run untouched", async () => { + const sendRequest = vi.fn(async () => ({ runId: "run-abort", status: "running" })); + const session = new CopilotSession("session-wait-abort", { sendRequest } as never); + const controller = new AbortController(); + + const settled = session.factory.waitForRun("run-abort", { signal: controller.signal }); + await vi.waitFor(() => expect(sendRequest).toHaveBeenCalledTimes(1)); + + controller.abort(); + await expect(settled).rejects.toThrow(); + // Aborting the wait must not cancel the run. + expect(sendRequest).not.toHaveBeenCalledWith("session.factory.cancel", expect.anything()); + }); + + it("propagates a read failure", async () => { + const sendRequest = vi.fn(async () => { + throw new Error("factory_storage_unavailable"); + }); + const session = new CopilotSession("session-wait-error", { sendRequest } as never); + + await expect(session.factory.waitForRun("run-broken")).rejects.toThrow( + "factory_storage_unavailable" + ); + }); + + it("collapses a burst of invalidation events into one in-flight read", async () => { + const running = { runId: "run-burst", status: "running" as const }; + const terminal = { runId: "run-burst", status: "completed" as const }; + let release: (() => void) | undefined; + const gate = new Promise((resolve) => (release = resolve)); + let readCount = 0; + const sendRequest = vi.fn(async () => { + readCount += 1; + if (readCount === 2) { + await gate; + } + // Reads 1 and 2 observe a running run; only the coalesced third + // read observes the terminal one. + return readCount >= 3 ? terminal : running; + }); + const session = new CopilotSession("session-wait-burst", { sendRequest } as never); + + const settled = session.factory.waitForRun("run-burst"); + await vi.waitFor(() => expect(sendRequest).toHaveBeenCalledTimes(1)); + + const dispatch = (revision: number): void => + (session as never as { _dispatchEvent(event: unknown): void })._dispatchEvent( + runUpdatedEvent("run-burst", revision) + ); + + // Second read is held open while three more events arrive; they must + // collapse into a single follow-up read rather than three. + dispatch(2); + await vi.waitFor(() => expect(sendRequest).toHaveBeenCalledTimes(2)); + dispatch(3); + dispatch(4); + dispatch(5); + expect(sendRequest).toHaveBeenCalledTimes(2); + + release?.(); + await expect(settled).resolves.toEqual(terminal); + // One initial read, the held read, and exactly one coalesced re-read + // standing in for all three queued events. + expect(sendRequest).toHaveBeenCalledTimes(3); + }); +}); diff --git a/nodejs/test/session-event-types.test.ts b/nodejs/test/session-event-types.test.ts index 37b49f8a4..213670216 100644 --- a/nodejs/test/session-event-types.test.ts +++ b/nodejs/test/session-event-types.test.ts @@ -15,9 +15,18 @@ */ import { describe, expect, it } from "vitest"; +import { approveAll } from "../src/index.js"; +import { FACTORY_AGENT_OPTION_KEYS } from "../src/factory.js"; +import type { FactoryAgentOptions as WireFactoryAgentOptions } from "../src/generated/rpc.js"; import type { // The aggregate union; must still resolve via the package root. SessionEvent, + PermissionRequest, + PermissionRequestedData, + PermissionRequestedEvent, + ManagedSettingsResolvedData, + ManagedSettingsResolvedEvent, + ManagedSettingsResolvedSource, // *Data payload types from the v0.3.0 generated session-event schema. AssistantMessageData, @@ -50,6 +59,11 @@ import type { UserMessageAgentMode, Attachment, WorkingDirectoryContextHostType, + FactoryContext, + FactoryDefinition, + FactoryAgentOptions, + FactoryRunResult, + JsonValue, } from "../src/index.js"; /** @@ -80,6 +94,37 @@ type _AssistantMessageEventStaysAlignedWithSessionEventUnion = _AssertEqual< Extract >; const _assistantMessageEventAlignmentCheck: _AssistantMessageEventStaysAlignedWithSessionEventUnion = true; +type _DefaultFactoryArgsAreJsonValue = _AssertEqual; +const _defaultFactoryArgsCheck: _DefaultFactoryArgsAreJsonValue = true; +type _DefaultFactoryResultIsJsonValueOrVoid = _AssertEqual< + Awaited>, + JsonValue | void +>; +const _defaultFactoryResultCheck: _DefaultFactoryResultIsJsonValueOrVoid = true; +type _FactoryRunResultIsJsonValueOrUndefined = _AssertEqual< + FactoryRunResult["result"], + JsonValue | undefined +>; +const _factoryRunResultCheck: _FactoryRunResultIsJsonValueOrUndefined = true; +type _FactoryAgentOptionKeysMatchPublicInterface = _AssertEqual< + (typeof FACTORY_AGENT_OPTION_KEYS)[number], + keyof FactoryAgentOptions +>; +const _factoryAgentOptionKeysCheck: _FactoryAgentOptionKeysMatchPublicInterface = true; +type _PublicFactoryAgentOptionsMatchWire = _AssertEqual< + keyof FactoryAgentOptions, + keyof WireFactoryAgentOptions +>; +const _publicFactoryAgentOptionsCheck: _PublicFactoryAgentOptionsMatchWire = true; +// @ts-expect-error Factory arguments must be representable on the JSON wire. +type _FactoryArgsRejectUndefined = FactoryContext; +// @ts-expect-error Factory results must be JSON values or top-level void. +type _FactoryResultRejectsFunction = FactoryDefinition void>; +type _PermissionRequestedEventStaysAlignedWithSessionEventUnion = _AssertEqual< + PermissionRequestedEvent, + Extract +>; +const _permissionRequestedEventAlignmentCheck: _PermissionRequestedEventStaysAlignedWithSessionEventUnion = true; describe("Session event type exports (#1156)", () => { it("exposes the headline ToolExecutionStartData type with a usable shape", () => { @@ -97,12 +142,127 @@ describe("Session event type exports (#1156)", () => { expect(data.toolName).toBe("shell"); expect(data.toolCallId).toBe("call-1"); - expect(data.arguments?.command).toBe("ls"); + expect(data.arguments).toEqual({ command: "ls" }); expect(data.mcpServerName).toBe("filesystem"); expect(data.mcpToolName).toBe("list_dir"); expect(data.turnId).toBe("turn-1"); }); + it("exposes explicit user approval metadata for managed Domain requests", () => { + const request: PermissionRequest = { + kind: "url", + url: "https://api.example.com/data", + intention: "Fetch domain data", + managedApprovalRequired: true, + }; + + expect(request.managedApprovalRequired).toBe(true); + }); + + it("exposes managed approval metadata through permission event types", () => { + const data: PermissionRequestedData = { + permissionRequest: { + kind: "url", + url: "https://api.example.com/data", + intention: "Fetch domain data", + managedApprovalRequired: true, + }, + requestId: "permission-1", + }; + const event: SessionEvent = { + id: "evt-permission-1", + parentId: null, + timestamp: "2026-01-01T00:00:00.000Z", + type: "permission.requested", + data, + }; + + if (event.type !== "permission.requested") { + throw new Error("expected permission.requested narrowing"); + } + + const permissionEvent: PermissionRequestedEvent = event; + expect(permissionEvent.data.permissionRequest.managedApprovalRequired).toBe(true); + }); + + it("exposes managed settings client and mixed provenance", () => { + const sources: ManagedSettingsResolvedSource[] = [ + "server", + "device", + "client", + "mixed", + "none", + ]; + expect(sources).toEqual(["server", "device", "client", "mixed", "none"]); + + const clientData: ManagedSettingsResolvedData = { + bypassPermissionsDisabled: true, + clientManaged: true, + deviceManaged: false, + failClosed: false, + managedKeys: ["permissions"], + serverManaged: false, + source: "client", + }; + const clientEvent: ManagedSettingsResolvedEvent = { + ephemeral: true, + id: "evt-managed-1", + parentId: null, + timestamp: "2026-01-01T00:00:00.000Z", + type: "session.managed_settings_resolved", + data: clientData, + }; + expect(clientEvent.data.source).toBe("client"); + expect(clientEvent.data.clientManaged).toBe(true); + + const { clientManaged: _, ...withoutClientManaged } = clientData; + const mixedData: ManagedSettingsResolvedData = { + ...withoutClientManaged, + source: "mixed", + }; + expect(mixedData.source).toBe("mixed"); + expect("clientManaged" in mixedData).toBe(false); + }); + + it("rejects approveAll in managed settings sessions", () => { + expect(() => + approveAll( + { + kind: "url", + url: "https://api.example.com/data", + intention: "Fetch ordinary data", + }, + { sessionId: "session-1", managedSettingsEnabled: true } + ) + ).toThrow("approveAll cannot be used when managed settings are enabled"); + + expect(() => + approveAll( + { + kind: "url", + url: "https://api.example.com/data", + intention: "Fetch managed data", + managedApprovalRequired: true, + }, + { sessionId: "session-1", managedSettingsEnabled: true } + ) + ).toThrow("approveAll cannot be used when managed settings are enabled"); + }); + + it("leaves managed requests pending when managed settings are disabled", () => { + expect( + approveAll( + { + kind: "url", + url: "https://api.example.com/data", + intention: "Fetch managed data", + managedApprovalRequired: true, + }, + { sessionId: "session-1", managedSettingsEnabled: false } + ) + ).toEqual({ kind: "no-result" }); + }); + it("wraps ToolExecutionStartData inside the exported ToolExecutionStartEvent", () => { const event: ToolExecutionStartEvent = { id: "evt-1", @@ -160,6 +320,8 @@ describe("Session event type exports (#1156)", () => { assertImportable(); assertImportable(); assertImportable(); + assertImportable(); + assertImportable(); assertImportable(); assertImportable(); @@ -169,6 +331,9 @@ describe("Session event type exports (#1156)", () => { assertImportable(); assertImportable(); assertImportable(); + assertImportable(); + assertImportable(); + assertImportable(); // Supporting auxiliary types referenced by the *Data shapes — these // must round-trip through the package root too, otherwise consumers diff --git a/nodejs/test/session-send-and-wait.test.ts b/nodejs/test/session-send-and-wait.test.ts new file mode 100644 index 000000000..8b6e390c4 --- /dev/null +++ b/nodejs/test/session-send-and-wait.test.ts @@ -0,0 +1,137 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { describe, expect, it, onTestFinished } from "vitest"; +import type { MessageConnection } from "vscode-jsonrpc/node.js"; +import { CopilotSession } from "../src/session.js"; +import type { SessionEvent } from "../src/generated/session-events.js"; + +function sessionEvent(type: "session.idle", data: Record = {}): SessionEvent { + return { + type, + id: "00000000-0000-4000-8000-000000000001", + parentId: null, + timestamp: new Date().toISOString(), + ephemeral: true, + data, + } as SessionEvent; +} + +/** Builds a `session.error` event, the shape `session.log(…, { level: "error" })` produces. */ +function errorEvent(message: string): SessionEvent { + return { + type: "session.error", + id: "00000000-0000-4000-8000-000000000001", + parentId: null, + timestamp: new Date().toISOString(), + data: { errorType: "notification", message }, + } as SessionEvent; +} + +function controlledSession(): { + session: CopilotSession; + sendStarted: Promise; + resolveSend: () => void; + rejectSend: (error: Error) => void; +} { + let resolveSendRequest: ((value: unknown) => void) | undefined; + let rejectSendRequest: ((error: Error) => void) | undefined; + let markSendStarted: () => void; + const sendStarted = new Promise((resolve) => { + markSendStarted = resolve; + }); + const connection = { + sendRequest: () => + new Promise((resolve, reject) => { + resolveSendRequest = resolve; + rejectSendRequest = reject; + markSendStarted(); + }), + } as unknown as MessageConnection; + + return { + session: new CopilotSession("session-1", connection), + sendStarted, + resolveSend: () => resolveSendRequest?.({ messageId: "msg-1" }), + rejectSend: (error) => rejectSendRequest?.(error), + }; +} + +describe("sendAndWait", () => { + it("does not emit an unhandled rejection when session.error arrives before the idle race is armed", async () => { + const { session, sendStarted, resolveSend } = controlledSession(); + + const unhandled: unknown[] = []; + const onUnhandled = (reason: unknown): void => { + unhandled.push(reason); + }; + process.on("unhandledRejection", onUnhandled); + onTestFinished(() => { + process.off("unhandledRejection", onUnhandled); + }); + + const pending = session.sendAndWait({ prompt: "hi" }); + await sendStarted; + + // A session.error lands while send()'s RPC is still in flight. This is + // ordinary traffic: a joined client calling session.log(…, { level: "error" }) + // or an MCP server failing to start both produce one. + session._dispatchEvent(errorEvent("MCP server failed to start")); + + // Yield past a macrotask boundary so Node has run the checkpoint at which + // it classifies a rejection as unhandled. + await new Promise((resolve) => setTimeout(resolve, 0)); + + expect(unhandled).toEqual([]); + + resolveSend(); + await expect(pending).rejects.toThrow("MCP server failed to start"); + }); + + it("preserves an early idle event until send completes", async () => { + const { session, sendStarted, resolveSend } = controlledSession(); + const pending = session.sendAndWait({ prompt: "hi" }); + await sendStarted; + + session._dispatchEvent(sessionEvent("session.idle")); + + const stateBeforeSend = await Promise.race([ + pending.then(() => "settled"), + new Promise<"pending">((resolve) => setTimeout(() => resolve("pending"), 0)), + ]); + expect(stateBeforeSend).toBe("pending"); + + resolveSend(); + await expect(pending).resolves.toBeUndefined(); + }); + + it("preserves the send rejection when a session error arrives first", async () => { + const { session, sendStarted, rejectSend } = controlledSession(); + const pending = session.sendAndWait({ prompt: "hi" }); + await sendStarted; + + session._dispatchEvent(errorEvent("session error")); + rejectSend(new Error("send failed")); + + await expect(pending).rejects.toThrow("send failed"); + }); + + it("uses the first session outcome observed while send is in flight", async () => { + const idleFirst = controlledSession(); + const idleFirstPending = idleFirst.session.sendAndWait({ prompt: "hi" }); + await idleFirst.sendStarted; + idleFirst.session._dispatchEvent(sessionEvent("session.idle")); + idleFirst.session._dispatchEvent(errorEvent("later error")); + idleFirst.resolveSend(); + await expect(idleFirstPending).resolves.toBeUndefined(); + + const errorFirst = controlledSession(); + const errorFirstPending = errorFirst.session.sendAndWait({ prompt: "hi" }); + await errorFirst.sendStarted; + errorFirst.session._dispatchEvent(errorEvent("first error")); + errorFirst.session._dispatchEvent(sessionEvent("session.idle")); + errorFirst.resolveSend(); + await expect(errorFirstPending).rejects.toThrow("first error"); + }); +}); diff --git a/nodejs/test/session_fs_adapter.test.ts b/nodejs/test/session_fs_adapter.test.ts index fb62d9904..98749dffb 100644 --- a/nodejs/test/session_fs_adapter.test.ts +++ b/nodejs/test/session_fs_adapter.test.ts @@ -67,6 +67,20 @@ describe("SessionFsAdapter", () => { rowsAffected: 0, }; }, + async transaction(statements) { + return statements.map((statement) => ({ + columns: ["sessionId", "query", "queryType", "answer"], + rows: [ + { + sessionId, + query: statement.query, + queryType: statement.queryType, + answer: statement.params?.answer, + }, + ], + rowsAffected: 0, + })); + }, async exists() { return true; }, @@ -205,6 +219,7 @@ describe("SessionFsAdapter", () => { rename: () => Promise.reject(error), sqlite: { query: () => Promise.reject(error), + transaction: () => Promise.reject(error), exists: () => Promise.reject(error), }, }; @@ -244,6 +259,14 @@ describe("SessionFsAdapter", () => { ).rejects.toThrow("missing file"); await expect(handler.sqliteExists({ sessionId })).rejects.toThrow("missing file"); + // sqliteTransaction reports a classified result-level error instead + const transaction = await handler.sqliteTransaction({ + sessionId, + statements: [{ query: "select 1", queryType: "query" }], + }); + expect(transaction.results).toEqual([]); + expect(transaction.error).toEqual({ errorClass: "fatal", message: "missing file" }); + const unknownProvider = createSessionFsAdapter(makeThrowingProvider(makeError("bad path"))); const unknownError = await unknownProvider.writeFile({ sessionId, diff --git a/nodejs/test/typescript-codegen.test.ts b/nodejs/test/typescript-codegen.test.ts index 248b60968..0a63a5293 100644 --- a/nodejs/test/typescript-codegen.test.ts +++ b/nodejs/test/typescript-codegen.test.ts @@ -2,7 +2,12 @@ import type { JSONSchema7 } from "json-schema"; import { compile } from "json-schema-to-typescript"; import { describe, expect, it } from "vitest"; -import { normalizeSchemaForTypeScript } from "../../scripts/codegen/typescript.ts"; +import { + assertNoPublicInternalReferences, + filterPublicSessionEventVariants, + normalizeSchemaForTypeScript, +} from "../../scripts/codegen/typescript.ts"; +import type { DefinitionCollections } from "../../scripts/codegen/utils.ts"; describe("typescript schema codegen", () => { it("emits JSDoc comments for described enum values", async () => { @@ -43,4 +48,346 @@ describe("typescript schema codegen", () => { ); expect(code).toContain('inlineMode: /** Use a direct value. */ "direct" | "indirect";'); }); + + it("maps bare opaque properties to their marker aliases", async () => { + const code = await compile( + normalizeSchemaForTypeScript({ + title: "OpaqueProperty", + type: "object", + properties: { + json: { "x-opaque-json": true }, + inProcess: { "x-opaque-in-process": true }, + }, + required: ["json", "inProcess"], + }), + "OpaqueProperty", + { bannerComment: "", style: { semi: true, singleQuote: false } } + ); + + expect(code).toContain("json: JsonValue;"); + expect(code).toContain("inProcess: OpaqueInProcessValue;"); + }); + + it("maps a bare opaque JSON additional property to JsonValue", async () => { + const code = await compile( + normalizeSchemaForTypeScript({ + title: "OpaqueMap", + type: "object", + additionalProperties: { "x-opaque-json": true }, + }), + "OpaqueMap", + { bannerComment: "", style: { semi: true, singleQuote: false } } + ); + + expect(code).toContain("[k: string]: JsonValue;"); + }); + + it("maps a bare opaque JSON array item to JsonValue", async () => { + const code = await compile( + normalizeSchemaForTypeScript({ + title: "OpaqueArray", + type: "object", + properties: { values: { type: "array", items: { "x-opaque-json": true } } }, + required: ["values"], + }), + "OpaqueArray", + { bannerComment: "", style: { semi: true, singleQuote: false } } + ); + + expect(code).toContain("values: JsonValue[];"); + }); + + it("maps a bare opaque JSON definition to a named alias", async () => { + const code = await compile( + normalizeSchemaForTypeScript({ + title: "OpaqueDefinitionRoot", + type: "object", + properties: { value: { $ref: "#/definitions/OpaqueDefinition" } }, + definitions: { OpaqueDefinition: { "x-opaque-json": true } }, + }), + "OpaqueDefinitionRoot", + { bannerComment: "", style: { semi: true, singleQuote: false } } + ); + + expect(code).toContain("export type OpaqueDefinition = JsonValue;"); + }); + + it("keeps an opaque JSON node with anyOf as a union", async () => { + const code = await compile( + normalizeSchemaForTypeScript({ + title: "ConstrainedUnion", + "x-opaque-json": true, + anyOf: [{ type: "string" }, { type: "number" }], + }), + "ConstrainedUnion", + { bannerComment: "", style: { semi: true, singleQuote: false } } + ); + + expect(code).toContain("export type ConstrainedUnion = string | number;"); + expect(code).not.toContain("JsonValue"); + }); + + it("keeps an opaque JSON node with object constraints as an object", async () => { + const code = await compile( + normalizeSchemaForTypeScript({ + title: "ConstrainedObject", + type: "object", + "x-opaque-json": true, + properties: { name: { type: "string" } }, + required: ["name"], + }), + "ConstrainedObject", + { bannerComment: "", style: { semi: true, singleQuote: false } } + ); + + expect(code).toContain("export interface ConstrainedObject {"); + expect(code).toContain("name: string;"); + expect(code).not.toContain("JsonValue"); + }); + + it("removes both opaque markers from every normalized schema node", () => { + const normalized = normalizeSchemaForTypeScript({ + type: "object", + "x-opaque-json": true, + properties: { + json: { "x-opaque-json": true }, + inProcess: { "x-opaque-in-process": true }, + }, + additionalProperties: { "x-opaque-in-process": true }, + }) as Record; + + const assertMarkersRemoved = (value: unknown): void => { + if (Array.isArray(value)) { + value.forEach(assertMarkersRemoved); + } else if (value && typeof value === "object") { + for (const [key, child] of Object.entries(value as Record)) { + expect(key).not.toBe("x-opaque-json"); + expect(key).not.toBe("x-opaque-in-process"); + assertMarkersRemoved(child); + } + } + }; + + assertMarkersRemoved(normalized); + }); +}); + +describe("filterPublicSessionEventVariants", () => { + const makeCollections = (defs: Record): DefinitionCollections => ({ + definitions: defs, + $defs: {}, + }); + + it("keeps public union arms", () => { + const defs = { + PublicEvent: { type: "object" as const, properties: { type: { const: "pub" } } }, + }; + const variants: JSONSchema7[] = [{ $ref: "#/definitions/PublicEvent" }]; + const { publicVariants, excludedDefinitionNames } = filterPublicSessionEventVariants( + variants, + makeCollections(defs) + ); + expect(publicVariants).toHaveLength(1); + expect(excludedDefinitionNames.size).toBe(0); + }); + + it("excludes arms whose arm object is marked visibility:internal", () => { + const defs = { + InternalEvent: { + type: "object" as const, + visibility: "internal", + properties: { type: { const: "internal.evt" } }, + } as JSONSchema7 & { visibility: string }, + }; + const variants: JSONSchema7[] = [ + { $ref: "#/definitions/InternalEvent", visibility: "internal" } as JSONSchema7 & { + visibility: string; + }, + ]; + const { publicVariants, excludedDefinitionNames } = filterPublicSessionEventVariants( + variants, + makeCollections(defs) + ); + expect(publicVariants).toHaveLength(0); + expect(excludedDefinitionNames.has("InternalEvent")).toBe(true); + }); + + it("excludes arms whose resolved definition is marked visibility:internal", () => { + const defs = { + InternalEvent: { + type: "object" as const, + visibility: "internal", + properties: { type: { const: "internal.evt" } }, + } as JSONSchema7 & { visibility: string }, + }; + // arm object itself is NOT marked, but the resolved definition is + const variants: JSONSchema7[] = [{ $ref: "#/definitions/InternalEvent" }]; + const { publicVariants, excludedDefinitionNames } = filterPublicSessionEventVariants( + variants, + makeCollections(defs) + ); + expect(publicVariants).toHaveLength(0); + expect(excludedDefinitionNames.has("InternalEvent")).toBe(true); + }); + + it("excludes arms whose internal data sub-property is the only internal marker (legacy pattern)", () => { + // Event types that carry a `data: InternalData` field — the `data` property is what is + // internal, not the event wrapper type itself. + const defs = { + InternalData: { + type: "object" as const, + visibility: "internal", + } as JSONSchema7 & { visibility: string }, + WrapperEvent: { + type: "object" as const, + properties: { + type: { const: "wrapper.evt" }, + data: { $ref: "#/definitions/InternalData" }, + }, + }, + }; + const variants: JSONSchema7[] = [{ $ref: "#/definitions/WrapperEvent" }]; + const { publicVariants, excludedDefinitionNames } = filterPublicSessionEventVariants( + variants, + makeCollections(defs) + ); + expect(publicVariants).toHaveLength(0); + expect(excludedDefinitionNames.has("WrapperEvent")).toBe(true); + expect(excludedDefinitionNames.has("InternalData")).toBe(true); + }); +}); + +describe("assertNoPublicInternalReferences", () => { + it("passes when all declarations are public and do not reference internal types", () => { + const ts = ` +export interface Foo { + bar: string; +} +export type Bar = "a" | "b"; +`; + expect(() => assertNoPublicInternalReferences(ts, new Set(["Hidden"]))).not.toThrow(); + }); + + it("passes when the only reference is from an @internal-tagged declaration", () => { + const ts = ` +/** @internal */ +export interface Hidden { + x: number; +} +/** @internal */ +export interface AlsoInternal { + h: Hidden; +} +export interface Public { + y: string; +} +`; + expect(() => assertNoPublicInternalReferences(ts, new Set(["Hidden"]))).not.toThrow(); + }); + + it("passes when the reference is inside an @internal-tagged member of a public type", () => { + const ts = ` +/** @internal */ +export interface Hidden { + x: number; +} +export interface Public { + /** + * Some field. + * @internal + */ + secret?: Hidden; + visible: string; +} +`; + expect(() => assertNoPublicInternalReferences(ts, new Set(["Hidden"]))).not.toThrow(); + }); + + it("throws when a public declaration references an internal type directly", () => { + const ts = ` +/** @internal */ +export interface Hidden { + x: number; +} +export type Event = PublicEvent | Hidden; +`; + expect(() => assertNoPublicInternalReferences(ts, new Set(["Hidden"]))).toThrow( + /Event \(public\) references internal type Hidden/ + ); + }); + + it("throws when a public interface member references an internal type", () => { + const ts = ` +/** @internal */ +export interface Hidden { + x: number; +} +export interface Public { + value: Hidden; +} +`; + expect(() => assertNoPublicInternalReferences(ts, new Set(["Hidden"]))).toThrow( + /Public \(public\) references internal type Hidden/ + ); + }); + + it("does not count JSDoc comment text as a code reference", () => { + // The auto-generated JSDoc says 'via the definition "Hidden"' but that is not a + // real TypeScript type reference — it must not trigger the validator. + const ts = ` +/** @internal */ +export interface Hidden { + x: number; +} +export interface Preceding { + y: string; +} +/** + * This interface was referenced by something. + * via the definition "Hidden". + */ +export interface Following { + z: string; +} +`; + expect(() => assertNoPublicInternalReferences(ts, new Set(["Hidden"]))).not.toThrow(); + }); + + it("does not count inline object-shaped @internal members as public references", () => { + const ts = ` +/** @internal */ +export interface Hidden { + x: number; +} +export interface Public { + /** + * Some field. + * @internal + */ + secret?: { + [k: string]: Hidden | undefined; + }; + visible: string; +} +`; + expect(() => assertNoPublicInternalReferences(ts, new Set(["Hidden"]))).not.toThrow(); + }); + + it("does not count function body references as public type references", () => { + const ts = ` +/** @internal */ +export interface Hidden { + x: number; +} +/** @internal */ +export function doInternal(connection: unknown): void { + connection.onRequest("x", async (params: Hidden) => { return params; }); +} +export function doPublic(connection: unknown): void { + connection.onRequest("x", async (params: Hidden) => { return params; }); +} +`; + // function body references are stripped — only signature matters + expect(() => assertNoPublicInternalReferences(ts, new Set(["Hidden"]))).not.toThrow(); + }); }); diff --git a/nodejs/vitest.config.ts b/nodejs/vitest.config.ts index 03f6c779e..bb07cb017 100644 --- a/nodejs/vitest.config.ts +++ b/nodejs/vitest.config.ts @@ -1,11 +1,13 @@ import { defineConfig } from "vitest/config"; +const integrationTestTimeout = process.platform === "win32" ? 60000 : 30000; + export default defineConfig({ test: { globals: true, environment: "node", - testTimeout: 30000, // 30 seconds for integration tests - hookTimeout: 30000, + testTimeout: integrationTestTimeout, + hookTimeout: integrationTestTimeout, teardownTimeout: 10000, isolate: true, // Run each test file in isolation pool: "forks", // Use process forking for better isolation diff --git a/python/README.md b/python/README.md index 3089c3669..bb17f68cc 100644 --- a/python/README.md +++ b/python/README.md @@ -76,6 +76,7 @@ from copilot import CopilotClient from copilot.session_events import AssistantMessageData, SessionIdleData from copilot.session import PermissionHandler + async def main(): # Client automatically starts on enter and cleans up on exit async with CopilotClient() as client: @@ -100,6 +101,7 @@ async def main(): await session.send("What is 2+2?") await done.wait() + asyncio.run(main()) ``` @@ -114,11 +116,12 @@ from copilot import CopilotClient from copilot.session_events import AssistantMessageData, SessionIdleData from copilot.session import PermissionHandler + async def main(): client = CopilotClient() await client.start() - # Create a session (on_permission_request is optional; approve_all allows every tool) + # approve_all is only valid when managed settings are disabled. session = await client.create_session( on_permission_request=PermissionHandler.approve_all, model="gpt-5", @@ -141,6 +144,7 @@ async def main(): await session.disconnect() await client.stop() + asyncio.run(main()) ``` @@ -167,6 +171,7 @@ async with CopilotClient() as client: on_permission_request=PermissionHandler.approve_all, model="gpt-5", ) as session: + def on_event(event): print(f"Event: {event.type}") @@ -267,16 +272,19 @@ finally: These are passed as keyword arguments to `create_session()`: - `model` (str): Model to use ("gpt-5", "claude-sonnet-4.5", etc.). **Required when using custom provider.** -- `reasoning_effort` (str): Reasoning effort level for models that support it ("low", "medium", "high", "xhigh"). Use `list_models()` to check which models support this option. +- `reasoning_effort` (str): Reasoning effort level for models that support it ("low", "medium", "high", "xhigh", "max"). Use `list_models()` to check which models support this option. - `session_id` (str): Custom session ID - `tools` (list): Custom tools exposed to the CLI. Tools with `handler=None` are declaration-only and must be resolved via pending tool-call RPCs. - `system_message` (SystemMessageConfig): System message configuration - `streaming` (bool): Enable streaming delta events - `provider` (ProviderConfig): Custom API provider configuration (BYOK). See [Custom Providers](#custom-providers) section. - `infinite_sessions` (InfiniteSessionConfig): Automatic context compaction configuration -- `on_permission_request` (callable): Optional handler called before each tool execution to approve or deny it. When omitted, permission requests are emitted as events and left pending for manual resolution. Use `PermissionHandler.approve_all` to allow everything, or provide a custom function for fine-grained control. See [Permission Handling](#permission-handling) section. +- `working_directory` (str | None): Working directory for the session (default: runtime process working directory). +- `enable_session_store` (bool): Enables the cross-session store for search and retrieval across sessions. When unset in `"copilot-cli"` mode, the runtime default applies (enabled). In `"empty"` mode, defaults to disabled. +- `on_permission_request` (callable): Optional handler called before each tool execution to approve or deny it. When omitted, permission requests are emitted as events and left pending for manual resolution. `PermissionHandler.approve_all` approves requests when managed settings are disabled and raises an error when `enable_managed_settings` is true. Custom handlers can inspect `managed_approval_required` for human-facing confirmation logic. See [Permission Handling](#permission-handling) section. - `on_user_input_request` (callable): Handler for user input requests from the agent (enables ask_user tool). See [User Input Requests](#user-input-requests) section. - `hooks` (SessionHooks): Hook handlers for session lifecycle events. See [Session Hooks](#session-hooks) section. +- `available_tools` / `excluded_tools` / `default_agent.excluded_tools` / custom-agent `tools`: MCP tools registered from `mcp_servers` are exposed to the runtime as `-`. For `available_tools` and `excluded_tools`, prefer `ToolSet().add_mcp("-")` or the raw `mcp:-` form. For custom-agent `tools` and `default_agent.excluded_tools`, use `-` directly. **Session Lifecycle Methods:** @@ -287,14 +295,18 @@ session_id = await client.get_foreground_session_id() # Request TUI to display a specific session (TUI+server mode only) await client.set_foreground_session_id("session-123") + # Subscribe to all lifecycle events def on_lifecycle(event): print(f"{event.type}: {event.session_id}") + unsubscribe = client.on_lifecycle(on_lifecycle) # Subscribe to specific event type -unsubscribe = client.on_lifecycle("session.foreground", lambda e: print(f"Foreground: {e.session_id}")) +unsubscribe = client.on_lifecycle( + "session.foreground", lambda e: print(f"Foreground: {e.session_id}") +) # Later, to stop receiving events: unsubscribe() @@ -316,14 +328,17 @@ Define tools with automatic JSON schema generation using the `@define_tool` deco from pydantic import BaseModel, Field from copilot import CopilotClient, define_tool + class LookupIssueParams(BaseModel): id: str = Field(description="Issue identifier") + @define_tool(description="Fetch issue details from our tracker") async def lookup_issue(params: LookupIssueParams) -> str: issue = await fetch_issue(params.id) return issue.summary + async with await client.create_session( on_permission_request=PermissionHandler.approve_all, model="gpt-5", @@ -343,6 +358,7 @@ from copilot import CopilotClient from copilot.tools import Tool, ToolInvocation, ToolResult from copilot.session import PermissionHandler + async def lookup_issue(invocation: ToolInvocation) -> ToolResult: issue_id = invocation.arguments["id"] issue = await fetch_issue(issue_id) @@ -352,6 +368,7 @@ async def lookup_issue(invocation: ToolInvocation) -> ToolResult: session_log=f"Fetched issue {issue_id}", ) + async with await client.create_session( on_permission_request=PermissionHandler.approve_all, model="gpt-5", @@ -471,6 +488,7 @@ from copilot.session_events import ( ) from copilot.session import PermissionHandler + async def main(): async with CopilotClient() as client: async with await client.create_session( @@ -507,6 +525,7 @@ async def main(): await session.send("Tell me a short story") await done.wait() # Wait for streaming to complete + asyncio.run(main()) ``` @@ -586,7 +605,7 @@ The SDK supports custom OpenAI-compatible API providers (BYOK - Bring Your Own K - `api_key` (str): API key (optional for local providers like Ollama) - `bearer_token` (str): Bearer token for authentication (takes precedence over `api_key`) - `wire_api` (str): API format for OpenAI/Azure - `"completions"` or `"responses"` (default: `"completions"`) -- `azure` (dict): Azure-specific options with `api_version` (default: `"2024-10-21"`) +- `azure` (dict): Azure-specific options with `api_version`; when omitted, the runtime uses the GA versionless `v1` route **Example with Ollama:** @@ -678,7 +697,10 @@ async with await client.create_session( system_message={ "mode": "customize", "sections": { - "tone": {"action": "replace", "content": "Respond in a warm, professional tone. Be thorough in explanations."}, + "tone": { + "action": "replace", + "content": "Respond in a warm, professional tone. Be thorough in explanations.", + }, "code_change_rules": {"action": "remove"}, "guidelines": {"action": "append", "content": "\n* Always cite data sources"}, }, @@ -688,9 +710,9 @@ async with await client.create_session( ... ``` -Available section IDs: `"identity"`, `"tone"`, `"tool_efficiency"`, `"environment_context"`, `"code_change_rules"`, `"guidelines"`, `"safety"`, `"tool_instructions"`, `"custom_instructions"`, `"last_instructions"`. +Available section IDs: `"preamble"`, `"identity"`, `"tone"`, `"tool_efficiency"`, `"environment_context"`, `"code_change_rules"`, `"guidelines"`, `"safety"`, `"tool_instructions"`, `"custom_instructions"`, `"runtime_instructions"`, `"last_instructions"`. `"identity"` and `"tool_instructions"` are section groups that target a collection of related sub-sections as a unit; use `"preamble"` to target just the identity preamble. -Each section override supports four string actions: `"replace"`, `"remove"`, `"append"`, and `"prepend"`. Unknown section IDs are handled gracefully: content is appended to additional instructions, and `"remove"` overrides are silently ignored. +Each section override supports five string actions: `"replace"`, `"remove"`, `"append"`, `"prepend"`, and `"preserve"` (a no-op that opts an individually-addressable section out of a group-level `"remove"`). Unknown section IDs are handled gracefully: content from `"replace"`/`"append"`/`"prepend"` overrides is appended to additional instructions, and `"remove"` overrides are silently ignored. You can also pass a transform callback as the `action` instead of a string. The callback receives the current section content and returns the new content (sync or async): @@ -698,6 +720,7 @@ You can also pass a transform callback as the `action` instead of a string. The def redact_paths(content: str) -> str: return content.replace("/home/user", "/***") + async with await client.create_session( on_permission_request=PermissionHandler.approve_all, model="gpt-5", @@ -760,7 +783,7 @@ An `on_permission_request` handler is optional when you create or resume a sessi ### Approve All (simplest) -Use the built-in `PermissionHandler.approve_all` helper to allow every tool call without any checks: +Use the built-in `PermissionHandler.approve_all` helper to approve ordinary permission requests automatically: ```python from copilot import CopilotClient @@ -772,12 +795,14 @@ session = await client.create_session( ) ``` +When `enable_managed_settings` is true for the session, `approve_all` raises an error. Use a custom handler for managed sessions; request-level `managed_approval_required` remains available for human-facing confirmation logic. + ### Custom Permission Handler -Provide your own function to inspect each request and apply custom logic (sync or async): +Provide your own function to inspect each request and apply custom logic (sync or async). Check `managed_approval_required` before any automatic approval: ```python -from copilot import PermissionRequest, PermissionRequestResult +from copilot import PermissionNoResult, PermissionRequest, PermissionRequestResult from copilot.rpc import ( PermissionDecisionApproveOnce, PermissionDecisionReject, @@ -785,9 +810,10 @@ from copilot.rpc import ( from copilot.session_events import PermissionRequestShell -def on_permission_request( - request: PermissionRequest, invocation: dict -) -> PermissionRequestResult: +def on_permission_request(request: PermissionRequest, invocation: dict) -> PermissionRequestResult: + if getattr(request, "managed_approval_required", False) is True: + return PermissionNoResult() + # ``PermissionRequest`` is a discriminated union — pattern-match on # the variant class to access the per-kind fields. match request: @@ -810,6 +836,9 @@ Async handlers are also supported: async def on_permission_request( request: PermissionRequest, invocation: dict ) -> PermissionRequestResult: + if getattr(request, "managed_approval_required", False) is True: + return PermissionNoResult() + # Simulate an async approval check (e.g., prompting a user over a network) await asyncio.sleep(0) return PermissionDecisionApproveOnce() @@ -819,7 +848,8 @@ async def on_permission_request( The handler returns a ``PermissionRequestResult``, which is an alias for ``PermissionDecision | PermissionNoResult`` (the generated wire-level -union of every decision variant, plus a small sentinel for v1 servers). +union of every decision variant, plus a sentinel that suppresses this SDK +client's response). Approval decisions are present-tense — they describe the decision to apply, not the past-tense outcome reported back on `permission.completed` session events. @@ -829,7 +859,7 @@ session events. | `PermissionDecisionApproveOnce()` | Allow this single request | | `PermissionDecisionReject(feedback="…")` | Deny the request (optional feedback string forwarded to the LLM) | | `PermissionDecisionUserNotAvailable()` | Deny the request because no user is available to confirm it (the default) | -| `PermissionNoResult()` | Leave the request unanswered (only valid with protocol v1; rejected by protocol v2 servers) | +| `PermissionNoResult()` | During event-based dispatch, suppress this SDK client's response so another connected client can answer the pending request; legacy direct callbacks cannot abstain | Several richer variants (``PermissionDecisionApproveForSession``, ``PermissionDecisionApproveForLocation``, ``PermissionDecisionApprovePermanently``, @@ -871,6 +901,7 @@ async def handle_user_input(request, invocation): "wasFreeform": True, # Whether the answer was freeform (not from choices) } + async with await client.create_session( on_permission_request=PermissionHandler.approve_all, model="gpt-5", @@ -893,12 +924,14 @@ async def on_pre_tool_use(input, invocation): "additionalContext": "Extra context for the model", } + async def on_post_tool_use(input, invocation): print(f"Tool {input['toolName']} completed") return { "additionalContext": "Post-execution notes", } + async def on_post_tool_use_failure(input, invocation): # Fires when a tool's result was a failure. `on_post_tool_use` only fires # on success, so register this handler to observe failed tool calls. The @@ -908,27 +941,32 @@ async def on_post_tool_use_failure(input, invocation): "additionalContext": f"Retry guidance for {input['toolName']}", } + async def on_user_prompt_submitted(input, invocation): print(f"User prompt: {input['prompt']}") return { "modifiedPrompt": input["prompt"], # Optionally modify the prompt } + async def on_session_start(input, invocation): print(f"Session started from: {input['source']}") # "startup", "resume", "new" return { "additionalContext": "Session initialization context", } + async def on_session_end(input, invocation): print(f"Session ended: {input['reason']}") + async def on_error_occurred(input, invocation): print(f"Error in {input['errorContext']}: {input['error']}") return { "errorHandling": "retry", # "retry", "skip", or "abort" } + async with await client.create_session( on_permission_request=PermissionHandler.approve_all, model="gpt-5", @@ -962,6 +1000,7 @@ Register slash commands that users can invoke from the CLI TUI. When the user ty ```python from copilot.session import CommandDefinition, CommandContext, PermissionHandler + async def handle_deploy(ctx: CommandContext) -> None: print(f"Deploying with args: {ctx.args}") # ctx.session_id — the session where the command was invoked @@ -969,6 +1008,7 @@ async def handle_deploy(ctx: CommandContext) -> None: # ctx.command_name — command name without leading / (e.g. "deploy") # ctx.args — raw argument string (e.g. "production") + async with await client.create_session( on_permission_request=PermissionHandler.approve_all, commands=[ @@ -1030,11 +1070,14 @@ Shows a text input dialog with optional constraints: name = await session.ui.input("Enter your name:") # With options -email = await session.ui.input("Enter email:", { - "title": "Email Address", - "description": "We'll use this for notifications", - "format": "email", -}) +email = await session.ui.input( + "Enter email:", + { + "title": "Email Address", + "description": "We'll use this for notifications", + "format": "email", + }, +) ``` ### Custom Elicitation @@ -1042,17 +1085,19 @@ email = await session.ui.input("Enter email:", { For full control, use the `elicitation()` method with a custom JSON schema: ```python -result = await session.ui.elicitation({ - "message": "Configure deployment", - "requestedSchema": { - "type": "object", - "properties": { - "region": {"type": "string", "enum": ["us-east-1", "eu-west-1"]}, - "replicas": {"type": "number", "minimum": 1, "maximum": 10}, +result = await session.ui.elicitation( + { + "message": "Configure deployment", + "requestedSchema": { + "type": "object", + "properties": { + "region": {"type": "string", "enum": ["us-east-1", "eu-west-1"]}, + "replicas": {"type": "number", "minimum": 1, "maximum": 10}, + }, + "required": ["region"], }, - "required": ["region"], - }, -}) + } +) if result["action"] == "accept": region = result["content"]["region"] @@ -1066,6 +1111,7 @@ When the server (or an MCP tool) needs to ask the end-user a question, it sends ```python from copilot.session import ElicitationContext, ElicitationResult, PermissionHandler + async def handle_elicitation( context: ElicitationContext, ) -> ElicitationResult: @@ -1082,6 +1128,7 @@ async def handle_elicitation( "content": {"answer": "yes"}, } + async with await client.create_session( on_permission_request=PermissionHandler.approve_all, on_elicitation_request=handle_elicitation, @@ -1095,3 +1142,23 @@ When `on_elicitation_request` is provided, the SDK automatically: - Reports the `elicitation` capability on the session - Dispatches `elicitation.requested` events to your handler - Auto-cancels if your handler throws an error (so the server doesn't hang) + +## Development + +Install [uv](https://docs.astral.sh/uv/) and a supported [Node.js version](../nodejs/README.md#prerequisites), then from the repository root: + +```bash +cd nodejs +npm ci +``` + +```bash +cd test/harness +npm ci +``` + +```bash +cd python +uv sync +uv run pytest +``` diff --git a/python/copilot/__init__.py b/python/copilot/__init__.py index 6fb59a895..f7a71ebe9 100644 --- a/python/copilot/__init__.py +++ b/python/copilot/__init__.py @@ -41,6 +41,8 @@ GetStatusResponse, InProcessRuntimeConnection, LogLevel, + ManagedSettings, + ManagedSettingsPermissions, ModelBilling, ModelCapabilities, ModelInfo, @@ -85,6 +87,10 @@ GitHubTelemetryNotification, ModelBillingTokenPrices, ModelBillingTokenPricesLongContext, + PermissionDecisionContext, + PermissionDecisionOutcome, + PermissionDecisionSource, + PermissionDecisionSurface, ) from .generated.session_events import ( PermissionRequest, @@ -92,6 +98,10 @@ SessionEventType, ) from .session import ( + AgentStopHandler, + AgentStopHookInput, + AgentStopHookOutput, + AttributedPermissionResult, AutoModeSwitchHandler, AutoModeSwitchRequest, AutoModeSwitchResponse, @@ -110,6 +120,7 @@ ExitPlanModeHandler, ExitPlanModeRequest, ExitPlanModeResult, + GitHubMcpToolConfig, InfiniteSessionConfig, InputOptions, LargeToolOutputConfig, @@ -169,12 +180,17 @@ UserPromptSubmittedHandler, UserPromptSubmittedHookInput, UserPromptSubmittedHookOutput, + UserPromptTransformedHandler, + UserPromptTransformedHookInput, + UserPromptTransformedHookOutput, + create_attributed_permission_result, ) from .session_fs_provider import ( SessionFsFileInfo, SessionFsProvider, SessionFsSqliteProvider, SessionFsSqliteQueryResult, + SessionFsSqliteTransactionFailure, create_session_fs_adapter, ) from .tools import ( @@ -196,6 +212,10 @@ __version__ = "0.0.0.dev0" __all__ = [ + "AgentStopHandler", + "AgentStopHookInput", + "AgentStopHookOutput", + "AttributedPermissionResult", "AutoModeSwitchHandler", "AutoModeSwitchRequest", "AutoModeSwitchResponse", @@ -241,6 +261,7 @@ "GetAuthStatusResponse", "BearerTokenProvider", "GetStatusResponse", + "GitHubMcpToolConfig", "GitHubTelemetryClientInfo", "GitHubTelemetryEvent", "GitHubTelemetryNotification", @@ -260,6 +281,8 @@ "McpAuthStaticClientConfig", "McpAuthToken", "McpAuthWwwAuthenticateParams", + "ManagedSettings", + "ManagedSettingsPermissions", "ModelBilling", "ModelBillingTokenPrices", "ModelBillingTokenPricesLongContext", @@ -279,6 +302,10 @@ "PermissionNoResult", "PermissionRequest", "PermissionRequestResult", + "PermissionDecisionContext", + "PermissionDecisionOutcome", + "PermissionDecisionSource", + "PermissionDecisionSurface", "PingResponse", "PostToolUseHandler", "PostToolUseFailureHandler", @@ -318,6 +345,7 @@ "SessionFsProvider", "SessionFsSqliteProvider", "SessionFsSqliteQueryResult", + "SessionFsSqliteTransactionFailure", "SessionHooks", "SessionLimitsConfig", "SessionLifecycleEvent", @@ -352,7 +380,11 @@ "UserPromptSubmittedHandler", "UserPromptSubmittedHookInput", "UserPromptSubmittedHookOutput", + "UserPromptTransformedHandler", + "UserPromptTransformedHookInput", + "UserPromptTransformedHookOutput", "convert_mcp_call_tool_result", + "create_attributed_permission_result", "create_session_fs_adapter", "define_tool", ] diff --git a/python/copilot/_mode.py b/python/copilot/_mode.py index d8baf2663..1a9ed6e1f 100644 --- a/python/copilot/_mode.py +++ b/python/copilot/_mode.py @@ -251,6 +251,22 @@ def _enable_skills_default( return _empty_mode_bool_default(mode, supplied, False) +def _custom_agents_local_only_default( + mode: CopilotClientMode | None, + supplied: bool | None, +) -> bool | None: + """Empty mode defaults custom agents to local-only; caller value wins.""" + return _empty_mode_bool_default(mode, supplied, True) + + +def _enable_experimental_mode_default( + mode: CopilotClientMode | None, + supplied: bool | None, +) -> bool | None: + """Empty mode defaults experimental mode to False; caller value wins.""" + return _empty_mode_bool_default(mode, supplied, False) + + def _mcp_oauth_token_storage_default( mode: CopilotClientMode | None, supplied: Literal["persistent", "in-memory"] | None, @@ -294,8 +310,8 @@ def _post_create_options_patch( "skipCustomInstructions": ( skip_custom_instructions if skip_custom_instructions is not None else True ), - "customAgentsLocalOnly": ( - custom_agents_local_only if custom_agents_local_only is not None else True + "customAgentsLocalOnly": _custom_agents_local_only_default( + mode, custom_agents_local_only ), "coauthorEnabled": coauthor_enabled if coauthor_enabled is not None else False, "manageScheduleEnabled": ( diff --git a/python/copilot/client.py b/python/copilot/client.py index 401618355..6cdd765c3 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -37,7 +37,9 @@ from ._mode import ( CopilotClientMode, ToolSet, + _custom_agents_local_only_default, _embedding_cache_storage_default, + _enable_experimental_mode_default, _enable_file_hooks_default, _enable_host_git_operations_default, _enable_on_demand_instruction_discovery_default, @@ -94,6 +96,7 @@ DefaultAgentConfig, ElicitationHandler, ExitPlanModeHandler, + GitHubMcpToolConfig, InfiniteSessionConfig, LargeToolOutputConfig, McpAuthHandler, @@ -244,6 +247,63 @@ def _capi_session_options_to_wire(options: CapiSessionOptions) -> dict[str, Any] return wire +@dataclass +class ManagedSettingsPermissions: + """Permissions-only managed policy injected via :class:`ManagedSettings`. + + Rule strings use the same vocabulary the runtime accepts for fetched + managed policy (e.g. ``"Read(**)"``, ``"Shell(git push *)"``); malformed + rules are rejected by the runtime at session creation. + """ + + disable_bypass_permissions_mode: Literal["disable"] | None = None + """When ``"disable"``, turns off bypass-permissions ("yolo") mode for the + session. Deny-wins: no other layer can re-enable it. Sent on the wire as + ``disableBypassPermissionsMode``.""" + deny: list[str] | None = None + """Operations that must always be denied. Unioned across managed layers.""" + ask: list[str] | None = None + """Operations that must prompt for approval. Unioned across managed layers.""" + allow: list[str] | None = None + """Operations permitted without prompting. Every declared ``allow`` list + across managed layers must admit an operation for it to be allowed.""" + + +@dataclass +class ManagedSettings: + """Host-injected enterprise managed settings for a session. + + Unlike ``enable_managed_settings`` — which asks the runtime to *self-fetch* + account/org and device policy — this supplies the managed policy directly. + The runtime validates it with the same managed-permission parser it uses + for fetched policy and composes it restrictively with any self-fetched + (server) and device-managed (MDM) layers. + + The first supported contract is permissions-only; unknown sibling keys are + rejected by the runtime. Serialized on the wire as ``managedSettings``. + """ + + permissions: ManagedSettingsPermissions | None = None + """Managed permission policy for the session.""" + + +def _managed_settings_to_dict(settings: ManagedSettings) -> dict[str, Any]: + wire: dict[str, Any] = {} + permissions = settings.permissions + if permissions is not None: + perms: dict[str, Any] = {} + if permissions.disable_bypass_permissions_mode is not None: + perms["disableBypassPermissionsMode"] = permissions.disable_bypass_permissions_mode + if permissions.deny is not None: + perms["deny"] = list(permissions.deny) + if permissions.ask is not None: + perms["ask"] = list(permissions.ask) + if permissions.allow is not None: + perms["allow"] = list(permissions.allow) + wire["permissions"] = perms + return wire + + # Implicit provider name for the singular, whole-session ``provider`` config. # Named providers are keyed by their own ``name``. _DEFAULT_BEARER_TOKEN_PROVIDER_NAME = "default" @@ -334,6 +394,22 @@ def _tool_search_to_wire(config: Mapping[str, Any]) -> dict[str, Any]: return wire +def _github_mcp_tool_config_to_wire(config: Mapping[str, Any]) -> dict[str, Any]: + """Convert a ``GitHubMcpToolConfig`` mapping to wire format.""" + wire: dict[str, Any] = {} + if "enable_all_tools" in config: + wire["enableAllTools"] = config["enable_all_tools"] + if "additional_toolsets" in config: + wire["additionalToolsets"] = config["additional_toolsets"] + if "additional_tools" in config: + wire["additionalTools"] = config["additional_tools"] + if "enable_insiders_mode" in config: + wire["enableInsidersMode"] = config["enable_insiders_mode"] + if "disable_form_deferral" in config: + wire["disableFormDeferral"] = config["disable_form_deferral"] + return wire + + class TelemetryConfig(TypedDict, total=False): """Configuration for OpenTelemetry integration with the Copilot CLI.""" @@ -579,6 +655,7 @@ class _CopilotClientOptions: env: dict[str, str] | None = None github_token: str | None = None base_directory: str | None = None + builtin_plugin_directories: tuple[str, ...] = () use_logged_in_user: bool | None = None telemetry: TelemetryConfig | None = None session_fs: SessionFsConfig | None = None @@ -1351,6 +1428,7 @@ def __init__( env: dict[str, str] | None = None, github_token: str | None = None, base_directory: str | None = None, + builtin_plugin_directories: Sequence[str] | None = None, use_logged_in_user: bool | None = None, telemetry: TelemetryConfig | None = None, session_fs: SessionFsConfig | None = None, @@ -1386,6 +1464,9 @@ def __init__( config, etc.). Sets the ``COPILOT_HOME`` environment variable on the spawned runtime. When ``None``, the runtime defaults to ``~/.copilot``. + builtin_plugin_directories: Absolute paths to trusted plugin + directories bundled by the host. When non-empty, the complete + set is registered during startup before sessions can be created. use_logged_in_user: Use the logged-in user for authentication. ``None`` (default) resolves to ``True`` unless ``github_token`` is set. @@ -1433,6 +1514,7 @@ def __init__( env=env, github_token=github_token, base_directory=base_directory, + builtin_plugin_directories=tuple(builtin_plugin_directories or ()), use_logged_in_user=use_logged_in_user, telemetry=telemetry, session_fs=session_fs, @@ -1449,6 +1531,11 @@ def __init__( else _resolve_default_connection(os.environ) ) _validate_environment_options(options, connection) + for path in options.builtin_plugin_directories: + if not os.path.isabs(path): + raise ValueError( + f"builtin_plugin_directories must contain only absolute paths: {path}" + ) _require_storage_for_empty_mode( mode=options.mode, base_directory=options.base_directory, @@ -1731,6 +1818,17 @@ async def start(self) -> None: start_time, ) + if self._options.builtin_plugin_directories: + assert self._client is not None + try: + await self._client.request( + "plugins.builtin.set", + {"paths": list(self._options.builtin_plugin_directories)}, + ) + except Exception: + await self.force_stop() + raise + if self._session_fs_config: session_fs_start = time.perf_counter() await self._set_session_fs_provider() @@ -2001,6 +2099,7 @@ async def create_session( client_name: str | None = None, reasoning_effort: ReasoningEffort | None = None, reasoning_summary: ReasoningSummary | None = None, + enable_experimental_mode: bool | None = None, context_tier: ContextTier | None = None, tools: list[Tool] | None = None, system_message: SystemMessageConfig | None = None, @@ -2010,12 +2109,14 @@ async def create_session( on_user_input_request: UserInputHandler | None = None, hooks: SessionHooks | None = None, working_directory: str | None = None, + additional_directories: list[str] | None = None, provider: ProviderConfig | None = None, capi: CapiSessionOptions | None = None, providers: list[NamedProviderConfig] | None = None, models: list[ProviderModelConfig] | None = None, enable_session_telemetry: bool | None = None, enable_citations: bool | None = None, + enable_file_change_tracking: bool | None = None, excluded_builtin_agents: list[str] | None = None, session_limits: SessionLimitsConfig | None = None, skip_custom_instructions: bool | None = None, @@ -2044,6 +2145,7 @@ async def create_session( plugin_directories: list[str] | None = None, instruction_directories: list[str] | None = None, disabled_skills: list[str] | None = None, + disabled_mcp_servers: list[str] | None = None, infinite_sessions: InfiniteSessionConfig | None = None, large_output: LargeToolOutputConfig | None = None, memory: MemoryConfiguration | None = None, @@ -2067,6 +2169,8 @@ async def create_session( canvas_handler: CanvasHandler | None = None, exp_assignments: CopilotExpAssignmentResponse | None = None, enable_managed_settings: bool | None = None, + github_mcp_tool_config: GitHubMcpToolConfig | None = None, + managed_settings: ManagedSettings | None = None, ) -> CopilotSession: """ Create a new conversation session with the Copilot CLI. @@ -2086,6 +2190,9 @@ async def create_session( reasoning_summary: Reasoning summary mode for supported models. Use ``"none"`` to suppress summary output regardless of whether reasoning is enabled. + enable_experimental_mode: Controls whether the session enables + experimental features. Defaults to ``False`` in ``"empty"`` + mode; otherwise the runtime decides when omitted. context_tier: Context window tier for models that support it. Use ``"long_context"`` to pin the session to the long-context tier. tools: Custom tools to register with the session. @@ -2126,6 +2233,8 @@ async def create_session( OpenTelemetry configuration. enable_citations: **Experimental.** Enables native model citations for supported providers. + enable_file_change_tracking: Opts in to capturing file changes from the + first turn for session rewind and cumulative session diff. excluded_builtin_agents: Built-in agent names to exclude from the session. Excluded built-in agents are hidden from discovery and cannot be selected or invoked unless a custom agent with the same @@ -2153,13 +2262,9 @@ async def create_session( including tool visibility controls. agent: Agent to use for the session. config_directory: Override for the configuration directory. - enable_config_discovery: When True, automatically discovers MCP server - configurations (e.g. ``.mcp.json``, ``.vscode/mcp.json``) and skill - directories from the working directory and merges them with any - explicitly provided ``mcp_servers`` and ``skill_directories``, with - explicit values taking precedence on name collision. Custom instruction - files (``.github/copilot-instructions.md``, ``AGENTS.md``, etc.) are - always loaded regardless of this setting. + enable_config_discovery: Enables runtime discovery of supported + configuration. Explicitly supplied configuration takes precedence + over discovered values. skip_embedding_retrieval: When True, skips embedding-based retrieval. organization_custom_instructions: Organization-level custom instructions. enable_on_demand_instruction_discovery: Enables on-demand instruction file @@ -2172,6 +2277,10 @@ async def create_session( instruction_directories: Additional directories to search for custom instruction files. disabled_skills: Skills to disable. + disabled_mcp_servers: Exact MCP server names to disable only for this + session. Disabled servers are not started or authenticated on + create or cold resume; a resident resume cannot stop servers + already running. This does not change global MCP settings. infinite_sessions: Infinite session configuration. memory: Session memory configuration. cloud: Creates a remote session in the cloud instead of a local @@ -2187,6 +2296,16 @@ async def create_session( override) is on; otherwise the request is silently dropped. Inspect ``capabilities.ui.mcpApps`` on the create response to detect the drop. + github_mcp_tool_config: Configuration for the built-in GitHub MCP + server, sent as ``githubMcpToolConfig`` on ``session.create``. + Supports ``enable_all_tools``, ``additional_toolsets``, + ``additional_tools``, ``enable_insiders_mode``, and + ``disable_form_deferral``. Setting ``disable_form_deferral`` + makes form-backed GitHub write tools execute directly instead + of returning an awaiting-form stub; it does not enable MCP Apps + on its own and has no effect unless MCP Apps are enabled for + the session (see ``enable_mcp_apps``). Omitted from the wire + payload entirely when None. exp_assignments: ExP assignment ("flight") data injected by a trusted integrator, in the same JSON shape the Copilot CLI fetches from the experimentation service @@ -2205,6 +2324,15 @@ async def create_session( expected to reject session creation (fail-closed). When unset, behaves exactly as before. Sent on the wire as ``enableManagedSettings``. + managed_settings: Host-injected enterprise managed settings for the + session. Supplies managed policy directly instead of + self-fetching; the runtime validates it and composes it + restrictively with any self-fetched (server) and device-managed + layers. Startup-only and not persisted: re-supply on + :meth:`resume_session` (omitting it clears the injected layer). + May be combined with ``enable_managed_settings``. Requires a + runtime whose RPC schema includes ``managedSettings``. Sent on + the wire as ``managedSettings``. Returns: A :class:`CopilotSession` instance for the new session. @@ -2246,6 +2374,8 @@ async def create_session( definition["defer"] = tool.defer if tool.metadata is not None: definition["metadata"] = tool.metadata + if tool.is_terminal: + definition["isTerminal"] = True tool_defs.append(definition) # Empty-mode validation and normalization @@ -2271,6 +2401,8 @@ async def create_session( ) enable_session_store = _enable_session_store_default(mode, enable_session_store) enable_skills = _enable_skills_default(mode, enable_skills) + custom_agents_local_only = _custom_agents_local_only_default(mode, custom_agents_local_only) + enable_experimental_mode = _enable_experimental_mode_default(mode, enable_experimental_mode) payload: dict[str, Any] = {} if model: @@ -2281,6 +2413,8 @@ async def create_session( payload["reasoningEffort"] = reasoning_effort if reasoning_summary: payload["reasoningSummary"] = reasoning_summary + if enable_experimental_mode is not None: + payload["isExperimentalMode"] = enable_experimental_mode if context_tier: payload["contextTier"] = context_tier if tool_defs: @@ -2312,6 +2446,8 @@ async def create_session( payload["requestElicitation"] = bool(on_elicitation_request) if enable_mcp_apps: payload["requestMcpApps"] = True + if github_mcp_tool_config is not None: + payload["githubMcpToolConfig"] = _github_mcp_tool_config_to_wire(github_mcp_tool_config) payload["requestExitPlanMode"] = bool(on_exit_plan_mode_request) payload["requestAutoModeSwitch"] = bool(on_auto_mode_switch_request) @@ -2345,9 +2481,15 @@ async def create_session( if enable_managed_settings is not None: payload["enableManagedSettings"] = enable_managed_settings + # Host-injected managed settings (permissions-only contract) + if managed_settings is not None: + payload["managedSettings"] = _managed_settings_to_dict(managed_settings) + # Add working directory if provided if working_directory: payload["workingDirectory"] = working_directory + if additional_directories: + payload["additionalDirectories"] = additional_directories # Add streaming option if provided if streaming is not None: @@ -2383,6 +2525,8 @@ async def create_session( payload["enableSessionTelemetry"] = enable_session_telemetry if enable_citations is not None: payload["enableCitations"] = enable_citations + if enable_file_change_tracking is not None: + payload["enableFileChangeTracking"] = enable_file_change_tracking if excluded_builtin_agents is not None: payload["excludedBuiltinAgents"] = excluded_builtin_agents if session_limits is not None: @@ -2409,6 +2553,8 @@ async def create_session( payload["customAgents"] = [ self._convert_custom_agent_to_wire_format(agent) for agent in custom_agents ] + if custom_agents_local_only is not None: + payload["customAgentsLocalOnly"] = custom_agents_local_only # Add default agent configuration if provided if default_agent: @@ -2455,6 +2601,8 @@ async def create_session( # Add disabled skills configuration if provided if disabled_skills: payload["disabledSkills"] = disabled_skills + if disabled_mcp_servers is not None: + payload["disabledMcpServers"] = disabled_mcp_servers # Add infinite sessions configuration if provided if infinite_sessions: @@ -2521,7 +2669,13 @@ def _initialize_session(sid: str) -> CopilotSession: to a registered session. """ setup_start = time.perf_counter() - s = CopilotSession(sid, self._client, workspace_path=None) + s = CopilotSession( + sid, + self._client, + workspace_path=None, + managed_settings_enabled=enable_managed_settings is True + or managed_settings is not None, + ) if self._session_fs_config: if create_session_fs_handler is None: raise ValueError( @@ -2673,6 +2827,7 @@ async def resume_session( client_name: str | None = None, reasoning_effort: ReasoningEffort | None = None, reasoning_summary: ReasoningSummary | None = None, + enable_experimental_mode: bool | None = None, context_tier: ContextTier | None = None, tools: list[Tool] | None = None, system_message: SystemMessageConfig | None = None, @@ -2682,12 +2837,14 @@ async def resume_session( on_user_input_request: UserInputHandler | None = None, hooks: SessionHooks | None = None, working_directory: str | None = None, + additional_directories: list[str] | None = None, provider: ProviderConfig | None = None, capi: CapiSessionOptions | None = None, providers: list[NamedProviderConfig] | None = None, models: list[ProviderModelConfig] | None = None, enable_session_telemetry: bool | None = None, enable_citations: bool | None = None, + enable_file_change_tracking: bool | None = None, excluded_builtin_agents: list[str] | None = None, session_limits: SessionLimitsConfig | None = None, skip_custom_instructions: bool | None = None, @@ -2716,6 +2873,7 @@ async def resume_session( plugin_directories: list[str] | None = None, instruction_directories: list[str] | None = None, disabled_skills: list[str] | None = None, + disabled_mcp_servers: list[str] | None = None, infinite_sessions: InfiniteSessionConfig | None = None, large_output: LargeToolOutputConfig | None = None, memory: MemoryConfiguration | None = None, @@ -2740,6 +2898,8 @@ async def resume_session( open_canvases: list[OpenCanvasInstance] | None = None, exp_assignments: CopilotExpAssignmentResponse | None = None, enable_managed_settings: bool | None = None, + github_mcp_tool_config: GitHubMcpToolConfig | None = None, + managed_settings: ManagedSettings | None = None, ) -> CopilotSession: """ Resume an existing conversation session by its ID. @@ -2759,6 +2919,9 @@ async def resume_session( reasoning_summary: Reasoning summary mode for supported models. Use ``"none"`` to suppress summary output regardless of whether reasoning is enabled. + enable_experimental_mode: Controls whether the session enables + experimental features. Defaults to ``False`` in ``"empty"`` + mode; otherwise the runtime decides when omitted. context_tier: Context window tier for models that support it. Use ``"long_context"`` to pin the session to the long-context tier. tools: Custom tools to register with the session. @@ -2799,6 +2962,9 @@ async def resume_session( OpenTelemetry configuration. enable_citations: **Experimental.** Enables native model citations for supported providers. + enable_file_change_tracking: Opts in to capturing file changes for + session rewind and cumulative session diff when the resumed session + has a valid baseline. Earlier untracked changes cannot be reconstructed. excluded_builtin_agents: Built-in agent names to exclude from the resumed session. Excluded built-in agents are hidden from discovery and cannot be selected or invoked unless a custom agent with the @@ -2826,13 +2992,9 @@ async def resume_session( including tool visibility controls. agent: Agent to use for the session. config_directory: Override for the configuration directory. - enable_config_discovery: When True, automatically discovers MCP server - configurations (e.g. ``.mcp.json``, ``.vscode/mcp.json``) and skill - directories from the working directory and merges them with any - explicitly provided ``mcp_servers`` and ``skill_directories``, with - explicit values taking precedence on name collision. Custom instruction - files (``.github/copilot-instructions.md``, ``AGENTS.md``, etc.) are - always loaded regardless of this setting. + enable_config_discovery: Enables runtime discovery of supported + configuration. Explicitly supplied configuration takes precedence + over discovered values. skip_embedding_retrieval: When True, skips embedding-based retrieval. organization_custom_instructions: Organization-level custom instructions. enable_on_demand_instruction_discovery: Enables on-demand instruction file @@ -2845,6 +3007,10 @@ async def resume_session( instruction_directories: Additional directories to search for custom instruction files. disabled_skills: Skills to disable. + disabled_mcp_servers: Exact MCP server names to disable only for this + session. Disabled servers are not started or authenticated on + create or cold resume; a resident resume cannot stop servers + already running. This does not change global MCP settings. infinite_sessions: Infinite session configuration. memory: Session memory configuration. on_event: Callback for session events. @@ -2857,6 +3023,16 @@ async def resume_session( override) is on; otherwise the request is silently dropped. Inspect ``capabilities.ui.mcpApps`` on the resume response to detect the drop. + github_mcp_tool_config: Configuration for the built-in GitHub MCP + server, sent as ``githubMcpToolConfig`` on ``session.resume``. + Supports ``enable_all_tools``, ``additional_toolsets``, + ``additional_tools``, ``enable_insiders_mode``, and + ``disable_form_deferral``. Setting ``disable_form_deferral`` + makes form-backed GitHub write tools execute directly instead + of returning an awaiting-form stub; it does not enable MCP Apps + on its own and has no effect unless MCP Apps are enabled for + the session (see ``enable_mcp_apps``). Omitted from the wire + payload entirely when None. continue_pending_work: When True, instructs the runtime to continue any tool calls or permission prompts that were still pending when the session was last suspended. When False (the default), the runtime @@ -2879,6 +3055,11 @@ async def resume_session( expected to reject session creation (fail-closed). When unset, behaves exactly as before. Sent on the wire as ``enableManagedSettings``. + managed_settings: Host-injected enterprise managed settings for the + session. Must be re-supplied on resume; it replaces the prior + injected layer, and omitting it clears that layer so warm and + cold resume behave identically. See :meth:`create_session`. Sent + on the wire as ``managedSettings``. Returns: A :class:`CopilotSession` instance for the resumed session. @@ -2922,6 +3103,8 @@ async def resume_session( definition["defer"] = tool.defer if tool.metadata is not None: definition["metadata"] = tool.metadata + if tool.is_terminal: + definition["isTerminal"] = True tool_defs.append(definition) # Empty-mode validation and normalization @@ -2944,6 +3127,8 @@ async def resume_session( ) enable_session_store = _enable_session_store_default(mode, enable_session_store) enable_skills = _enable_skills_default(mode, enable_skills) + custom_agents_local_only = _custom_agents_local_only_default(mode, custom_agents_local_only) + enable_experimental_mode = _enable_experimental_mode_default(mode, enable_experimental_mode) payload: dict[str, Any] = {"sessionId": session_id} @@ -2955,6 +3140,8 @@ async def resume_session( payload["reasoningEffort"] = reasoning_effort if reasoning_summary: payload["reasoningSummary"] = reasoning_summary + if enable_experimental_mode is not None: + payload["isExperimentalMode"] = enable_experimental_mode if context_tier: payload["contextTier"] = context_tier if tool_defs: @@ -2983,6 +3170,8 @@ async def resume_session( payload["enableSessionTelemetry"] = enable_session_telemetry if enable_citations is not None: payload["enableCitations"] = enable_citations + if enable_file_change_tracking is not None: + payload["enableFileChangeTracking"] = enable_file_change_tracking if excluded_builtin_agents is not None: payload["excludedBuiltinAgents"] = excluded_builtin_agents if session_limits is not None: @@ -3014,6 +3203,8 @@ async def resume_session( payload["requestElicitation"] = bool(on_elicitation_request) if enable_mcp_apps: payload["requestMcpApps"] = True + if github_mcp_tool_config is not None: + payload["githubMcpToolConfig"] = _github_mcp_tool_config_to_wire(github_mcp_tool_config) payload["requestExitPlanMode"] = bool(on_exit_plan_mode_request) payload["requestAutoModeSwitch"] = bool(on_auto_mode_switch_request) @@ -3042,8 +3233,14 @@ async def resume_session( if enable_managed_settings is not None: payload["enableManagedSettings"] = enable_managed_settings + # Host-injected managed settings (permissions-only contract) + if managed_settings is not None: + payload["managedSettings"] = _managed_settings_to_dict(managed_settings) + if working_directory: payload["workingDirectory"] = working_directory + if additional_directories: + payload["additionalDirectories"] = additional_directories if config_directory: payload["configDir"] = config_directory if enable_config_discovery is not None: @@ -3082,6 +3279,8 @@ async def resume_session( payload["customAgents"] = [ self._convert_custom_agent_to_wire_format(a) for a in custom_agents ] + if custom_agents_local_only is not None: + payload["customAgentsLocalOnly"] = custom_agents_local_only # Add default agent configuration if provided if default_agent: @@ -3097,6 +3296,8 @@ async def resume_session( payload["instructionDirectories"] = instruction_directories if disabled_skills: payload["disabledSkills"] = disabled_skills + if disabled_mcp_servers is not None: + payload["disabledMcpServers"] = disabled_mcp_servers if infinite_sessions: wire_config: dict[str, Any] = {} @@ -3144,7 +3345,13 @@ async def resume_session( # Create and register the session before issuing the RPC so that # events emitted by the CLI (e.g. session.start) are not dropped. setup_start = time.perf_counter() - session = CopilotSession(session_id, self._client, workspace_path=None) + session = CopilotSession( + session_id, + self._client, + workspace_path=None, + managed_settings_enabled=enable_managed_settings is True + or managed_settings is not None, + ) if self._session_fs_config: if create_session_fs_handler is None: raise ValueError( @@ -3386,7 +3593,7 @@ async def list_sessions(self, filter: SessionListFilter | None = None) -> list[S Example: >>> sessions = await client.list_sessions() >>> for session in sessions: - ... print(f"Session: {session.sessionId}") + ... print(f"Session: {session.session_id}") >>> # Filter sessions by repository >>> from copilot.client import SessionListFilter >>> filtered = await client.list_sessions(SessionListFilter(repository="owner/repo")) diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py index 713e78cca..103149088 100644 --- a/python/copilot/generated/rpc.py +++ b/python/copilot/generated/rpc.py @@ -126,6 +126,7 @@ class CopilotUserResponseEndpoints: """Endpoint URLs from the raw Copilot `/copilot_internal/v2/token` user-response passthrough.""" api: str | None = None + exp: str | None = None origin_tracker: str | None = None proxy: str | None = None telemetry: str | None = None @@ -134,15 +135,18 @@ class CopilotUserResponseEndpoints: def from_dict(obj: Any) -> 'CopilotUserResponseEndpoints': assert isinstance(obj, dict) api = from_union([from_str, from_none], obj.get("api")) + exp = from_union([from_str, from_none], obj.get("exp")) origin_tracker = from_union([from_str, from_none], obj.get("origin-tracker")) proxy = from_union([from_str, from_none], obj.get("proxy")) telemetry = from_union([from_str, from_none], obj.get("telemetry")) - return CopilotUserResponseEndpoints(api, origin_tracker, proxy, telemetry) + return CopilotUserResponseEndpoints(api, exp, origin_tracker, proxy, telemetry) def to_dict(self) -> dict: result: dict = {} if self.api is not None: result["api"] = from_union([from_str, from_none], self.api) + if self.exp is not None: + result["exp"] = from_union([from_str, from_none], self.exp) if self.origin_tracker is not None: result["origin-tracker"] = from_union([from_str, from_none], self.origin_tracker) if self.proxy is not None: @@ -1015,38 +1019,6 @@ def to_dict(self) -> dict: result["input"] = from_union([from_str, from_none], self.input) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class CommandsListRequest: - """Optional filters controlling which command sources to include in the listing.""" - - include_builtins: bool | None = None - """Include runtime built-in commands""" - - include_client_commands: bool | None = None - """Include commands registered by protocol clients, including SDK clients and extensions""" - - include_skills: bool | None = None - """Include enabled user-invocable skills and commands""" - - @staticmethod - def from_dict(obj: Any) -> 'CommandsListRequest': - assert isinstance(obj, dict) - include_builtins = from_union([from_bool, from_none], obj.get("includeBuiltins")) - include_client_commands = from_union([from_bool, from_none], obj.get("includeClientCommands")) - include_skills = from_union([from_bool, from_none], obj.get("includeSkills")) - return CommandsListRequest(include_builtins, include_client_commands, include_skills) - - def to_dict(self) -> dict: - result: dict = {} - if self.include_builtins is not None: - result["includeBuiltins"] = from_union([from_bool, from_none], self.include_builtins) - if self.include_client_commands is not None: - result["includeClientCommands"] = from_union([from_bool, from_none], self.include_client_commands) - if self.include_skills is not None: - result["includeSkills"] = from_union([from_bool, from_none], self.include_skills) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class CommandsRespondToQueuedCommandRequest: @@ -1093,29 +1065,6 @@ def to_dict(self) -> dict: result["success"] = from_bool(self.success) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class CompletionsGetTriggerCharactersResult: - """Characters that, when typed in the composer, should trigger a `completions.request`. - Empty when the session has no host-driven completions (e.g. local sessions, or a relay - host that does not advertise `completionTriggerCharacters`). - """ - trigger_characters: list[str] - """Trigger characters advertised by the host (e.g. `["@", "#"]`). Empty disables host-driven - completions for the session. - """ - - @staticmethod - def from_dict(obj: Any) -> 'CompletionsGetTriggerCharactersResult': - assert isinstance(obj, dict) - trigger_characters = from_list(from_str, obj.get("triggerCharacters")) - return CompletionsGetTriggerCharactersResult(trigger_characters) - - def to_dict(self) -> dict: - result: dict = {} - result["triggerCharacters"] = from_list(from_str, self.trigger_characters) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class CompletionsRequestRequest: @@ -1244,11 +1193,14 @@ class _ConnectRequest: enable_git_hub_telemetry_forwarding: bool | None = None """Opt this connection in to GitHub telemetry forwarding for its lifetime. When set, the runtime forwards every internal telemetry event it emits — across all sessions, plus - sessionless events — to this connection over the `gitHubTelemetry.event` notification, in - addition to the runtime's normal GitHub/CTS emission (dual-write). Intended for - first-party hosts that re-emit the events into their own telemetry stores. Both - unrestricted and restricted events are forwarded, each tagged with a `restricted` - discriminator; a backstop drops restricted events when restricted telemetry is disabled. + sessionless events — to this connection over the `gitHubTelemetry.event` notification. + Regular events are also written to the runtime's normal GitHub/CTS path (dual-write); + host-only compatibility events are forward-only and intentionally skip that path. + Intended for first-party hosts that re-emit the events into their own telemetry stores. + Both unrestricted and restricted events are forwarded, each tagged with a `restricted` + discriminator; a backstop drops restricted events when restricted telemetry is disabled — + using the process-global gate for ordinary events and an explicit session-scoped decision + for host-only events. """ token: str | None = None """Connection token; required when the server was started with COPILOT_CONNECTION_TOKEN""" @@ -1334,6 +1286,52 @@ def to_dict(self) -> dict: result["owner"] = from_str(self.owner) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ContentExclusionCheckPathsRequest: + """Local file system absolute paths within the session working directory to check against + its content-exclusion policy. + """ + paths: list[str] + """Local file system absolute paths within the session working directory to check. Results + are returned in the same order, including duplicates. + """ + + @staticmethod + def from_dict(obj: Any) -> 'ContentExclusionCheckPathsRequest': + assert isinstance(obj, dict) + paths = from_list(from_str, obj.get("paths")) + return ContentExclusionCheckPathsRequest(paths) + + def to_dict(self) -> dict: + result: dict = {} + result["paths"] = from_list(from_str, self.paths) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ContentExclusionPathCheck: + """Content-exclusion decision for one requested path.""" + + excluded: bool + """Whether the session's complete content-exclusion policy excludes the path.""" + + path: str + """The path supplied by the caller.""" + + @staticmethod + def from_dict(obj: Any) -> 'ContentExclusionPathCheck': + assert isinstance(obj, dict) + excluded = from_bool(obj.get("excluded")) + path = from_str(obj.get("path")) + return ContentExclusionPathCheck(excluded, path) + + def to_dict(self) -> dict: + result: dict = {} + result["excluded"] = from_bool(self.excluded) + result["path"] = from_str(self.path) + return result + # Experimental: this type is part of an experimental API and may change or be removed. class ContentFilterMode(Enum): """Controls how MCP tool result content is filtered: none leaves content unchanged, markdown @@ -1811,6 +1809,90 @@ def to_dict(self) -> dict: result["path"] = from_union([from_str, from_none], self.path) return result +# Experimental: this type is part of an experimental API and may change or be removed. +class DisableBypassPermissionsMode(Enum): + """When set to `disable`, prevents bypass/allow-all permission modes.""" + + DISABLE = "disable" + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class DiscoveredExtensionPlugin: + """Containing plugin metadata for plugin-contributed extensions + + Installed plugin that contributes a discovered extension. + """ + name: str + """Installed plugin name""" + + @staticmethod + def from_dict(obj: Any) -> 'DiscoveredExtensionPlugin': + assert isinstance(obj, dict) + name = from_str(obj.get("name")) + return DiscoveredExtensionPlugin(name) + + def to_dict(self) -> dict: + result: dict = {} + result["name"] = from_str(self.name) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +class DiscoveredExtensionSource(Enum): + """Discovery source + + Persisted extension discovery source + """ + PLUGIN = "plugin" + USER = "user" + +# Experimental: this type is part of an experimental API and may change or be removed. +class DiscoveredExtensionMode(Enum): + """Effective extension loading and agent-management mode + + Effective extension loading mode. Defaults to load_and_augment when unset. + """ + DISABLED = "disabled" + LOAD_AND_AUGMENT = "load_and_augment" + LOAD_ONLY = "load_only" + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class DiscoveredExtensionsDisableRequest: + """Source-qualified extension identifiers to persistently disable for future sessions.""" + + ids: list[str] + """Source-qualified user or plugin extension IDs to disable""" + + @staticmethod + def from_dict(obj: Any) -> 'DiscoveredExtensionsDisableRequest': + assert isinstance(obj, dict) + ids = from_list(from_str, obj.get("ids")) + return DiscoveredExtensionsDisableRequest(ids) + + def to_dict(self) -> dict: + result: dict = {} + result["ids"] = from_list(from_str, self.ids) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class DiscoveredExtensionsEnableRequest: + """Source-qualified extension identifiers to persistently enable for future sessions.""" + + ids: list[str] + """Source-qualified user or plugin extension IDs to enable""" + + @staticmethod + def from_dict(obj: Any) -> 'DiscoveredExtensionsEnableRequest': + assert isinstance(obj, dict) + ids = from_list(from_str, obj.get("ids")) + return DiscoveredExtensionsEnableRequest(ids) + + def to_dict(self) -> dict: + result: dict = {} + result["ids"] = from_list(from_str, self.ids) + return result + # Experimental: this type is part of an experimental API and may change or be removed. class DiscoveredMCPServerType(Enum): """Server transport type: stdio, http, sse (deprecated), or memory""" @@ -1875,6 +1957,28 @@ class EventsAgentScope(Enum): ALL = "all" PRIMARY = "primary" +# Experimental: this type is part of an experimental API and may change or be removed. +class EventsReadDirection(Enum): + """Direction to page through the session's persisted event history. 'forward' (default) + pages from the cursor toward newer events (or from the start of history when no cursor is + given). 'backward' enables tail-first reads: with no cursor it returns the NEWEST `max` + events, and the returned cursor pages toward OLDER events on subsequent backward reads. + Events within a returned batch are always in chronological (oldest-to-newest) order, even + for a backward read. Backward reads cover PERSISTED history only; ephemeral events are + never returned by a backward read. `direction` selects the INITIAL read only: the + returned cursor is self-describing, so a continuation read pages in the cursor's own + direction regardless of the `direction` passed alongside it — a forward cursor always + pages forward and a backward cursor always pages backward. Pass the direction that + matches the cursor to avoid confusion. + + Direction to page through the session's persisted event history. 'forward' pages from the + cursor toward newer events; 'backward' returns the newest window first (tail-first) and + pages toward older events. Events within a returned batch are always chronological + (oldest-to-newest), even for a backward read. + """ + BACKWARD = "backward" + FORWARD = "forward" + # Experimental: this type is part of an experimental API and may change or be removed. class EventLogTypes(Enum): EMPTY = "*" @@ -1928,7 +2032,22 @@ def to_dict(self) -> dict: class EventsCursorStatus(Enum): """Cursor status: 'ok' means the cursor was applied successfully; 'expired' means the cursor referred to an event that no longer exists in history (e.g. truncated or compacted away) - and the read started from the beginning of the remaining history. + and the read fell back to a boundary of the remaining history (the beginning for a + forward read, the tail for a backward read). The fallback page is a fresh boundary + snapshot, not a continuation of the requested cursor, so it may overlap already-rendered + events; on 'expired' a consumer should reset/rebase its pagination state (or deduplicate + by event id) before continuing from the returned cursor. + + Cursor status: 'ok' means the cursor was applied successfully; 'expired' means the cursor + referred to an event that no longer exists in history (e.g. truncated or compacted away) + and the read fell back to a boundary of the remaining history. For a forward read the + fallback starts from the beginning of the remaining history; for a backward read it falls + back to the tail (the newest window). Because the fallback page is a fresh boundary + snapshot rather than a continuation of the requested cursor, it may overlap events the + consumer has already rendered — a backward fallback to the tail in particular can repeat + the newest window. On 'expired', consumers should reset or rebase their local pagination + state (or deduplicate by event id) before continuing from the returned cursor rather than + blindly appending/prepending the fallback page. """ EXPIRED = "expired" OK = "ok" @@ -1983,6 +2102,8 @@ def to_dict(self) -> dict: class ExtensionSource(Enum): """Discovery source: project (.github/extensions/), user (~/.copilot/extensions/), plugin (installed plugin), or session (session-state//extensions/) + + Discovery source for the extension entrypoint. """ PLUGIN = "plugin" PROJECT = "project" @@ -2001,6 +2122,39 @@ class ExtensionStatus(Enum): class ExtensionContextPushInputType(Enum): EXTENSION_CONTEXT = "extension_context" +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ExtensionLaunchProfile: + """Opaque integrator-owned process launch profile for one extension entrypoint. + + Opaque launch profile, omitted when this provider does not support the entrypoint. + """ + args: list[str] + """Opaque integrator-defined arguments passed to the executable. The runtime does not append + the extension entrypoint. + """ + env: dict[str, str] + """Opaque integrator-defined environment variables. Runtime-owned COPILOT_SDK_PATH, + SESSION_ID, and COPILOT_EXTENSION_PARENT_PID values take precedence. + """ + executable: str + """Executable used to launch the extension entrypoint.""" + + @staticmethod + def from_dict(obj: Any) -> 'ExtensionLaunchProfile': + assert isinstance(obj, dict) + args = from_list(from_str, obj.get("args")) + env = from_dict(from_str, obj.get("env")) + executable = from_str(obj.get("executable")) + return ExtensionLaunchProfile(args, env, executable) + + def to_dict(self) -> dict: + result: dict = {} + result["args"] = from_list(from_str, self.args) + result["env"] = from_dict(from_str, self.env) + result["executable"] = from_str(self.executable) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ExtensionsDisableRequest: @@ -2130,29 +2284,47 @@ class FactoryAgentOptions: Subagent execution options. """ + agent: str | None = None + """Optional custom agent name for the subagent. This field is accepted but not yet honored.""" + + context_tier: ContextTier | None = None + """Optional context tier for the subagent. This field is accepted but not yet honored.""" + label: str | None = None """Optional label distinguishing otherwise identical memoized agent calls.""" model: str | None = None """Optional model identifier for the subagent.""" + reasoning_effort: str | None = None + """Optional reasoning effort for the subagent. This field is accepted but not yet honored.""" + schema: Any = None """Optional JSON Schema for structured agent output.""" @staticmethod def from_dict(obj: Any) -> 'FactoryAgentOptions': assert isinstance(obj, dict) + agent = from_union([from_str, from_none], obj.get("agent")) + context_tier = from_union([ContextTier, from_none], obj.get("contextTier")) label = from_union([from_str, from_none], obj.get("label")) model = from_union([from_str, from_none], obj.get("model")) + reasoning_effort = from_union([from_str, from_none], obj.get("reasoningEffort")) schema = obj.get("schema") - return FactoryAgentOptions(label, model, schema) + return FactoryAgentOptions(agent, context_tier, label, model, reasoning_effort, schema) def to_dict(self) -> dict: result: dict = {} + if self.agent is not None: + result["agent"] = from_union([from_str, from_none], self.agent) + if self.context_tier is not None: + result["contextTier"] = from_union([lambda x: to_enum(ContextTier, x), from_none], self.context_tier) if self.label is not None: result["label"] = from_union([from_str, from_none], self.label) if self.model is not None: result["model"] = from_union([from_str, from_none], self.model) + if self.reasoning_effort is not None: + result["reasoningEffort"] = from_union([from_str, from_none], self.reasoning_effort) if self.schema is not None: result["schema"] = self.schema return result @@ -2177,6 +2349,65 @@ def to_dict(self) -> dict: result["result"] = self.result return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryAgentSummary: + """Prompt-safe durable identity and live status for a direct factory agent.""" + + active_ms: int + agent_id: str + agent_type: str + label: str + run_id: str + status: str + tool_call_id: str + activity: str | None = None + completed_at: int | None = None + phase_id: str | None = None + requested_model: str | None = None + resolved_model: str | None = None + started_at: int | None = None + + @staticmethod + def from_dict(obj: Any) -> 'FactoryAgentSummary': + assert isinstance(obj, dict) + active_ms = from_int(obj.get("activeMs")) + agent_id = from_str(obj.get("agentId")) + agent_type = from_str(obj.get("agentType")) + label = from_str(obj.get("label")) + run_id = from_str(obj.get("runId")) + status = from_str(obj.get("status")) + tool_call_id = from_str(obj.get("toolCallId")) + activity = from_union([from_str, from_none], obj.get("activity")) + completed_at = from_union([from_int, from_none], obj.get("completedAt")) + phase_id = from_union([from_none, from_str], obj.get("phaseId")) + requested_model = from_union([from_str, from_none], obj.get("requestedModel")) + resolved_model = from_union([from_str, from_none], obj.get("resolvedModel")) + started_at = from_union([from_int, from_none], obj.get("startedAt")) + return FactoryAgentSummary(active_ms, agent_id, agent_type, label, run_id, status, tool_call_id, activity, completed_at, phase_id, requested_model, resolved_model, started_at) + + def to_dict(self) -> dict: + result: dict = {} + result["activeMs"] = from_int(self.active_ms) + result["agentId"] = from_str(self.agent_id) + result["agentType"] = from_str(self.agent_type) + result["label"] = from_str(self.label) + result["runId"] = from_str(self.run_id) + result["status"] = from_str(self.status) + result["toolCallId"] = from_str(self.tool_call_id) + if self.activity is not None: + result["activity"] = from_union([from_str, from_none], self.activity) + if self.completed_at is not None: + result["completedAt"] = from_union([from_int, from_none], self.completed_at) + result["phaseId"] = from_union([from_none, from_str], self.phase_id) + if self.requested_model is not None: + result["requestedModel"] = from_union([from_str, from_none], self.requested_model) + if self.resolved_model is not None: + result["resolvedModel"] = from_union([from_str, from_none], self.resolved_model) + if self.started_at is not None: + result["startedAt"] = from_union([from_int, from_none], self.started_at) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class FactoryCancelRequest: @@ -2196,6 +2427,75 @@ def to_dict(self) -> dict: result["runId"] = from_str(self.run_id) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryCurrentPhase: + """Current factory phase identity.""" + + id: str + ordinal: int | None = None + + @staticmethod + def from_dict(obj: Any) -> 'FactoryCurrentPhase': + assert isinstance(obj, dict) + id = from_str(obj.get("id")) + ordinal = from_union([from_none, from_int], obj.get("ordinal")) + return FactoryCurrentPhase(id, ordinal) + + def to_dict(self) -> dict: + result: dict = {} + result["id"] = from_str(self.id) + result["ordinal"] = from_union([from_none, from_int], self.ordinal) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryDeclaredLimits: + """Declared or approved factory resource ceilings.""" + + max_ai_credits: float | None = None + max_concurrent_subagents: int | None = None + max_total_subagents: int | None = None + timeout_seconds: float | None = None + + @staticmethod + def from_dict(obj: Any) -> 'FactoryDeclaredLimits': + assert isinstance(obj, dict) + max_ai_credits = from_union([from_float, from_none], obj.get("maxAiCredits")) + max_concurrent_subagents = from_union([from_int, from_none], obj.get("maxConcurrentSubagents")) + max_total_subagents = from_union([from_int, from_none], obj.get("maxTotalSubagents")) + timeout_seconds = from_union([from_float, from_none], obj.get("timeoutSeconds")) + return FactoryDeclaredLimits(max_ai_credits, max_concurrent_subagents, max_total_subagents, timeout_seconds) + + def to_dict(self) -> dict: + result: dict = {} + if self.max_ai_credits is not None: + result["maxAiCredits"] = from_union([to_float, from_none], self.max_ai_credits) + if self.max_concurrent_subagents is not None: + result["maxConcurrentSubagents"] = from_union([from_int, from_none], self.max_concurrent_subagents) + if self.max_total_subagents is not None: + result["maxTotalSubagents"] = from_union([from_int, from_none], self.max_total_subagents) + if self.timeout_seconds is not None: + result["timeoutSeconds"] = from_union([to_float, from_none], self.timeout_seconds) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +class FactoryDurableOperation(Enum): + """Execution-critical factory storage operation. + + Execution-critical durable operation that failed. + """ + ADD_ELAPSED = "addElapsed" + CHARGE_CREDIT = "chargeCredit" + CREATE_RUN = "createRun" + FINISH_RUN = "finishRun" + JOURNAL_GET = "journalGet" + JOURNAL_PUT = "journalPut" + MARK_RUN_STARTED = "markRunStarted" + RECONCILE_CREDIT_TOTAL = "reconcileCreditTotal" + RELEASE_AGENT = "releaseAgent" + RESERVE_AGENT = "reserveAgent" + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class FactoryExecuteRequest: @@ -2204,6 +2504,9 @@ class FactoryExecuteRequest: args: Any """Factory input value.""" + execution_token: str + """Opaque token identifying this factory execution attempt.""" + name: str """Registered factory name.""" @@ -2217,14 +2520,16 @@ class FactoryExecuteRequest: def from_dict(obj: Any) -> 'FactoryExecuteRequest': assert isinstance(obj, dict) args = obj.get("args") + execution_token = from_str(obj.get("executionToken")) name = from_str(obj.get("name")) run_id = from_str(obj.get("runId")) session_id = from_str(obj.get("sessionId")) - return FactoryExecuteRequest(args, name, run_id, session_id) + return FactoryExecuteRequest(args, execution_token, name, run_id, session_id) def to_dict(self) -> dict: result: dict = {} result["args"] = self.args + result["executionToken"] = from_str(self.execution_token) result["name"] = from_str(self.name) result["runId"] = from_str(self.run_id) result["sessionId"] = from_str(self.session_id) @@ -2235,7 +2540,7 @@ def to_dict(self) -> dict: class FactoryExecuteResult: """Result returned by an extension factory closure.""" - result: Any + result: Any = None """Factory result value.""" @staticmethod @@ -2246,33 +2551,80 @@ def from_dict(obj: Any) -> 'FactoryExecuteResult': def to_dict(self) -> dict: result: dict = {} - result["result"] = self.result + if self.result is not None: + result["result"] = self.result return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class FactoryGetRunRequest: - """Parameters for retrieving a factory run.""" +class FactoryGetRunProgressRequest: + """Parameters for paging factory progress.""" run_id: str """Factory run identifier.""" + after_seq: int | None = None + """Exclusive forward cursor.""" + + before_seq: int | None = None + """Exclusive backward cursor.""" + + limit: int | None = None + """Maximum records to return. Defaults to 200 and is capped at 500.""" + + phase_id: str | None = None + """Optional phase identifier used to scope records and cursors.""" + @staticmethod - def from_dict(obj: Any) -> 'FactoryGetRunRequest': + def from_dict(obj: Any) -> 'FactoryGetRunProgressRequest': assert isinstance(obj, dict) run_id = from_str(obj.get("runId")) - return FactoryGetRunRequest(run_id) + after_seq = from_union([from_int, from_none], obj.get("afterSeq")) + before_seq = from_union([from_int, from_none], obj.get("beforeSeq")) + limit = from_union([from_int, from_none], obj.get("limit")) + phase_id = from_union([from_str, from_none], obj.get("phaseId")) + return FactoryGetRunProgressRequest(run_id, after_seq, before_seq, limit, phase_id) def to_dict(self) -> dict: result: dict = {} result["runId"] = from_str(self.run_id) + if self.after_seq is not None: + result["afterSeq"] = from_union([from_int, from_none], self.after_seq) + if self.before_seq is not None: + result["beforeSeq"] = from_union([from_int, from_none], self.before_seq) + if self.limit is not None: + result["limit"] = from_union([from_int, from_none], self.limit) + if self.phase_id is not None: + result["phaseId"] = from_union([from_str, from_none], self.phase_id) return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class FactoryJournalGetRequest: - """Parameters for reading a factory journal entry.""" - +class FactoryGetRunRequest: + """Parameters for retrieving a factory run.""" + + run_id: str + """Factory run identifier.""" + + @staticmethod + def from_dict(obj: Any) -> 'FactoryGetRunRequest': + assert isinstance(obj, dict) + run_id = from_str(obj.get("runId")) + return FactoryGetRunRequest(run_id) + + def to_dict(self) -> dict: + result: dict = {} + result["runId"] = from_str(self.run_id) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryJournalGetRequest: + """Parameters for reading a factory journal entry.""" + + execution_token: str + """Opaque token identifying the current factory execution attempt.""" + key: str """Namespaced journal key.""" @@ -2282,12 +2634,14 @@ class FactoryJournalGetRequest: @staticmethod def from_dict(obj: Any) -> 'FactoryJournalGetRequest': assert isinstance(obj, dict) + execution_token = from_str(obj.get("executionToken")) key = from_str(obj.get("key")) run_id = from_str(obj.get("runId")) - return FactoryJournalGetRequest(key, run_id) + return FactoryJournalGetRequest(execution_token, key, run_id) def to_dict(self) -> dict: result: dict = {} + result["executionToken"] = from_str(self.execution_token) result["key"] = from_str(self.key) result["runId"] = from_str(self.run_id) return result @@ -2322,6 +2676,9 @@ def to_dict(self) -> dict: class FactoryJournalPutRequest: """Parameters for storing a factory journal entry.""" + execution_token: str + """Opaque token identifying the current factory execution attempt.""" + key: str """Namespaced journal key.""" @@ -2334,26 +2691,88 @@ class FactoryJournalPutRequest: @staticmethod def from_dict(obj: Any) -> 'FactoryJournalPutRequest': assert isinstance(obj, dict) + execution_token = from_str(obj.get("executionToken")) key = from_str(obj.get("key")) result_json = obj.get("resultJson") run_id = from_str(obj.get("runId")) - return FactoryJournalPutRequest(key, result_json, run_id) + return FactoryJournalPutRequest(execution_token, key, result_json, run_id) def to_dict(self) -> dict: result: dict = {} + result["executionToken"] = from_str(self.execution_token) result["key"] = from_str(self.key) result["resultJson"] = self.result_json result["runId"] = from_str(self.run_id) return result # Experimental: this type is part of an experimental API and may change or be removed. -class FactoryLogLineKind(Enum): - """Progress line kind. +@dataclass +class FactoryListRunsRequest: + """Parameters for paging factory runs.""" - Kind of factory progress line. + after_seq: int | None = None + """Exclusive forward cursor.""" + + before_seq: int | None = None + """Exclusive backward cursor.""" + + limit: int | None = None + """Maximum terminal runs to return. Defaults to 200 and is capped at 500.""" + + @staticmethod + def from_dict(obj: Any) -> 'FactoryListRunsRequest': + assert isinstance(obj, dict) + after_seq = from_union([from_int, from_none], obj.get("afterSeq")) + before_seq = from_union([from_int, from_none], obj.get("beforeSeq")) + limit = from_union([from_int, from_none], obj.get("limit")) + return FactoryListRunsRequest(after_seq, before_seq, limit) + + def to_dict(self) -> dict: + result: dict = {} + if self.after_seq is not None: + result["afterSeq"] = from_union([from_int, from_none], self.after_seq) + if self.before_seq is not None: + result["beforeSeq"] = from_union([from_int, from_none], self.before_seq) + if self.limit is not None: + result["limit"] = from_union([from_int, from_none], self.limit) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryRunConsumed: + """Durable factory resource consumption.""" + + active_ms: int + nano_aiu: int + subagents: int + + @staticmethod + def from_dict(obj: Any) -> 'FactoryRunConsumed': + assert isinstance(obj, dict) + active_ms = from_int(obj.get("activeMs")) + nano_aiu = from_int(obj.get("nanoAiu")) + subagents = from_int(obj.get("subagents")) + return FactoryRunConsumed(active_ms, nano_aiu, subagents) + + def to_dict(self) -> dict: + result: dict = {} + result["activeMs"] = from_int(self.active_ms) + result["nanoAiu"] = from_int(self.nano_aiu) + result["subagents"] = from_int(self.subagents) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +class FactoryRunStatus(Enum): + """Current or terminal state of a factory run. + + Current or terminal factory run status. """ - LOG = "log" - PHASE = "phase" + CANCELLED = "cancelled" + COMPLETED = "completed" + ERROR = "error" + HALTED = "halted" + PENDING = "pending" + RUNNING = "running" # Experimental: this type is part of an experimental API and may change or be removed. class FactoryRunFailureKind(Enum): @@ -2361,60 +2780,82 @@ class FactoryRunFailureKind(Enum): Cumulative resource ceiling that stopped a factory run. """ + MAX_AI_CREDITS = "maxAiCredits" MAX_TOTAL_SUBAGENTS = "maxTotalSubagents" - TIMEOUT = "timeout" + TIMEOUT_SECONDS = "timeoutSeconds" class FactoryRunFailureType(Enum): + FACTORY_ACCOUNTING_INCOMPLETE = "factory_accounting_incomplete" + FACTORY_DURABLE_FAILURE = "factory_durable_failure" FACTORY_LIMIT_REACHED = "factory_limit_reached" FACTORY_RESUME_DECLINED = "factory_resume_declined" +# Experimental: this type is part of an experimental API and may change or be removed. +class FactoryLogLineKind(Enum): + """Progress line kind. + + Kind of factory progress line. + + Progress record kind. + """ + LOG = "log" + PHASE = "phase" + +# Experimental: this type is part of an experimental API and may change or be removed. +class FactoryPhaseStatus(Enum): + """Derived lifecycle state of a factory phase.""" + + ACTIVE = "active" + COMPLETED = "completed" + PENDING = "pending" + SKIPPED = "skipped" + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class FactoryRunLimits: - """Wire-only per-invocation factory resource ceiling overrides. + """Optional per-invocation resource ceiling overrides. + + Wire-only per-invocation factory resource ceiling overrides. Per-invocation resource ceiling overrides. """ + max_ai_credits: float | None = None + """Maximum AI credits consumed by factory subagents and their descendants. The post-paid + ceiling is soft: parallel turns can settle beyond it before the run stops. + """ max_concurrent_subagents: int | None = None """Maximum number of factory subagents that may run concurrently.""" max_total_subagents: int | None = None """Maximum total number of factory subagents that may be admitted.""" - timeout: float | None = None - """Factory active-run timeout in milliseconds.""" + timeout_seconds: float | None = None + """Maximum accumulated active-execution time in seconds. Active execution includes the + entire extension body, subprocess waits, queued-agent waits, and sleeps; time between + resumed attempts is not counted. + """ @staticmethod def from_dict(obj: Any) -> 'FactoryRunLimits': assert isinstance(obj, dict) + max_ai_credits = from_union([from_float, from_none], obj.get("maxAiCredits")) max_concurrent_subagents = from_union([from_int, from_none], obj.get("maxConcurrentSubagents")) max_total_subagents = from_union([from_int, from_none], obj.get("maxTotalSubagents")) - timeout = from_union([from_float, from_none], obj.get("timeout")) - return FactoryRunLimits(max_concurrent_subagents, max_total_subagents, timeout) + timeout_seconds = from_union([from_float, from_none], obj.get("timeoutSeconds")) + return FactoryRunLimits(max_ai_credits, max_concurrent_subagents, max_total_subagents, timeout_seconds) def to_dict(self) -> dict: result: dict = {} + if self.max_ai_credits is not None: + result["maxAiCredits"] = from_union([to_float, from_none], self.max_ai_credits) if self.max_concurrent_subagents is not None: result["maxConcurrentSubagents"] = from_union([from_int, from_none], self.max_concurrent_subagents) if self.max_total_subagents is not None: result["maxTotalSubagents"] = from_union([from_int, from_none], self.max_total_subagents) - if self.timeout is not None: - result["timeout"] = from_union([to_float, from_none], self.timeout) + if self.timeout_seconds is not None: + result["timeoutSeconds"] = from_union([to_float, from_none], self.timeout_seconds) return result -# Experimental: this type is part of an experimental API and may change or be removed. -class FactoryRunStatus(Enum): - """Current or terminal factory run status. - - Current or terminal state of a factory run. - """ - CANCELLED = "cancelled" - COMPLETED = "completed" - ERROR = "error" - HALTED = "halted" - PENDING = "pending" - RUNNING = "running" - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class FleetStartRequest: @@ -2646,6 +3087,51 @@ def to_dict(self) -> dict: result["cancelled"] = from_bool(self.cancelled) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class HistoryClearContextRequest: + """Parameters for clearing the conversation and seeding the window that replaces it.""" + + prompt: str + """First user message of the fresh context window. Required: a cleared window holding only + system and developer messages is not a conversation a model can answer, so every clear + seeds the window it creates. Delivered by the enclosing turn driver once the agentic loop + exits, which is why the call must be made from inside a tool handler. + """ + + @staticmethod + def from_dict(obj: Any) -> 'HistoryClearContextRequest': + assert isinstance(obj, dict) + prompt = from_str(obj.get("prompt")) + return HistoryClearContextRequest(prompt) + + def to_dict(self) -> dict: + result: dict = {} + result["prompt"] = from_str(self.prompt) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class HistoryClearContextResult: + """What a successful clear removed. A clear that could not be applied rejects instead of + reporting a count. + """ + messages_cleared: int + """Number of non-system, non-developer messages that were removed from the conversation. + Zero only when the window already held no conversation. + """ + + @staticmethod + def from_dict(obj: Any) -> 'HistoryClearContextResult': + assert isinstance(obj, dict) + messages_cleared = from_int(obj.get("messagesCleared")) + return HistoryClearContextResult(messages_cleared) + + def to_dict(self) -> dict: + result: dict = {} + result["messagesCleared"] = from_int(self.messages_cleared) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class HistoryCompactContextWindow: @@ -2693,26 +3179,161 @@ def to_dict(self) -> dict: result["toolDefinitionsTokens"] = from_union([from_int, from_none], self.tool_definitions_tokens) return result +# Experimental: this type is part of an experimental API and may change or be removed. +class HistoryFileRestoreSkipReason(Enum): + """Reason a captured file was not restored. + + Reason the file was not restored. + """ + SKIPPED_CAPTURE = "skipped-capture" + USER_MODIFIED = "user-modified" + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class HistoryCompactRequest: - """Optional compaction parameters.""" +class HistoryRewindPoint: + """A root user turn that the session can rewind to.""" - custom_instructions: str | None = None - """Optional user-provided instructions to focus the compaction summary""" + can_restore_files: bool + """Whether at least one file in this turn or a later turn can be restored.""" + + event_id: str + """ID of the user.message event that begins the discarded suffix.""" + + file_count: int + """Number of unique files in this turn and all later turns that have captured changes.""" + + is_autopilot_continuation: bool + """Whether this turn was an automatically injected autopilot continuation.""" + + lines_added: int + """Lines added by this turn's captured file changes.""" + + lines_removed: int + """Lines removed by this turn's captured file changes.""" + + timestamp: str + """ISO timestamp of the user turn.""" + + turn_changed_files: bool + """Whether this turn itself captured any file changes.""" + + user_message: str + """User-visible message text for the turn.""" @staticmethod - def from_dict(obj: Any) -> 'HistoryCompactRequest': + def from_dict(obj: Any) -> 'HistoryRewindPoint': assert isinstance(obj, dict) - custom_instructions = from_union([from_str, from_none], obj.get("customInstructions")) - return HistoryCompactRequest(custom_instructions) + can_restore_files = from_bool(obj.get("canRestoreFiles")) + event_id = from_str(obj.get("eventId")) + file_count = from_int(obj.get("fileCount")) + is_autopilot_continuation = from_bool(obj.get("isAutopilotContinuation")) + lines_added = from_int(obj.get("linesAdded")) + lines_removed = from_int(obj.get("linesRemoved")) + timestamp = from_str(obj.get("timestamp")) + turn_changed_files = from_bool(obj.get("turnChangedFiles")) + user_message = from_str(obj.get("userMessage")) + return HistoryRewindPoint(can_restore_files, event_id, file_count, is_autopilot_continuation, lines_added, lines_removed, timestamp, turn_changed_files, user_message) def to_dict(self) -> dict: result: dict = {} - if self.custom_instructions is not None: - result["customInstructions"] = from_union([from_str, from_none], self.custom_instructions) + result["canRestoreFiles"] = from_bool(self.can_restore_files) + result["eventId"] = from_str(self.event_id) + result["fileCount"] = from_int(self.file_count) + result["isAutopilotContinuation"] = from_bool(self.is_autopilot_continuation) + result["linesAdded"] = from_int(self.lines_added) + result["linesRemoved"] = from_int(self.lines_removed) + result["timestamp"] = from_str(self.timestamp) + result["turnChangedFiles"] = from_bool(self.turn_changed_files) + result["userMessage"] = from_str(self.user_message) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +class HistoryRewindUnavailableReason(Enum): + """Why the listed points could not be produced, when applicable; the points list is empty + whenever it is set. `unsupported-remote-session` is permanent for the session and comes + with `fileChangeTrackingEnabled: false`. `session-busy` is transient and only ever + reported by a session that *is* tracking (`fileChangeTrackingEnabled: true`), because the + file-change captures cannot be read while work that may still mutate them is in flight; + the same request succeeds once the session settles, so a client that wants points should + retry rather than treat it as a failure. It is never `file-change-tracking-disabled`: an + untracked local session still lists conversation-only points and reports that through + `fileChangeTrackingEnabled: false`. + + Reason a rewind read (rewind points, file-restore preview, or session diff) could not be + answered from the session's file-change captures. + + Why file restore is unavailable, when applicable. Populated only when `available` is + false and never set when `available` is true. + + Why the session diff could not be produced, when applicable. Set only when `session` mode + was requested and `isFallback` is true, so a client can tell the permanent + `file-change-tracking-disabled` apart from the transient `session-busy`, which the same + request answers once the session settles. Never set for `unstaged` or `branch` mode, and + never `unsupported-remote-session`: a remote session's captures live on its own host, so + a `session`-mode diff is rejected for one rather than answered with a controller-side + fallback. + """ + FILE_CHANGE_TRACKING_DISABLED = "file-change-tracking-disabled" + SESSION_BUSY = "session-busy" + UNSUPPORTED_REMOTE_SESSION = "unsupported-remote-session" + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class HistoryPreviewRewindRequest: + """Event boundary to preview for conversation-and-files rewind.""" + + event_id: str + """ID of the user.message event that begins the discarded suffix.""" + + @staticmethod + def from_dict(obj: Any) -> 'HistoryPreviewRewindRequest': + assert isinstance(obj, dict) + event_id = from_str(obj.get("eventId")) + return HistoryPreviewRewindRequest(event_id) + + def to_dict(self) -> dict: + result: dict = {} + result["eventId"] = from_str(self.event_id) return result +# Experimental: this type is part of an experimental API and may change or be removed. +class HistoryRewindChangeType(Enum): + """Aggregate change made across the discarded turns. + + Aggregate file change represented by a rewind preview. + """ + CREATED = "created" + DELETED = "deleted" + MODIFIED = "modified" + +# Experimental: this type is part of an experimental API and may change or be removed. +class HistoryRewindMode(Enum): + """Scope of a rewind operation. + + Whether to rewind only conversation history or also restore captured files. + """ + CONVERSATION = "conversation" + CONVERSATION_AND_FILES = "conversation-and-files" + +# Experimental: this type is part of an experimental API and may change or be removed. +class HistoryRewindOutcome(Enum): + """Outcome of a rewind request. + + Overall rewind outcome. This discriminates the result: it governs which of the remaining + fields are populated, so consumers must switch on it before reading `eventsRemoved`, + `restoredFiles`, `skippedFiles`, or `error`. See each field for the outcomes that + populate it. + """ + CHECKPOINT_CLEANUP_FAILED = "checkpoint-cleanup-failed" + FILES_ROLLED_BACK = "files-rolled-back" + FILE_CHANGE_TRACKING_DISABLED = "file-change-tracking-disabled" + ROLLBACK_INCOMPLETE = "rollback-incomplete" + SESSION_BUSY = "session-busy" + SNAPSHOT_PRUNE_FAILED = "snapshot-prune-failed" + SUCCESS = "success" + TRUNCATION_FAILED = "truncation-failed" + UNSUPPORTED_REMOTE_SESSION = "unsupported-remote-session" + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class HistorySummarizeForHandoffResult: @@ -2761,15 +3382,30 @@ class HistoryTruncateResult: events_removed: int """Number of events that were removed""" + checkpoint_cleanup_error: str | None = None + """Failure detail when checkpointCleanupFailed is true.""" + + checkpoint_cleanup_failed: bool | None = None + """True when conversation truncation succeeded but post-truncation workspace checkpoint + cleanup failed. History is already truncated; callers may still prune snapshots but + should report a checkpoint-cleanup rather than a truncation failure. + """ + @staticmethod def from_dict(obj: Any) -> 'HistoryTruncateResult': assert isinstance(obj, dict) events_removed = from_int(obj.get("eventsRemoved")) - return HistoryTruncateResult(events_removed) + checkpoint_cleanup_error = from_union([from_str, from_none], obj.get("checkpointCleanupError")) + checkpoint_cleanup_failed = from_union([from_bool, from_none], obj.get("checkpointCleanupFailed")) + return HistoryTruncateResult(events_removed, checkpoint_cleanup_error, checkpoint_cleanup_failed) def to_dict(self) -> dict: result: dict = {} result["eventsRemoved"] = from_int(self.events_removed) + if self.checkpoint_cleanup_error is not None: + result["checkpointCleanupError"] = from_union([from_str, from_none], self.checkpoint_cleanup_error) + if self.checkpoint_cleanup_failed is not None: + result["checkpointCleanupFailed"] = from_union([from_bool, from_none], self.checkpoint_cleanup_failed) return result class HMACAuthInfoType(Enum): @@ -2912,6 +3548,50 @@ def to_dict(self) -> dict: result["projectPaths"] = from_union([lambda x: from_list(from_str, x), from_none], self.project_paths) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class InterruptMainTurnRequest: + """Parameters for interrupting the main agent turn.""" + + flush_queued: bool | None = None + """When true, the user's queued prompts are preserved and run as the next turn once the + interrupted turn unwinds; when false (the default), the queue is cleared like a plain + abort. + """ + + @staticmethod + def from_dict(obj: Any) -> 'InterruptMainTurnRequest': + assert isinstance(obj, dict) + flush_queued = from_union([from_bool, from_none], obj.get("flushQueued")) + return InterruptMainTurnRequest(flush_queued) + + def to_dict(self) -> dict: + result: dict = {} + if self.flush_queued is not None: + result["flushQueued"] = from_union([from_bool, from_none], self.flush_queued) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class InterruptMainTurnResult: + """Result of interrupting the main agent turn.""" + + interrupted: bool + """Whether an in-flight main agent turn was interrupted. False when the main loop was not + processing. + """ + + @staticmethod + def from_dict(obj: Any) -> 'InterruptMainTurnResult': + assert isinstance(obj, dict) + interrupted = from_bool(obj.get("interrupted")) + return InterruptMainTurnResult(interrupted) + + def to_dict(self) -> dict: + result: dict = {} + result["interrupted"] = from_bool(self.interrupted) + return result + @dataclass class LlmInferenceHTTPRequestChunkRequest: """A request body chunk or cancellation signal.""" @@ -3207,6 +3887,34 @@ def to_dict(self) -> dict: result["workingDirectory"] = from_union([from_str, from_none], self.working_directory) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ManagedSettingsReadResult: + """Validated device-managed settings discovered before a session exists.""" + + error_message: str | None = None + """Discovery or validation error text when managed settings could not be read safely.""" + + settings_json: Any = None + """Validated, canonical managed-settings JSON. Omitted when no managed settings were + discovered or when discovered settings failed validation. + """ + + @staticmethod + def from_dict(obj: Any) -> 'ManagedSettingsReadResult': + assert isinstance(obj, dict) + error_message = from_union([from_str, from_none], obj.get("errorMessage")) + settings_json = obj.get("settingsJson") + return ManagedSettingsReadResult(error_message, settings_json) + + def to_dict(self) -> dict: + result: dict = {} + if self.error_message is not None: + result["errorMessage"] = from_union([from_str, from_none], self.error_message) + if self.settings_json is not None: + result["settingsJson"] = self.settings_json + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MarketplaceAddResult: @@ -3809,9 +4517,8 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MCPFilteredServer: - """MCP server filtered by policy, with name, reason, optional redacted reason, and - enterprise login. - """ + """MCP server filtered by policy, with name, reason, and optional redacted reason.""" + name: str """Filtered server name""" @@ -3819,7 +4526,7 @@ class MCPFilteredServer: """Human-readable filter reason""" enterprise_name: str | None = None - """Enterprise login associated with an allowlist policy""" + """Deprecated. This field is no longer populated.""" redacted_reason: str | None = None """PII-free filter reason""" @@ -3975,6 +4682,35 @@ class MCPToolUIVisibility(Enum): APP = "app" MODEL = "model" +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class MCPOauthAuthenticationStateChangedRequest: + """Identifies the MCP server whose persisted OAuth credentials were updated.""" + + refresh_session_token: bool | None = None + """Whether the target session must mint a session-scoped access token instead of reusing a + shared access token persisted by another session. + """ + server_name: str | None = None + """Name of the MCP server whose OAuth credentials were updated. Omit only when the host + cannot identify the server. + """ + + @staticmethod + def from_dict(obj: Any) -> 'MCPOauthAuthenticationStateChangedRequest': + assert isinstance(obj, dict) + refresh_session_token = from_union([from_bool, from_none], obj.get("refreshSessionToken")) + server_name = from_union([from_str, from_none], obj.get("serverName")) + return MCPOauthAuthenticationStateChangedRequest(refresh_session_token, server_name) + + def to_dict(self) -> dict: + result: dict = {} + if self.refresh_session_token is not None: + result["refreshSessionToken"] = from_union([from_bool, from_none], self.refresh_session_token) + if self.server_name is not None: + result["serverName"] = from_union([from_str, from_none], self.server_name) + return result + class MCPOauthPendingRequestResponseKind(Enum): CANCELLED = "cancelled" TOKEN = "token" @@ -4027,14 +4763,54 @@ def to_dict(self) -> dict: return result # Experimental: this type is part of an experimental API and may change or be removed. -# Internal: this type is an internal SDK API and is not part of the public surface. @dataclass -class MCPReloadWithConfigRequest: - """Opaque MCP reload configuration.""" +class MCPOauthRespondRequest: + """Pending MCP OAuth request id to respond to.""" - config: Any = None - """Opaque runtime MCP reload configuration. Marked internal: an in-process runtime shape - (reloadMcpServers throws over the wire). + request_id: str + """OAuth request identifier from the mcp.oauth_required event""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPOauthRespondRequest': + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + return MCPOauthRespondRequest(request_id) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class MCPOauthRespondResult: + """Indicates whether the pending MCP OAuth response was accepted.""" + + success: bool + """Whether the response was accepted. False if the request was unknown, timed out, or + already resolved. + """ + + @staticmethod + def from_dict(obj: Any) -> 'MCPOauthRespondResult': + assert isinstance(obj, dict) + success = from_bool(obj.get("success")) + return MCPOauthRespondResult(success) + + def to_dict(self) -> dict: + result: dict = {} + result["success"] = from_bool(self.success) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +# Internal: this type is an internal SDK API and is not part of the public surface. +@dataclass +class MCPReloadWithConfigRequest: + """Opaque MCP reload configuration.""" + + config: Any = None + """Opaque runtime MCP reload configuration. Marked internal: an in-process runtime shape + (reloadMcpServers throws over the wire). """ @staticmethod @@ -4275,6 +5051,57 @@ def to_dict(self) -> dict: result["enabled"] = from_bool(self.enabled) return result +@dataclass +class Categories: + """The six normalized `/context` header buckets, computed from the same tokenization as + `entries` so the two never disagree. Convenience rollups: `freeSpace` and `buffer` + describe window capacity rather than occupied context, so the values do not sum to + `totalTokens`. + """ + buffer: int + """Output reserve plus post-blocking-threshold buffer.""" + + custom_instructions: int + """Custom-instructions tokens (0 when none are configured).""" + + free_space: int + """Remaining unused window capacity (clamped at 0).""" + + mcp_tools: int + """MCP tool-definition tokens.""" + + messages: int + """Conversation (user/assistant/tool) message tokens.""" + + system_prompt: int + """System prompt tokens, excluding custom instructions.""" + + system_tools: int + """Non-MCP tool-definition tokens.""" + + @staticmethod + def from_dict(obj: Any) -> 'Categories': + assert isinstance(obj, dict) + buffer = from_int(obj.get("buffer")) + custom_instructions = from_int(obj.get("customInstructions")) + free_space = from_int(obj.get("freeSpace")) + mcp_tools = from_int(obj.get("mcpTools")) + messages = from_int(obj.get("messages")) + system_prompt = from_int(obj.get("systemPrompt")) + system_tools = from_int(obj.get("systemTools")) + return Categories(buffer, custom_instructions, free_space, mcp_tools, messages, system_prompt, system_tools) + + def to_dict(self) -> dict: + result: dict = {} + result["buffer"] = from_int(self.buffer) + result["customInstructions"] = from_int(self.custom_instructions) + result["freeSpace"] = from_int(self.free_space) + result["mcpTools"] = from_int(self.mcp_tools) + result["messages"] = from_int(self.messages) + result["systemPrompt"] = from_int(self.system_prompt) + result["systemTools"] = from_int(self.system_tools) + return result + @dataclass class Compactions: """Successful compaction history for the session.""" @@ -4633,40 +5460,41 @@ def to_dict(self) -> dict: @dataclass class ModelBillingPromo: """Active server-driven promotion for this model, if any. Present when the model is being - promoted with a time-boxed discount. + promoted with a discount, which may be time-boxed or open-ended. - Active server-driven promotion for a model, including its discount and expiry. - """ - ends_at: str - """UTC ISO 8601 timestamp marking when the promotion ends. Always present: the API only - surfaces a promo whose expiry parses and is in the future. Consumers should treat a past - value as expired. + Active server-driven promotion for a model, including its discount and optional expiry. """ discount_percent: float | None = None """Percentage discount (0-100) applied while the promotion is active. May be fractional.""" + ends_at: str | None = None + """UTC ISO 8601 timestamp marking when the promotion ends. Optional: an open-ended promotion + omits this field. When present, the API only surfaces a promo whose expiry parses and is + in the future, so consumers should treat a past value as expired. + """ id: str | None = None """Stable identifier for the promotion campaign.""" message: str | None = None """Human-readable promotion message. Does not include the expiry timestamp; consumers may - format endsAt and append it. + format endsAt and append it when present. """ @staticmethod def from_dict(obj: Any) -> 'ModelBillingPromo': assert isinstance(obj, dict) - ends_at = from_str(obj.get("endsAt")) discount_percent = from_union([from_float, from_none], obj.get("discountPercent")) + ends_at = from_union([from_str, from_none], obj.get("endsAt")) id = from_union([from_str, from_none], obj.get("id")) message = from_union([from_str, from_none], obj.get("message")) - return ModelBillingPromo(ends_at, discount_percent, id, message) + return ModelBillingPromo(discount_percent, ends_at, id, message) def to_dict(self) -> dict: result: dict = {} - result["endsAt"] = from_str(self.ends_at) if self.discount_percent is not None: result["discountPercent"] = from_union([to_float, from_none], self.discount_percent) + if self.ends_at is not None: + result["endsAt"] = from_union([from_str, from_none], self.ends_at) if self.id is not None: result["id"] = from_union([from_str, from_none], self.id) if self.message is not None: @@ -4809,26 +5637,6 @@ def to_dict(self) -> dict: result["supported_media_types"] = from_union([lambda x: from_list(from_str, x), from_none], self.supported_media_types) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class ModelListRequest: - """Optional listing options.""" - - skip_cache: bool | None = None - """If true, bypasses the per-session model list cache and re-fetches from CAPI.""" - - @staticmethod - def from_dict(obj: Any) -> 'ModelListRequest': - assert isinstance(obj, dict) - skip_cache = from_union([from_bool, from_none], obj.get("skipCache")) - return ModelListRequest(skip_cache) - - def to_dict(self) -> dict: - result: dict = {} - if self.skip_cache is not None: - result["skipCache"] = from_union([from_bool, from_none], self.skip_cache) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ModelSetReasoningEffortRequest: @@ -4876,17 +5684,26 @@ def to_dict(self) -> dict: class ModelSwitchToResult: """The model identifier active on the session after the switch.""" + deferred: bool | None = None + """True when the switch was deferred (enqueued as a cancellable `/model` command) because a + turn was active or another model change was already queued, rather than applied + immediately. When true, the session's live model is unchanged until the queued change + drains. + """ model_id: str | None = None """Currently active model identifier after the switch""" @staticmethod def from_dict(obj: Any) -> 'ModelSwitchToResult': assert isinstance(obj, dict) + deferred = from_union([from_bool, from_none], obj.get("deferred")) model_id = from_union([from_str, from_none], obj.get("modelId")) - return ModelSwitchToResult(model_id) + return ModelSwitchToResult(deferred, model_id) def to_dict(self) -> dict: result: dict = {} + if self.deferred is not None: + result["deferred"] = from_union([from_bool, from_none], self.deferred) if self.model_id is not None: result["modelId"] = from_union([from_str, from_none], self.model_id) return result @@ -5125,6 +5942,7 @@ class ApprovalKind(Enum): CUSTOM_TOOL = "custom-tool" EXTENSION_MANAGEMENT = "extension-management" EXTENSION_PERMISSION_ACCESS = "extension-permission-access" + FACTORY = "factory" MCP = "mcp" MCP_SAMPLING = "mcp-sampling" MEMORY = "memory" @@ -5163,6 +5981,9 @@ class PermissionDecisionApproveForLocationApprovalExtensionManagementKind(Enum): class PermissionDecisionApproveForLocationApprovalExtensionPermissionAccessKind(Enum): EXTENSION_PERMISSION_ACCESS = "extension-permission-access" +class PermissionDecisionApproveForLocationApprovalFactoryKind(Enum): + FACTORY = "factory" + class PermissionDecisionApproveForLocationApprovalMCPKind(Enum): MCP = "mcp" @@ -5199,6 +6020,38 @@ class PermissionDecisionApprovedForSessionKind(Enum): class PermissionDecisionCancelledKind(Enum): CANCELLED = "cancelled" +# Experimental: this type is part of an experimental API and may change or be removed. +class PermissionDecisionOutcome(Enum): + """Disposition of the permission request as observed by the responding client. + + Disposition of a permission request as observed by the responding client. + """ + AUTOPILOT_DENIED = "autopilot_denied" + AUTO_APPROVED = "auto_approved" + PROMPTED_USER = "prompted_user" + +# Experimental: this type is part of an experimental API and may change or be removed. +class PermissionDecisionSource(Enum): + """Controlled reason or actor responsible for the response. + + Controlled reason or actor responsible for a permission response. + """ + HOST_POLICY = "host_policy" + HUMAN_RESPONSE = "human_response" + JUDGE_RECOMMENDATION = "judge_recommendation" + UNATTENDED_FALLBACK = "unattended_fallback" + +# Experimental: this type is part of an experimental API and may change or be removed. +class PermissionDecisionSurface(Enum): + """Client surface that submitted the response. + + Client surface that submitted a permission response. + """ + COPILOT_APP = "copilot_app" + PROMPT_MODE = "prompt_mode" + SDK = "sdk" + TUI = "tui" + class PermissionDecisionDeniedByContentExclusionPolicyKind(Enum): DENIED_BY_CONTENT_EXCLUSION_POLICY = "denied-by-content-exclusion-policy" @@ -5217,30 +6070,6 @@ class PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUserKind(Enum) class PermissionDecisionRejectKind(Enum): REJECT = "reject" -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class PermissionDecisionRequest: - """Pending permission request ID and the decision to apply (approve/reject and scope).""" - - request_id: str - """Request ID of the pending permission request""" - - result: PermissionDecision - """The client's response to the pending permission prompt""" - - @staticmethod - def from_dict(obj: Any) -> 'PermissionDecisionRequest': - assert isinstance(obj, dict) - request_id = from_str(obj.get("requestId")) - result = _load_PermissionDecision(obj.get("result")) - return PermissionDecisionRequest(request_id, result) - - def to_dict(self) -> dict: - result: dict = {} - result["requestId"] = from_str(self.request_id) - result["result"] = (self.result).to_dict() - return result - class PermissionDecisionUserNotAvailableKind(Enum): USER_NOT_AVAILABLE = "user-not-available" @@ -5753,14 +6582,21 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionsResetSessionApprovalsRequest: - """No parameters; clears all session-scoped tool permission approvals.""" + """Clears session-scoped tool permission approvals, and optionally the location-scoped ones.""" + + include_location: bool | None = None + """Whether location-scoped approvals are cleared too. Defaults to `true`.""" + @staticmethod def from_dict(obj: Any) -> 'PermissionsResetSessionApprovalsRequest': assert isinstance(obj, dict) - return PermissionsResetSessionApprovalsRequest() + include_location = from_union([from_bool, from_none], obj.get("includeLocation")) + return PermissionsResetSessionApprovalsRequest(include_location) def to_dict(self) -> dict: result: dict = {} + if self.include_location is not None: + result["includeLocation"] = from_union([from_bool, from_none], self.include_location) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -6184,54 +7020,6 @@ def to_dict(self) -> dict: result["force"] = from_union([from_bool, from_none], self.force) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class PluginsReloadRequest: - """Optional flags controlling which side effects the reload performs.""" - - defer_repo_hooks: bool | None = None - """When true, skip repo-level hooks during the hook reload. Use before folder trust is - confirmed; load them post-trust via `sessions.loadDeferredRepoHooks`. - """ - reload_custom_agents: bool | None = None - """Re-run custom-agent discovery after refreshing plugins. Defaults to true.""" - - reload_extensions: bool | None = None - """Re-discover and relaunch subprocess extensions (including plugin-shipped extensions) - after refreshing plugins. Defaults to true. Has no effect when the session has no active - extension controller (e.g. extensions were not requested for the session). - """ - reload_hooks: bool | None = None - """Re-load user, plugin, and (subject to `deferRepoHooks`) repo hooks. Defaults to true. Has - no effect when the host has not registered a hook reloader (e.g. remote sessions). - """ - reload_mcp: bool | None = None - """Reload MCP server connections after refreshing plugins. Defaults to true.""" - - @staticmethod - def from_dict(obj: Any) -> 'PluginsReloadRequest': - assert isinstance(obj, dict) - defer_repo_hooks = from_union([from_bool, from_none], obj.get("deferRepoHooks")) - reload_custom_agents = from_union([from_bool, from_none], obj.get("reloadCustomAgents")) - reload_extensions = from_union([from_bool, from_none], obj.get("reloadExtensions")) - reload_hooks = from_union([from_bool, from_none], obj.get("reloadHooks")) - reload_mcp = from_union([from_bool, from_none], obj.get("reloadMcp")) - return PluginsReloadRequest(defer_repo_hooks, reload_custom_agents, reload_extensions, reload_hooks, reload_mcp) - - def to_dict(self) -> dict: - result: dict = {} - if self.defer_repo_hooks is not None: - result["deferRepoHooks"] = from_union([from_bool, from_none], self.defer_repo_hooks) - if self.reload_custom_agents is not None: - result["reloadCustomAgents"] = from_union([from_bool, from_none], self.reload_custom_agents) - if self.reload_extensions is not None: - result["reloadExtensions"] = from_union([from_bool, from_none], self.reload_extensions) - if self.reload_hooks is not None: - result["reloadHooks"] = from_union([from_bool, from_none], self.reload_hooks) - if self.reload_mcp is not None: - result["reloadMcp"] = from_union([from_bool, from_none], self.reload_mcp) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ProviderAddResult: @@ -6494,165 +7282,603 @@ class PushAttachmentGitHubURLType(Enum): class PushAttachmentSelectionType(Enum): SELECTION = "selection" -# Experimental: this type is part of an experimental API and may change or be removed. -class QueuePendingItemsKind(Enum): - """Whether this item is a queued user message or a queued slash command / model change""" - - COMMAND = "command" - MESSAGE = "message" - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class QueueRemoveMostRecentResult: - """Indicates whether a user-facing pending item was removed.""" +class QueueBeginDeferredIdleDrainRequest: + """Inputs for starting a deferred-idle drain.""" - removed: bool - """True if a user-facing pending item was removed (LIFO across both queues); false when no - removable items remained. - """ + active_background_work: bool + """Whether the host still has active background work.""" @staticmethod - def from_dict(obj: Any) -> 'QueueRemoveMostRecentResult': + def from_dict(obj: Any) -> 'QueueBeginDeferredIdleDrainRequest': assert isinstance(obj, dict) - removed = from_bool(obj.get("removed")) - return QueueRemoveMostRecentResult(removed) + active_background_work = from_bool(obj.get("activeBackgroundWork")) + return QueueBeginDeferredIdleDrainRequest(active_background_work) def to_dict(self) -> dict: result: dict = {} - result["removed"] = from_bool(self.removed) + result["activeBackgroundWork"] = from_bool(self.active_background_work) return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class QueuedCommandHandled: - """Queued-command response indicating the host executed the command, with an optional flag - to stop queue processing. - """ - handled: ClassVar[str] = "true" - """The host actually executed the queued command.""" +class QueueBeginDeferredIdleDrainResult: + """Whether a deferred-idle drain should run.""" - stop_processing_queue: bool | None = None - """When true, the runtime will not process subsequent queued commands until a new request - comes in. - """ + should_drain: bool + """True when the host should run finishDeferredIdleDrain asynchronously.""" @staticmethod - def from_dict(obj: Any) -> 'QueuedCommandHandled': + def from_dict(obj: Any) -> 'QueueBeginDeferredIdleDrainResult': assert isinstance(obj, dict) - stop_processing_queue = from_union([from_bool, from_none], obj.get("stopProcessingQueue")) - return QueuedCommandHandled(stop_processing_queue) + should_drain = from_bool(obj.get("shouldDrain")) + return QueueBeginDeferredIdleDrainResult(should_drain) def to_dict(self) -> dict: result: dict = {} - result["handled"] = self.handled - if self.stop_processing_queue is not None: - result["stopProcessingQueue"] = from_union([from_bool, from_none], self.stop_processing_queue) + result["shouldDrain"] = from_bool(self.should_drain) return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class QueuedCommandNotHandled: - """Queued-command response indicating the host did not execute the command and the queue may - continue. - """ - handled: ClassVar[str] = "false" - """The host did not execute the queued command. Unblocks the queue without claiming the - command was processed (e.g. when the handler threw before completing). - """ +class QueueConsumeSystemNotificationsRequest: + """Internal filter for consuming queued system notifications.""" + + filter: Any + """Opaque runtime-owned filter object.""" @staticmethod - def from_dict(obj: Any) -> 'QueuedCommandNotHandled': + def from_dict(obj: Any) -> 'QueueConsumeSystemNotificationsRequest': assert isinstance(obj, dict) - return QueuedCommandNotHandled() + filter = obj.get("filter") + return QueueConsumeSystemNotificationsRequest(filter) def to_dict(self) -> dict: result: dict = {} - result["handled"] = self.handled + result["filter"] = self.filter return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class RegisterEventInterestParams: - """Event type to register consumer interest for, used by runtime gating logic.""" +class QueueDeferSessionIdleRequest: + """Inputs for marking session.idle deferred in native state.""" - event_type: str - """The event type the consumer wants the runtime to treat as 'observed' for - behavior-switching gating. Some runtime code paths inspect whether any consumer is - interested in a specific event type and choose a different implementation accordingly - (e.g. `mcp.oauth_required`: when interest is registered the runtime delegates interactive - OAuth token acquisition to the consumer via `mcp.oauth_required` events; when no interest - is registered the runtime still attempts non-interactive reconnect from cached or - refreshable tokens, and only marks the server `needs-auth` if usable credentials are - unavailable — it does not open a browser or start interactive OAuth without a consumer). - SDK clients that long-poll events do NOT automatically appear as listeners to these - gating checks — they must explicitly call `registerInterest` for each event type they - want the runtime to count as having a consumer. Multiple registrations for the same event - type from the same or different consumers are tracked independently and must each be - released. See: `mcp.oauth_required`, `sampling.requested`, `auto_mode_switch.requested`, - `session_limits_exhausted.requested`, `user_input.requested`, `elicitation.requested`, - `command.queued`, `exit_plan_mode.requested`. - """ + aborted: bool + """Whether the deferred idle was caused by an aborted foreground turn.""" @staticmethod - def from_dict(obj: Any) -> 'RegisterEventInterestParams': + def from_dict(obj: Any) -> 'QueueDeferSessionIdleRequest': assert isinstance(obj, dict) - event_type = from_str(obj.get("eventType")) - return RegisterEventInterestParams(event_type) + aborted = from_bool(obj.get("aborted")) + return QueueDeferSessionIdleRequest(aborted) def to_dict(self) -> dict: result: dict = {} - result["eventType"] = from_str(self.event_type) + result["aborted"] = from_bool(self.aborted) return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class RegisterEventInterestResult: - """Opaque handle representing an event-type interest registration.""" +class QueueDuplicateAtRequest: + """Parameters for duplicating a queued item.""" - handle: str - """Opaque handle for this registration. Pass to releaseInterest to release. Each call to - registerInterest produces a fresh handle, even when the same eventType is registered - multiple times. - """ + id: str @staticmethod - def from_dict(obj: Any) -> 'RegisterEventInterestResult': + def from_dict(obj: Any) -> 'QueueDuplicateAtRequest': assert isinstance(obj, dict) - handle = from_str(obj.get("handle")) - return RegisterEventInterestResult(handle) + id = from_str(obj.get("id")) + return QueueDuplicateAtRequest(id) def to_dict(self) -> dict: result: dict = {} - result["handle"] = from_str(self.handle) + result["id"] = from_str(self.id) return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SessionsRegisterExtensionToolsOnSessionOptions: - """Optional registration options.""" +class QueueDuplicateAtResult: + """Result of duplicating a queued item.""" - # Internal: this field is an internal SDK API and is not part of the public surface. - enabled: Any = None - """In-process `() => boolean` gating callback (CLI-only optimization). Marked internal: - replaced by runtime-side enable/disable RPCs in the SDK migration. - """ + id: str + """Fresh stable opaque id assigned to the duplicate.""" @staticmethod - def from_dict(obj: Any) -> 'SessionsRegisterExtensionToolsOnSessionOptions': + def from_dict(obj: Any) -> 'QueueDuplicateAtResult': assert isinstance(obj, dict) - enabled = obj.get("enabled") - return SessionsRegisterExtensionToolsOnSessionOptions(enabled) + id = from_str(obj.get("id")) + return QueueDuplicateAtResult(id) def to_dict(self) -> dict: result: dict = {} - if self.enabled is not None: - result["enabled"] = self.enabled + result["id"] = from_str(self.id) return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class ReleaseEventInterestParams: - """Opaque handle previously returned by `registerInterest` to release.""" +class QueueEnqueueResumePendingResult: + """Result of enqueueing the resume-pending wake item.""" + + queued: bool + """True when a wake item was newly queued.""" + + @staticmethod + def from_dict(obj: Any) -> 'QueueEnqueueResumePendingResult': + assert isinstance(obj, dict) + queued = from_bool(obj.get("queued")) + return QueueEnqueueResumePendingResult(queued) + + def to_dict(self) -> dict: + result: dict = {} + result["queued"] = from_bool(self.queued) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class QueueFinishDeferredIdleDrainRequest: + """Inputs for completing a deferred-idle drain.""" + + active_background_work: bool + """Whether the host still has active background work.""" + + has_pending: bool + """Whether native queued work remains.""" + + @staticmethod + def from_dict(obj: Any) -> 'QueueFinishDeferredIdleDrainRequest': + assert isinstance(obj, dict) + active_background_work = from_bool(obj.get("activeBackgroundWork")) + has_pending = from_bool(obj.get("hasPending")) + return QueueFinishDeferredIdleDrainRequest(active_background_work, has_pending) + + def to_dict(self) -> dict: + result: dict = {} + result["activeBackgroundWork"] = from_bool(self.active_background_work) + result["hasPending"] = from_bool(self.has_pending) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class QueueFinishDeferredIdleDrainResult: + """Action selected by the native deferred-idle drain.""" + + aborted: bool + """Whether the deferred idle was caused by an aborted foreground turn.""" + + action: str + """One of none, processQueue, or emitSessionIdle.""" + + @staticmethod + def from_dict(obj: Any) -> 'QueueFinishDeferredIdleDrainResult': + assert isinstance(obj, dict) + aborted = from_bool(obj.get("aborted")) + action = from_str(obj.get("action")) + return QueueFinishDeferredIdleDrainResult(aborted, action) + + def to_dict(self) -> dict: + result: dict = {} + result["aborted"] = from_bool(self.aborted) + result["action"] = from_str(self.action) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class QueueHasPendingResult: + """Whether the native queue has pending work.""" + + has_pending: bool + """True when queued or immediate native work is pending.""" + + @staticmethod + def from_dict(obj: Any) -> 'QueueHasPendingResult': + assert isinstance(obj, dict) + has_pending = from_bool(obj.get("hasPending")) + return QueueHasPendingResult(has_pending) + + def to_dict(self) -> dict: + result: dict = {} + result["hasPending"] = from_bool(self.has_pending) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +class SendAgentMode(Enum): + """Optional explicit agent mode. When omitted, the session's current mode is assigned. + + The UI mode the agent was in when this message was sent. Defaults to the session's + current mode. + + Agent mode stored on this queued entry, as stamped when it was enqueued. Items without an + explicit mode report interactive. This is not necessarily the mode that will constrain + the turn: a plan or autopilot session applies its own write gate, continuation loop and + permission posture to every drained item regardless of the mode stored here. + + The UI mode the agent was in when these messages were sent. Defaults to the session's + current mode. + """ + AUTOPILOT = "autopilot" + INTERACTIVE = "interactive" + PLAN = "plan" + SHELL = "shell" + +# Experimental: this type is part of an experimental API and may change or be removed. +class SendMode(Enum): + """Accepted for SendOptions compatibility but ignored; inserted items always use queued + delivery semantics. + + How to deliver the message. `enqueue` (default) appends to the message queue. `immediate` + interjects during an in-progress turn. + + How to deliver the messages. `enqueue` (default) appends to the message queue. + `immediate` interjects during an in-progress turn. + """ + ENQUEUE = "enqueue" + IMMEDIATE = "immediate" + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class QueueInsertAtResult: + """Result of inserting a queued message.""" + + id: str + """Fresh stable opaque id assigned to the inserted item.""" + + @staticmethod + def from_dict(obj: Any) -> 'QueueInsertAtResult': + assert isinstance(obj, dict) + id = from_str(obj.get("id")) + return QueueInsertAtResult(id) + + def to_dict(self) -> dict: + result: dict = {} + result["id"] = from_str(self.id) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class QueueMoveItemRequest: + """Parameters for moving a queued item by stable id.""" + + id: str + """Stable opaque queued-item id.""" + + to_position: int + """Zero-based target position in the public visible queue. Values outside the queue clamp to + an end. + """ + + @staticmethod + def from_dict(obj: Any) -> 'QueueMoveItemRequest': + assert isinstance(obj, dict) + id = from_str(obj.get("id")) + to_position = from_int(obj.get("toPosition")) + return QueueMoveItemRequest(id, to_position) + + def to_dict(self) -> dict: + result: dict = {} + result["id"] = from_str(self.id) + result["toPosition"] = from_int(self.to_position) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class QueueMoveItemResult: + """Result of moving a queued item.""" + + changed: bool + """True when the item changed position; false when it was already at the requested position.""" + + @staticmethod + def from_dict(obj: Any) -> 'QueueMoveItemResult': + assert isinstance(obj, dict) + changed = from_bool(obj.get("changed")) + return QueueMoveItemResult(changed) + + def to_dict(self) -> dict: + result: dict = {} + result["changed"] = from_bool(self.changed) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +class QueuePendingItemsKind(Enum): + """Whether this item is a queued user message or a queued slash command / model change""" + + COMMAND = "command" + MESSAGE = "message" + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class QueueRemoveAtRequest: + """Parameters for removing a queued item by stable id.""" + + id: str + + @staticmethod + def from_dict(obj: Any) -> 'QueueRemoveAtRequest': + assert isinstance(obj, dict) + id = from_str(obj.get("id")) + return QueueRemoveAtRequest(id) + + def to_dict(self) -> dict: + result: dict = {} + result["id"] = from_str(self.id) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class QueueRemoveAtResult: + """Result of removing a queued item.""" + + removed: bool + """True when the addressed item was removed.""" + + @staticmethod + def from_dict(obj: Any) -> 'QueueRemoveAtResult': + assert isinstance(obj, dict) + removed = from_bool(obj.get("removed")) + return QueueRemoveAtResult(removed) + + def to_dict(self) -> dict: + result: dict = {} + result["removed"] = from_bool(self.removed) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class QueueRemoveMostRecentResult: + """Indicates whether a user-facing pending item was removed.""" + + removed: bool + """True if a user-facing pending item was removed (LIFO across both queues); false when no + removable items remained. + """ + + @staticmethod + def from_dict(obj: Any) -> 'QueueRemoveMostRecentResult': + assert isinstance(obj, dict) + removed = from_bool(obj.get("removed")) + return QueueRemoveMostRecentResult(removed) + + def to_dict(self) -> dict: + result: dict = {} + result["removed"] = from_bool(self.removed) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class QueueSendNowRequest: + """Parameters for steering a queued message into a live turn.""" + + id: str + + @staticmethod + def from_dict(obj: Any) -> 'QueueSendNowRequest': + assert isinstance(obj, dict) + id = from_str(obj.get("id")) + return QueueSendNowRequest(id) + + def to_dict(self) -> dict: + result: dict = {} + result["id"] = from_str(self.id) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class QueueSendNowResult: + """Result of trying to steer a queued message into a live turn.""" + + steered: bool + """True when the item was accepted into the steering lane; false when no main turn was live.""" + + @staticmethod + def from_dict(obj: Any) -> 'QueueSendNowResult': + assert isinstance(obj, dict) + steered = from_bool(obj.get("steered")) + return QueueSendNowResult(steered) + + def to_dict(self) -> dict: + result: dict = {} + result["steered"] = from_bool(self.steered) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class QueueSetDrainPausedRequest: + """Parameters for acquiring or releasing the queued-lane drain pause. Acquisition is + exclusive and non-idempotent: `paused: true` against an already-paused session fails with + `queue_already_paused`. The pause is never released automatically — it is not tied to the + caller's lifetime, so a client that exits without sending `paused: false` leaves the lane + frozen. Release is unowned: `paused: false` clears the pause for any caller, including + one that never acquired it. + """ + paused: bool + + @staticmethod + def from_dict(obj: Any) -> 'QueueSetDrainPausedRequest': + assert isinstance(obj, dict) + paused = from_bool(obj.get("paused")) + return QueueSetDrainPausedRequest(paused) + + def to_dict(self) -> dict: + result: dict = {} + result["paused"] = from_bool(self.paused) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class QueueUpdateTextRequest: + """Parameters for editing a single queued message.""" + + id: str + prompt: str + display_prompt: str | None = None + + @staticmethod + def from_dict(obj: Any) -> 'QueueUpdateTextRequest': + assert isinstance(obj, dict) + id = from_str(obj.get("id")) + prompt = from_str(obj.get("prompt")) + display_prompt = from_union([from_str, from_none], obj.get("displayPrompt")) + return QueueUpdateTextRequest(id, prompt, display_prompt) + + def to_dict(self) -> dict: + result: dict = {} + result["id"] = from_str(self.id) + result["prompt"] = from_str(self.prompt) + if self.display_prompt is not None: + result["displayPrompt"] = from_union([from_str, from_none], self.display_prompt) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class QueueUpdateTextResult: + """Result of editing a queued message.""" + + updated: bool + """True when the stored text changed.""" + + @staticmethod + def from_dict(obj: Any) -> 'QueueUpdateTextResult': + assert isinstance(obj, dict) + updated = from_bool(obj.get("updated")) + return QueueUpdateTextResult(updated) + + def to_dict(self) -> dict: + result: dict = {} + result["updated"] = from_bool(self.updated) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class QueuedCommandHandled: + """Queued-command response indicating the host executed the command, with an optional flag + to stop queue processing. + """ + handled: ClassVar[bool] = True + """The host actually executed the queued command.""" + + stop_processing_queue: bool | None = None + """When true, the runtime will not process subsequent queued commands until a new request + comes in. + """ + + @staticmethod + def from_dict(obj: Any) -> 'QueuedCommandHandled': + assert isinstance(obj, dict) + stop_processing_queue = from_union([from_bool, from_none], obj.get("stopProcessingQueue")) + return QueuedCommandHandled(stop_processing_queue) + + def to_dict(self) -> dict: + result: dict = {} + result["handled"] = self.handled + if self.stop_processing_queue is not None: + result["stopProcessingQueue"] = from_union([from_bool, from_none], self.stop_processing_queue) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class QueuedCommandNotHandled: + """Queued-command response indicating the host did not execute the command and the queue may + continue. + """ + handled: ClassVar[bool] = False + """The host did not execute the queued command. Unblocks the queue without claiming the + command was processed (e.g. when the handler threw before completing). + """ + + @staticmethod + def from_dict(obj: Any) -> 'QueuedCommandNotHandled': + assert isinstance(obj, dict) + return QueuedCommandNotHandled() + + def to_dict(self) -> dict: + result: dict = {} + result["handled"] = self.handled + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class RegisterEventInterestParams: + """Event type to register consumer interest for, used by runtime gating logic.""" + + event_type: str + """The event type the consumer wants the runtime to treat as 'observed' for + behavior-switching gating. Some runtime code paths inspect whether any consumer is + interested in a specific event type and choose a different implementation accordingly + (e.g. `mcp.oauth_required`: when interest is registered the runtime delegates interactive + OAuth token acquisition to the consumer via `mcp.oauth_required` events; when no interest + is registered the runtime still attempts non-interactive reconnect from cached or + refreshable tokens, and only marks the server `needs-auth` if usable credentials are + unavailable — it does not open a browser or start interactive OAuth without a consumer). + SDK clients that long-poll events do NOT automatically appear as listeners to these + gating checks — they must explicitly call `registerInterest` for each event type they + want the runtime to count as having a consumer. Multiple registrations for the same event + type from the same or different consumers are tracked independently and must each be + released. See: `mcp.oauth_required`, `sampling.requested`, `auto_mode_switch.requested`, + `session_limits_exhausted.requested`, `user_input.requested`, `elicitation.requested`, + `command.queued`, `exit_plan_mode.requested`. + """ + + @staticmethod + def from_dict(obj: Any) -> 'RegisterEventInterestParams': + assert isinstance(obj, dict) + event_type = from_str(obj.get("eventType")) + return RegisterEventInterestParams(event_type) + + def to_dict(self) -> dict: + result: dict = {} + result["eventType"] = from_str(self.event_type) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class RegisterEventInterestResult: + """Opaque handle representing an event-type interest registration.""" + + handle: str + """Opaque handle for this registration. Pass to releaseInterest to release. Each call to + registerInterest produces a fresh handle, even when the same eventType is registered + multiple times. + """ + + @staticmethod + def from_dict(obj: Any) -> 'RegisterEventInterestResult': + assert isinstance(obj, dict) + handle = from_str(obj.get("handle")) + return RegisterEventInterestResult(handle) + + def to_dict(self) -> dict: + result: dict = {} + result["handle"] = from_str(self.handle) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionsRegisterExtensionToolsOnSessionOptions: + """Optional registration options.""" + + # Internal: this field is an internal SDK API and is not part of the public surface. + enabled: Any = None + """In-process `() => boolean` gating callback (CLI-only optimization). Marked internal: + replaced by runtime-side enable/disable RPCs in the SDK migration. + """ + + @staticmethod + def from_dict(obj: Any) -> 'SessionsRegisterExtensionToolsOnSessionOptions': + assert isinstance(obj, dict) + enabled = obj.get("enabled") + return SessionsRegisterExtensionToolsOnSessionOptions(enabled) + + def to_dict(self) -> dict: + result: dict = {} + if self.enabled is not None: + result["enabled"] = self.enabled + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ReleaseEventInterestParams: + """Opaque handle previously returned by `registerInterest` to release.""" handle: str """Handle returned by a previous `registerInterest` call. Idempotent: releasing an unknown @@ -6914,6 +8140,40 @@ def to_dict(self) -> dict: result["branch"] = from_union([from_str, from_none], self.branch) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SandboxConfigAuth: + """Credential-injection capability flags. + + Credential-injection capability flags applied while the sandbox is enabled. + """ + gh: bool | None = None + """Whether to export `GH_TOKEN` so the `gh` CLI authenticates inside the sandbox without the + OS keyring the sandbox blocks. Default: false (opt-in). + """ + git: bool | None = None + """Whether to inject git credentials as an `http..extraheader` so authenticated HTTPS + git works inside the sandbox without the shell-based credential helper the sandbox + blocks. github.com is served by the Copilot token; every other forge (Azure DevOps, + GitHub Enterprise Server, GitLab, ...) by a credential the host resolves from the user's + own helper before the sandbox is applied. Default: false (opt-in). + """ + + @staticmethod + def from_dict(obj: Any) -> 'SandboxConfigAuth': + assert isinstance(obj, dict) + gh = from_union([from_bool, from_none], obj.get("gh")) + git = from_union([from_bool, from_none], obj.get("git")) + return SandboxConfigAuth(gh, git) + + def to_dict(self) -> dict: + result: dict = {} + if self.gh is not None: + result["gh"] = from_union([from_bool, from_none], self.gh) + if self.git is not None: + result["git"] = from_union([from_bool, from_none], self.git) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SandboxConfigUserPolicyExperimentalSeatbelt: @@ -6974,28 +8234,52 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SandboxConfigUserPolicyNetwork: - """Network rules to merge into the base policy.""" - - allow_local_network: bool | None = None - """Whether traffic to local/loopback addresses is allowed.""" +class SandboxConfigUserPolicyNetworkProxy: + """HTTP proxy the sandboxed process routes traffic through. Enforced on Windows and + cooperative (honored by well-behaved tools, not strictly enforced) on Linux and macOS. + Credentials go in the separate `username`/`password` fields. A credential-free http:// + loopback proxy URL is routed through the localhost proxy automatically; an https:// or + authenticated loopback URL is used as-is. - allow_outbound: bool | None = None - """Whether outbound network traffic is allowed at all.""" + HTTP proxy configuration for sandboxed traffic. + """ + url: str + """Proxy URL (e.g. http://proxy.example.com:8080). The port is optional and defaults to the + scheme's standard port when omitted. Credentials must not be embedded here — a + `user:pass@` authority is rejected; put them in the separate `username`/`password` + fields. A credential-free http:// loopback URL is routed through the localhost proxy + automatically; loopback covers localhost and any *.localhost subdomain, the whole + 127.0.0.0/8 range, ::1, and IPv4-mapped loopback (::ffff:127.0.0.1). An https:// URL, or + one with a username/password set, is used as-is. + """ + password: str | None = None + """Optional password for proxy authentication, combined with the URL at spawn time. The + persisted value may be a literal password, a `${secret:…}` reference resolved from the OS + keychain, or a `${VAR}`/`$VAR` environment reference; it is resolved just before the + sandboxed process routes through the proxy. The /sandbox dialog stores a real password in + the OS keychain and persists only a `${secret:…}` placeholder (never plaintext in + settings.json); the field is masked in the dialog and redacted by /settings show. + """ + username: str | None = None + """Optional username for proxy authentication. Combined with the URL (and `password`) into + `user:pass@host` when the sandboxed process routes through the proxy. + """ @staticmethod - def from_dict(obj: Any) -> 'SandboxConfigUserPolicyNetwork': + def from_dict(obj: Any) -> 'SandboxConfigUserPolicyNetworkProxy': assert isinstance(obj, dict) - allow_local_network = from_union([from_bool, from_none], obj.get("allowLocalNetwork")) - allow_outbound = from_union([from_bool, from_none], obj.get("allowOutbound")) - return SandboxConfigUserPolicyNetwork(allow_local_network, allow_outbound) + url = from_str(obj.get("url")) + password = from_union([from_str, from_none], obj.get("password")) + username = from_union([from_str, from_none], obj.get("username")) + return SandboxConfigUserPolicyNetworkProxy(url, password, username) def to_dict(self) -> dict: result: dict = {} - if self.allow_local_network is not None: - result["allowLocalNetwork"] = from_union([from_bool, from_none], self.allow_local_network) - if self.allow_outbound is not None: - result["allowOutbound"] = from_union([from_bool, from_none], self.allow_outbound) + result["url"] = from_str(self.url) + if self.password is not None: + result["password"] = from_union([from_str, from_none], self.password) + if self.username is not None: + result["username"] = from_union([from_str, from_none], self.username) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -7020,10 +8304,126 @@ def to_dict(self) -> dict: result["keychainAccess"] = from_union([from_bool, from_none], self.keychain_access) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ScheduleAddAtRequest: + """Register an absolute-time scheduled prompt.""" + + at: int + """Epoch milliseconds when the prompt should fire.""" + + prompt: str + """Prompt text to enqueue when the schedule fires.""" + + display_prompt: str | None = None + """Optional display-only prompt label.""" + + recurring: bool | None = None + """Whether the schedule should re-arm after each tick. Defaults to false.""" + + @staticmethod + def from_dict(obj: Any) -> 'ScheduleAddAtRequest': + assert isinstance(obj, dict) + at = from_int(obj.get("at")) + prompt = from_str(obj.get("prompt")) + display_prompt = from_union([from_str, from_none], obj.get("displayPrompt")) + recurring = from_union([from_bool, from_none], obj.get("recurring")) + return ScheduleAddAtRequest(at, prompt, display_prompt, recurring) + + def to_dict(self) -> dict: + result: dict = {} + result["at"] = from_int(self.at) + result["prompt"] = from_str(self.prompt) + if self.display_prompt is not None: + result["displayPrompt"] = from_union([from_str, from_none], self.display_prompt) + if self.recurring is not None: + result["recurring"] = from_union([from_bool, from_none], self.recurring) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ScheduleAddCronRequest: + """Register a cron scheduled prompt.""" + + cron: str + """5-field cron expression.""" + + prompt: str + """Prompt text to enqueue when the schedule fires.""" + + display_prompt: str | None = None + """Optional display-only prompt label.""" + + recurring: bool | None = None + """Whether the schedule should re-arm after each tick. Defaults to true.""" + + tz: str | None = None + """IANA timezone for evaluating the cron expression.""" + + @staticmethod + def from_dict(obj: Any) -> 'ScheduleAddCronRequest': + assert isinstance(obj, dict) + cron = from_str(obj.get("cron")) + prompt = from_str(obj.get("prompt")) + display_prompt = from_union([from_str, from_none], obj.get("displayPrompt")) + recurring = from_union([from_bool, from_none], obj.get("recurring")) + tz = from_union([from_str, from_none], obj.get("tz")) + return ScheduleAddCronRequest(cron, prompt, display_prompt, recurring, tz) + + def to_dict(self) -> dict: + result: dict = {} + result["cron"] = from_str(self.cron) + result["prompt"] = from_str(self.prompt) + if self.display_prompt is not None: + result["displayPrompt"] = from_union([from_str, from_none], self.display_prompt) + if self.recurring is not None: + result["recurring"] = from_union([from_bool, from_none], self.recurring) + if self.tz is not None: + result["tz"] = from_union([from_str, from_none], self.tz) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ScheduleAddRequest: + """Register a relative-interval scheduled prompt.""" + + interval: str + """Human-readable interval such as `30s`, `5m`, or `2h`.""" + + prompt: str + """Prompt text to enqueue when the schedule fires.""" + + display_prompt: str | None = None + """Optional display-only prompt label.""" + + recurring: bool | None = None + """Whether the schedule should re-arm after each tick. Defaults to true.""" + + @staticmethod + def from_dict(obj: Any) -> 'ScheduleAddRequest': + assert isinstance(obj, dict) + interval = from_str(obj.get("interval")) + prompt = from_str(obj.get("prompt")) + display_prompt = from_union([from_str, from_none], obj.get("displayPrompt")) + recurring = from_union([from_bool, from_none], obj.get("recurring")) + return ScheduleAddRequest(interval, prompt, display_prompt, recurring) + + def to_dict(self) -> dict: + result: dict = {} + result["interval"] = from_str(self.interval) + result["prompt"] = from_str(self.prompt) + if self.display_prompt is not None: + result["displayPrompt"] = from_union([from_str, from_none], self.display_prompt) + if self.recurring is not None: + result["recurring"] = from_union([from_bool, from_none], self.recurring) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ScheduleEntry: - """Scheduled prompt entry with ID, timing (`intervalMs`, `cron`, or `at`), prompt text, + """The registered or updated schedule entry. + + Scheduled prompt entry with ID, timing (`intervalMs`, `cron`, or `at`), prompt text, recurrence, and next run time. The removed entry, or omitted if no entry matched. @@ -7096,6 +8496,74 @@ def to_dict(self) -> dict: result["tz"] = from_union([from_str, from_none], self.tz) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ScheduleAddSelfPacedRequest: + """Register a self-paced scheduled prompt.""" + + prompt: str + """Prompt text to enqueue when the schedule fires.""" + + display_prompt: str | None = None + """Optional display-only prompt label.""" + + @staticmethod + def from_dict(obj: Any) -> 'ScheduleAddSelfPacedRequest': + assert isinstance(obj, dict) + prompt = from_str(obj.get("prompt")) + display_prompt = from_union([from_str, from_none], obj.get("displayPrompt")) + return ScheduleAddSelfPacedRequest(prompt, display_prompt) + + def to_dict(self) -> dict: + result: dict = {} + result["prompt"] = from_str(self.prompt) + if self.display_prompt is not None: + result["displayPrompt"] = from_union([from_str, from_none], self.display_prompt) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ScheduleHasSelfPacedResult: + """Whether the session currently has an active self-paced schedule.""" + + has_self_paced: bool + """True when at least one active schedule is self-paced.""" + + @staticmethod + def from_dict(obj: Any) -> 'ScheduleHasSelfPacedResult': + assert isinstance(obj, dict) + has_self_paced = from_bool(obj.get("hasSelfPaced")) + return ScheduleHasSelfPacedResult(has_self_paced) + + def to_dict(self) -> dict: + result: dict = {} + result["hasSelfPaced"] = from_bool(self.has_self_paced) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ScheduleRearmSelfPacedRequest: + """Re-arm a self-paced scheduled prompt.""" + + at: int + """Epoch milliseconds when the prompt should next fire.""" + + id: int + """Id of the self-paced scheduled prompt.""" + + @staticmethod + def from_dict(obj: Any) -> 'ScheduleRearmSelfPacedRequest': + assert isinstance(obj, dict) + at = from_int(obj.get("at")) + id = from_int(obj.get("id")) + return ScheduleRearmSelfPacedRequest(at, id) + + def to_dict(self) -> dict: + result: dict = {} + result["at"] = from_int(self.at) + result["id"] = from_int(self.id) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ScheduleStopRequest: @@ -7153,19 +8621,6 @@ def to_dict(self) -> dict: result["ok"] = from_bool(self.ok) return result -# Experimental: this type is part of an experimental API and may change or be removed. -class SendAgentMode(Enum): - """The UI mode the agent was in when this message was sent. Defaults to the session's - current mode. - - The UI mode the agent was in when these messages were sent. Defaults to the session's - current mode. - """ - AUTOPILOT = "autopilot" - INTERACTIVE = "interactive" - PLAN = "plan" - SHELL = "shell" - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SendAttachmentsToMessageParams: @@ -7222,10 +8677,9 @@ class SendMessageItem: """ # Internal: this field is an internal SDK API and is not part of the public surface. source: str | None = None - """Optional provenance tag copied to the resulting user.message event. Must match one of - three forms: the literal `system`, `command-` for messages originating from a - command (e.g. slash command, Mission Control command), or `schedule-` for - messages originating from a scheduled job. + """Optional provenance tag copied to the resulting user.message event. Must be `user`, + `system`, `command-` for command-originated messages, `schedule-` + for scheduled prompts, or `agent-` for prompts sent by another agent. """ @staticmethod @@ -7254,17 +8708,6 @@ def to_dict(self) -> dict: result["source"] = from_union([from_str, from_none], self.source) return result -# Experimental: this type is part of an experimental API and may change or be removed. -class SendMode(Enum): - """How to deliver the messages. `enqueue` (default) appends to the message queue. - `immediate` interjects during an in-progress turn. - - How to deliver the message. `enqueue` (default) appends to the message queue. `immediate` - interjects during an in-progress turn. - """ - ENQUEUE = "enqueue" - IMMEDIATE = "immediate" - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SendMessagesResult: @@ -7305,6 +8748,37 @@ def to_dict(self) -> dict: result["messageId"] = from_str(self.message_id) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SendSystemNotificationRequest: + """Internal request for sending a system notification.""" + + message: str + """Notification text to deliver to the model.""" + + kind: Any = None + """Optional structured notification kind.""" + + options: Any = None + """Internal delivery options, including passive policy.""" + + @staticmethod + def from_dict(obj: Any) -> 'SendSystemNotificationRequest': + assert isinstance(obj, dict) + message = from_str(obj.get("message")) + kind = obj.get("kind") + options = obj.get("options") + return SendSystemNotificationRequest(message, kind, options) + + def to_dict(self) -> dict: + result: dict = {} + result["message"] = from_str(self.message) + if self.kind is not None: + result["kind"] = self.kind + if self.options is not None: + result["options"] = self.options + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ServerSkill: @@ -7330,6 +8804,9 @@ class ServerSkill: """Optional freeform hint describing the skill's expected arguments, from the `argument-hint` frontmatter field """ + command_name: str | None = None + """Canonical slash command name used to invoke the skill, without the leading '/'""" + path: str | None = None """Absolute path to the skill file""" @@ -7345,9 +8822,10 @@ def from_dict(obj: Any) -> 'ServerSkill': source = SkillSource(obj.get("source")) user_invocable = from_bool(obj.get("userInvocable")) argument_hint = from_union([from_str, from_none], obj.get("argumentHint")) + command_name = from_union([from_str, from_none], obj.get("commandName")) path = from_union([from_str, from_none], obj.get("path")) project_path = from_union([from_str, from_none], obj.get("projectPath")) - return ServerSkill(description, enabled, name, source, user_invocable, argument_hint, path, project_path) + return ServerSkill(description, enabled, name, source, user_invocable, argument_hint, command_name, path, project_path) def to_dict(self) -> dict: result: dict = {} @@ -7358,6 +8836,8 @@ def to_dict(self) -> dict: result["userInvocable"] = from_bool(self.user_invocable) if self.argument_hint is not None: result["argumentHint"] = from_union([from_str, from_none], self.argument_hint) + if self.command_name is not None: + result["commandName"] = from_union([from_str, from_none], self.command_name) if self.path is not None: result["path"] = from_union([from_str, from_none], self.path) if self.project_path is not None: @@ -7410,24 +8890,54 @@ def to_dict(self) -> dict: result["freedBytes"] = from_dict(from_int, self.freed_bytes) return result -# Experimental: this type is part of an experimental API and may change or be removed. -class SessionCapability(Enum): - """Session capability enabled for this session - - Session capability id - """ - ASK_USER = "ask-user" - CANVAS_RENDERER = "canvas-renderer" - CLI_DOCUMENTATION = "cli-documentation" - ELICITATION = "elicitation" - INTERACTIVE_MODE = "interactive-mode" - MCP_APPS = "mcp-apps" - MEMORY = "memory" - PLAN_MODE = "plan-mode" - SESSION_STORE = "session-store" - SYSTEM_NOTIFICATIONS = "system-notifications" - TUI_HINTS = "tui-hints" - +# Experimental: this type is part of an experimental API and may change or be removed. +class SessionCapability(Enum): + """Session capability enabled for this session + + Session capability id + """ + ASK_USER = "ask-user" + CANVAS_RENDERER = "canvas-renderer" + CLI_DOCUMENTATION = "cli-documentation" + ELICITATION = "elicitation" + INTERACTIVE_MODE = "interactive-mode" + MCP_APPS = "mcp-apps" + MEMORY = "memory" + PLAN_MODE = "plan-mode" + SESSION_STORE = "session-store" + SYSTEM_NOTIFICATIONS = "system-notifications" + TUI_HINTS = "tui-hints" + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionCommandsListRequest: + include_builtins: bool | None = None + """Include runtime built-in commands""" + + include_client_commands: bool | None = None + """Include commands registered by protocol clients, including SDK clients and extensions""" + + include_skills: bool | None = None + """Include enabled user-invocable skills and commands""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionCommandsListRequest': + assert isinstance(obj, dict) + include_builtins = from_union([from_bool, from_none], obj.get("includeBuiltins")) + include_client_commands = from_union([from_bool, from_none], obj.get("includeClientCommands")) + include_skills = from_union([from_bool, from_none], obj.get("includeSkills")) + return SessionCommandsListRequest(include_builtins, include_client_commands, include_skills) + + def to_dict(self) -> dict: + result: dict = {} + if self.include_builtins is not None: + result["includeBuiltins"] = from_union([from_bool, from_none], self.include_builtins) + if self.include_client_commands is not None: + result["includeClientCommands"] = from_union([from_bool, from_none], self.include_client_commands) + if self.include_skills is not None: + result["includeSkills"] = from_union([from_bool, from_none], self.include_skills) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionFSAppendFileRequest: @@ -7779,11 +9289,21 @@ def to_dict(self) -> dict: class SessionFSSqliteQueryType(Enum): """How to execute the query: 'exec' for DDL/multi-statement (no results), 'query' for SELECT (returns rows), 'run' for INSERT/UPDATE/DELETE (returns rowsAffected) + + How to execute the statement. """ EXEC = "exec" QUERY = "query" RUN = "run" +# Experimental: this type is part of an experimental API and may change or be removed. +class SessionFSSqliteTransactionErrorClass(Enum): + """SQLite transaction failure classification.""" + + BUSY_OR_LOCKED = "busyOrLocked" + FATAL = "fatal" + POST_COMMIT_AMBIGUOUS = "postCommitAmbiguous" + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionFSStatRequest: @@ -7843,6 +9363,83 @@ def to_dict(self) -> dict: result["mode"] = from_union([from_int, from_none], self.mode) return result +class Trigger(Enum): + """What initiated this compaction request, recorded as the `trigger` on the persisted + `session.compaction_start` / `session.compaction_complete` events. When absent, the + compaction is persisted without trigger attribution (initiator unknown). + """ + MANUAL = "manual" + MODEL_SWITCH = "model_switch" + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionLimitPredictionBaselineData: + """Baseline data provenance for a prediction. + + Baseline data provenance. + """ + window_end: str + """End of the baseline data slice.""" + + window_start: str + """Start of the baseline data slice.""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionLimitPredictionBaselineData': + assert isinstance(obj, dict) + window_end = from_str(obj.get("windowEnd")) + window_start = from_str(obj.get("windowStart")) + return SessionLimitPredictionBaselineData(window_end, window_start) + + def to_dict(self) -> dict: + result: dict = {} + result["windowEnd"] = from_str(self.window_end) + result["windowStart"] = from_str(self.window_start) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +class SessionLimitPredictionClientType(Enum): + """Client population used for the prediction baseline. + + Client population used for the prediction. + + Client type to size for. Defaults to `cli-interactive`. + """ + CLI_INTERACTIVE = "cli-interactive" + CLI_PROMPT = "cli-prompt" + +# Experimental: this type is part of an experimental API and may change or be removed. +class SessionLimitPredictionTier(Enum): + """Tier chosen as the recommended cap. + + Semantic usage tier used for a recommended cap or additional headroom. + """ + ADDITIONAL_HEADROOM = "additional_headroom" + GENEROUS_HEADROOM = "generous_headroom" + MAXIMUM_HEADROOM = "maximum_headroom" + RECOMMENDED = "recommended" + +# Experimental: this type is part of an experimental API and may change or be removed. +class SessionLimitPredictionSource(Enum): + """Baseline fallback level used to create the prediction.""" + + FAMILY = "family" + GLOBAL = "global" + MODEL = "model" + +class SessionLimitPredictionResultKind(Enum): + AVAILABLE = "available" + UNAVAILABLE = "unavailable" + +# Experimental: this type is part of an experimental API and may change or be removed. +class SessionLimitPredictionUnavailableReason(Enum): + """Reason no prediction is available. + + Reason a prediction could not be computed. + """ + AUTO_UNRESOLVED = "auto_unresolved" + NO_MODEL = "no_model" + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionList: @@ -7927,6 +9524,24 @@ def to_dict(self) -> dict: result["startupPrompts"] = from_list(from_str, self.startup_prompts) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionModelListRequest: + skip_cache: bool | None = None + """If true, bypasses the per-session model list cache and re-fetches from CAPI.""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionModelListRequest': + assert isinstance(obj, dict) + skip_cache = from_union([from_bool, from_none], obj.get("skipCache")) + return SessionModelListRequest(skip_cache) + + def to_dict(self) -> dict: + result: dict = {} + if self.skip_cache is not None: + result["skipCache"] = from_union([from_bool, from_none], self.skip_cache) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource: @@ -7948,6 +9563,23 @@ def to_dict(self) -> dict: result["type"] = from_str(self.type) return result +# Experimental: this type is part of an experimental API and may change or be removed. +class ShellInitProfile(Enum): + """Controls automatic non-interactive profile loading where supported. Explicit initScripts + are unaffected. + """ + NONE = "none" + NON_INTERACTIVE = "non-interactive" + +# Experimental: this type is part of an experimental API and may change or be removed. +class ShellInitScriptShell(Enum): + """Built-in shell that may source this script. + + Supported built-in shells for initialization scripts. + """ + BASH = "bash" + POWERSHELL = "powershell" + class SessionOpenParamsKind(Enum): ATTACH = "attach" CLOUD = "cloud" @@ -7985,6 +9617,52 @@ class SessionsOpenStatus(Enum): NOT_FOUND = "not_found" RESUMED = "resumed" +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionPluginsReloadRequest: + defer_repo_hooks: bool | None = None + """When true, skip repo-level hooks during the hook reload. Use before folder trust is + confirmed; load them post-trust via `sessions.loadDeferredRepoHooks`. + """ + reload_custom_agents: bool | None = None + """Re-run custom-agent discovery after refreshing plugins. Defaults to true.""" + + reload_extensions: bool | None = None + """Re-discover and relaunch subprocess extensions (including plugin-shipped extensions) + after refreshing plugins. Defaults to true. Has no effect when the session has no active + extension controller (e.g. extensions were not requested for the session). + """ + reload_hooks: bool | None = None + """Re-load user, plugin, and (subject to `deferRepoHooks`) repo hooks. Defaults to true. Has + no effect when the host has not registered a hook reloader (e.g. remote sessions). + """ + reload_mcp: bool | None = None + """Reload MCP server connections after refreshing plugins. Defaults to true.""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionPluginsReloadRequest': + assert isinstance(obj, dict) + defer_repo_hooks = from_union([from_bool, from_none], obj.get("deferRepoHooks")) + reload_custom_agents = from_union([from_bool, from_none], obj.get("reloadCustomAgents")) + reload_extensions = from_union([from_bool, from_none], obj.get("reloadExtensions")) + reload_hooks = from_union([from_bool, from_none], obj.get("reloadHooks")) + reload_mcp = from_union([from_bool, from_none], obj.get("reloadMcp")) + return SessionPluginsReloadRequest(defer_repo_hooks, reload_custom_agents, reload_extensions, reload_hooks, reload_mcp) + + def to_dict(self) -> dict: + result: dict = {} + if self.defer_repo_hooks is not None: + result["deferRepoHooks"] = from_union([from_bool, from_none], self.defer_repo_hooks) + if self.reload_custom_agents is not None: + result["reloadCustomAgents"] = from_union([from_bool, from_none], self.reload_custom_agents) + if self.reload_extensions is not None: + result["reloadExtensions"] = from_union([from_bool, from_none], self.reload_extensions) + if self.reload_hooks is not None: + result["reloadHooks"] = from_union([from_bool, from_none], self.reload_hooks) + if self.reload_mcp is not None: + result["reloadMcp"] = from_union([from_bool, from_none], self.reload_mcp) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionPruneResult: @@ -8507,6 +10185,31 @@ def to_dict(self) -> dict: result: dict = {} return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionsDeleteRequest: + """Session ID to delete from disk.""" + + session_id: str + """Session ID to delete""" + + session_path: str | None = None + """Internal resolved session directory path to delete""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionsDeleteRequest': + assert isinstance(obj, dict) + session_id = from_str(obj.get("sessionId")) + session_path = from_union([from_none, from_str], obj.get("sessionPath")) + return SessionsDeleteRequest(session_id, session_path) + + def to_dict(self) -> dict: + result: dict = {} + result["sessionId"] = from_str(self.session_id) + if self.session_path is not None: + result["sessionPath"] = from_union([from_none, from_str], self.session_path) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionsFindByPrefixRequest: @@ -8743,6 +10446,25 @@ def to_dict(self) -> dict: result["sessionId"] = from_union([from_str, from_none], self.session_id) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionsGetMetadataRequest: + """Session ID whose persisted metadata should be read.""" + + session_id: str + """Session ID to inspect""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionsGetMetadataRequest': + assert isinstance(obj, dict) + session_id = from_str(obj.get("sessionId")) + return SessionsGetMetadataRequest(session_id) + + def to_dict(self) -> dict: + result: dict = {} + result["sessionId"] = from_str(self.session_id) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionsGetPersistedRemoteSteerableRequest: @@ -8785,6 +10507,45 @@ def to_dict(self) -> dict: result["remoteSteerable"] = from_union([from_bool, from_none], self.remote_steerable) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionsListNonEmptySessionIDSRequest: + """Limit for non-empty local session IDs.""" + + limit: int | None = None + """Maximum number of session IDs to return.""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionsListNonEmptySessionIDSRequest': + assert isinstance(obj, dict) + limit = from_union([from_int, from_none], obj.get("limit")) + return SessionsListNonEmptySessionIDSRequest(limit) + + def to_dict(self) -> dict: + result: dict = {} + if self.limit is not None: + result["limit"] = from_union([from_int, from_none], self.limit) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionsListNonEmptySessionIDSResult: + """Recent local session IDs that contain user-visible history.""" + + session_ids: list[str] + """Session IDs ordered newest-first.""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionsListNonEmptySessionIDSResult': + assert isinstance(obj, dict) + session_ids = from_list(from_str, obj.get("sessionIds")) + return SessionsListNonEmptySessionIDSResult(session_ids) + + def to_dict(self) -> dict: + result: dict = {} + result["sessionIds"] = from_list(from_str, self.session_ids) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionsLoadDeferredRepoHooksRequest: @@ -9239,6 +11000,9 @@ class Skill: """Optional freeform hint describing the skill's expected arguments, from the `argument-hint` frontmatter field """ + command_name: str | None = None + """Canonical slash command name used to invoke the skill, without the leading '/'""" + path: str | None = None """Absolute path to the skill file""" @@ -9254,9 +11018,10 @@ def from_dict(obj: Any) -> 'Skill': source = SkillSource(obj.get("source")) user_invocable = from_bool(obj.get("userInvocable")) argument_hint = from_union([from_str, from_none], obj.get("argumentHint")) + command_name = from_union([from_str, from_none], obj.get("commandName")) path = from_union([from_str, from_none], obj.get("path")) plugin_name = from_union([from_str, from_none], obj.get("pluginName")) - return Skill(description, enabled, name, source, user_invocable, argument_hint, path, plugin_name) + return Skill(description, enabled, name, source, user_invocable, argument_hint, command_name, path, plugin_name) def to_dict(self) -> dict: result: dict = {} @@ -9267,6 +11032,8 @@ def to_dict(self) -> dict: result["userInvocable"] = from_bool(self.user_invocable) if self.argument_hint is not None: result["argumentHint"] = from_union([from_str, from_none], self.argument_hint) + if self.command_name is not None: + result["commandName"] = from_union([from_str, from_none], self.command_name) if self.path is not None: result["path"] = from_union([from_str, from_none], self.path) if self.plugin_name is not None: @@ -10532,26 +12299,131 @@ class WorkspaceDiffMode(Enum): # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class WorkspacesCreateFileRequest: - """Relative path and UTF-8 content for the workspace file to create or overwrite.""" - - content: str - """File content to write as a UTF-8 string""" +class WorkspacesAddSummaryRequest: + """Compaction summary checkpoint to persist.""" + + content: str + """Markdown summary content to persist.""" + + title: str + """Summary title shown in checkpoint listings.""" + + @staticmethod + def from_dict(obj: Any) -> 'WorkspacesAddSummaryRequest': + assert isinstance(obj, dict) + content = from_str(obj.get("content")) + title = from_str(obj.get("title")) + return WorkspacesAddSummaryRequest(content, title) + + def to_dict(self) -> dict: + result: dict = {} + result["content"] = from_str(self.content) + result["title"] = from_str(self.title) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class WorkspacesAddSummaryResult: + """Persisted summary metadata and refreshed workspace metadata.""" + + summary: dict[str, Any] | None = None + workspace: dict[str, Any] | None = None + + @staticmethod + def from_dict(obj: Any) -> 'WorkspacesAddSummaryResult': + assert isinstance(obj, dict) + summary = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("summary")) + workspace = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("workspace")) + return WorkspacesAddSummaryResult(summary, workspace) + + def to_dict(self) -> dict: + result: dict = {} + if self.summary is not None: + result["summary"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.summary) + if self.workspace is not None: + result["workspace"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.workspace) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class WorkspacesAutopilotObjectiveExistsResult: + """Whether the autopilot objective file exists.""" + + exists: bool + """True when the objective file exists.""" + + @staticmethod + def from_dict(obj: Any) -> 'WorkspacesAutopilotObjectiveExistsResult': + assert isinstance(obj, dict) + exists = from_bool(obj.get("exists")) + return WorkspacesAutopilotObjectiveExistsResult(exists) + + def to_dict(self) -> dict: + result: dict = {} + result["exists"] = from_bool(self.exists) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class WorkspacesCreateFileRequest: + """Relative path and UTF-8 content for the workspace file to create or overwrite.""" + + content: str + """File content to write as a UTF-8 string""" + + path: str + """Relative path within the workspace files directory""" + + @staticmethod + def from_dict(obj: Any) -> 'WorkspacesCreateFileRequest': + assert isinstance(obj, dict) + content = from_str(obj.get("content")) + path = from_str(obj.get("path")) + return WorkspacesCreateFileRequest(content, path) + + def to_dict(self) -> dict: + result: dict = {} + result["content"] = from_str(self.content) + result["path"] = from_str(self.path) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class WorkspacesDeleteAutopilotObjectiveResult: + """Result of deleting the autopilot objective file.""" + + deleted: bool + """True when a file was deleted.""" + + @staticmethod + def from_dict(obj: Any) -> 'WorkspacesDeleteAutopilotObjectiveResult': + assert isinstance(obj, dict) + deleted = from_bool(obj.get("deleted")) + return WorkspacesDeleteAutopilotObjectiveResult(deleted) + + def to_dict(self) -> dict: + result: dict = {} + result["deleted"] = from_bool(self.deleted) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class WorkspacesEnsureRequest: + """Optional session context used when creating a local workspace.""" - path: str - """Relative path within the workspace files directory""" + context: Any = None + """Opaque workspace context supplied by the session host.""" @staticmethod - def from_dict(obj: Any) -> 'WorkspacesCreateFileRequest': + def from_dict(obj: Any) -> 'WorkspacesEnsureRequest': assert isinstance(obj, dict) - content = from_str(obj.get("content")) - path = from_str(obj.get("path")) - return WorkspacesCreateFileRequest(content, path) + context = obj.get("context") + return WorkspacesEnsureRequest(context) def to_dict(self) -> dict: result: dict = {} - result["content"] = from_str(self.content) - result["path"] = from_str(self.path) + if self.context is not None: + result["context"] = self.context return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -10573,6 +12445,25 @@ def to_dict(self) -> dict: result["files"] = from_list(from_str, self.files) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class WorkspacesReadAutopilotObjectiveResult: + """Autopilot objective file content, or null when missing.""" + + content: str | None = None + """Autopilot objective file content, or null when missing.""" + + @staticmethod + def from_dict(obj: Any) -> 'WorkspacesReadAutopilotObjectiveResult': + assert isinstance(obj, dict) + content = from_union([from_none, from_str], obj.get("content")) + return WorkspacesReadAutopilotObjectiveResult(content) + + def to_dict(self) -> dict: + result: dict = {} + result["content"] = from_union([from_none, from_str], self.content) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class WorkspacesReadCheckpointRequest: @@ -10694,6 +12585,63 @@ def to_dict(self) -> dict: result["sizeBytes"] = from_int(self.size_bytes) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class WorkspacesTruncateSummariesRequest: + """Rollback point for local workspace summaries.""" + + keep_count: int + """Number of newest summaries to keep.""" + + @staticmethod + def from_dict(obj: Any) -> 'WorkspacesTruncateSummariesRequest': + assert isinstance(obj, dict) + keep_count = from_int(obj.get("keepCount")) + return WorkspacesTruncateSummariesRequest(keep_count) + + def to_dict(self) -> dict: + result: dict = {} + result["keepCount"] = from_int(self.keep_count) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class WorkspacesWriteAutopilotObjectiveRequest: + """Autopilot objective file content to persist.""" + + content: str + """Autopilot objective file content.""" + + @staticmethod + def from_dict(obj: Any) -> 'WorkspacesWriteAutopilotObjectiveRequest': + assert isinstance(obj, dict) + content = from_str(obj.get("content")) + return WorkspacesWriteAutopilotObjectiveRequest(content) + + def to_dict(self) -> dict: + result: dict = {} + result["content"] = from_str(self.content) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class WorkspacesWriteAutopilotObjectiveResult: + """Result of writing the autopilot objective file.""" + + operation: str + """Filesystem operation performed.""" + + @staticmethod + def from_dict(obj: Any) -> 'WorkspacesWriteAutopilotObjectiveResult': + assert isinstance(obj, dict) + operation = from_str(obj.get("operation")) + return WorkspacesWriteAutopilotObjectiveResult(operation) + + def to_dict(self) -> dict: + result: dict = {} + result["operation"] = from_str(self.operation) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionAuthStatus: @@ -11173,6 +13121,32 @@ def to_dict(self) -> dict: result["summary"] = from_union([from_str, from_none], self.summary) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ContentExclusionCheckPathsResult: + """Batch content-exclusion result. Callers must fail closed when policy evaluation is + unavailable. + """ + available: bool + """Whether the session's policy service was available for the complete batch. When false, + checks is empty and callers must treat every requested path as excluded. + """ + checks: list[ContentExclusionPathCheck] + """Per-path decisions in request order. Empty when available is false.""" + + @staticmethod + def from_dict(obj: Any) -> 'ContentExclusionCheckPathsResult': + assert isinstance(obj, dict) + available = from_bool(obj.get("available")) + checks = from_list(ContentExclusionPathCheck.from_dict, obj.get("checks")) + return ContentExclusionCheckPathsResult(available, checks) + + def to_dict(self) -> dict: + result: dict = {} + result["available"] = from_bool(self.available) + result["checks"] = from_list(lambda x: to_class(ContentExclusionPathCheck, x), self.checks) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MetadataContextHeaviestMessagesResult: @@ -11430,11 +13404,101 @@ def to_dict(self) -> dict: result["outputDirectory"] = from_union([from_str, from_none], self.output_directory) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionManagedPermissions: + """Enterprise permission policy expressed with the runtime's managed permission-rule syntax.""" + + allow: list[str] | None = None + """Permission rules that allow matching operations unless another managed source, deny, or + ask rule restricts them. + """ + ask: list[str] | None = None + """Permission rules that require explicit human approval.""" + + deny: list[str] | None = None + """Permission rules that block matching operations. Deny has highest precedence.""" + + disable_bypass_permissions_mode: DisableBypassPermissionsMode | None = None + """When set to `disable`, prevents bypass/allow-all permission modes.""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionManagedPermissions': + assert isinstance(obj, dict) + allow = from_union([lambda x: from_list(from_str, x), from_none], obj.get("allow")) + ask = from_union([lambda x: from_list(from_str, x), from_none], obj.get("ask")) + deny = from_union([lambda x: from_list(from_str, x), from_none], obj.get("deny")) + disable_bypass_permissions_mode = from_union([DisableBypassPermissionsMode, from_none], obj.get("disableBypassPermissionsMode")) + return SessionManagedPermissions(allow, ask, deny, disable_bypass_permissions_mode) + + def to_dict(self) -> dict: + result: dict = {} + if self.allow is not None: + result["allow"] = from_union([lambda x: from_list(from_str, x), from_none], self.allow) + if self.ask is not None: + result["ask"] = from_union([lambda x: from_list(from_str, x), from_none], self.ask) + if self.deny is not None: + result["deny"] = from_union([lambda x: from_list(from_str, x), from_none], self.deny) + if self.disable_bypass_permissions_mode is not None: + result["disableBypassPermissionsMode"] = from_union([lambda x: to_enum(DisableBypassPermissionsMode, x), from_none], self.disable_bypass_permissions_mode) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class DiscoveredExtension: + """Discovered extension metadata and persistent enablement state.""" + + enabled: bool + """Whether this extension's persistent per-ID preference is enabled""" + + id: str + """Source-qualified ID accepted by both server and session extension enablement methods""" + + name: str + """Human-readable extension name""" + + path: str + """Absolute path to the extension entry module, suitable for revealing it in a file manager""" + + source: DiscoveredExtensionSource + """Discovery source""" + + plugin: DiscoveredExtensionPlugin | None = None + """Containing plugin metadata for plugin-contributed extensions""" + + @staticmethod + def from_dict(obj: Any) -> 'DiscoveredExtension': + assert isinstance(obj, dict) + enabled = from_bool(obj.get("enabled")) + id = from_str(obj.get("id")) + name = from_str(obj.get("name")) + path = from_str(obj.get("path")) + source = DiscoveredExtensionSource(obj.get("source")) + plugin = from_union([DiscoveredExtensionPlugin.from_dict, from_none], obj.get("plugin")) + return DiscoveredExtension(enabled, id, name, path, source, plugin) + + def to_dict(self) -> dict: + result: dict = {} + result["enabled"] = from_bool(self.enabled) + result["id"] = from_str(self.id) + result["name"] = from_str(self.name) + result["path"] = from_str(self.path) + result["source"] = to_enum(DiscoveredExtensionSource, self.source) + if self.plugin is not None: + result["plugin"] = from_union([lambda x: to_class(DiscoveredExtensionPlugin, x), from_none], self.plugin) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class EventLogReadRequest: """Cursor, batch size, and optional long-poll/filter parameters for reading session events.""" + agent_ids: list[str] | None = None + """Optional non-empty list of subagent identifiers. When provided, only events owned by one + of these agents are returned; ownership recognizes the event envelope's agentId plus + legacy data.agentId and data.parentToolCallId markers. This filter takes precedence over + agentScope. + """ agent_scope: EventsAgentScope | None = None """Agent-scope filter: 'primary' returns only main-agent events plus events whose type starts with 'subagent.' (matching the typed-subscription default behavior); 'all' returns @@ -11445,6 +13509,26 @@ class EventLogReadRequest: """Opaque cursor returned by a previous read. Omit on the first call to start from the beginning of the session's persisted history. """ + direction: EventsReadDirection | None = None + """Direction to page through the session's persisted event history. 'forward' (default) + pages from the cursor toward newer events (or from the start of history when no cursor is + given). 'backward' enables tail-first reads: with no cursor it returns the NEWEST `max` + events, and the returned cursor pages toward OLDER events on subsequent backward reads. + Events within a returned batch are always in chronological (oldest-to-newest) order, even + for a backward read. Backward reads cover PERSISTED history only; ephemeral events are + never returned by a backward read. `direction` selects the INITIAL read only: the + returned cursor is self-describing, so a continuation read pages in the cursor's own + direction regardless of the `direction` passed alongside it — a forward cursor always + pages forward and a backward cursor always pages backward. Pass the direction that + matches the cursor to avoid confusion. + """ + include_ephemeral: bool | None = None + """When false, skip ephemeral events entirely and return only durable (persisted) events. + History-backfill callers that discard ephemerals anyway should set this so the read is + bounded by the durable log length instead of racing the ephemeral ring on a busy session. + Defaults to true (ephemerals are interleaved with durable events in creation order). + Ignored by backward reads, which always cover persisted history only. + """ max: int | None = None """Maximum number of events to return in this batch (1–1000, default 200).""" @@ -11456,25 +13540,37 @@ class EventLogReadRequest: (default) returns immediately even if no events are available. Capped at 30000ms. Ephemeral events that arrive during the wait are delivered in this batch but are NOT replayable on a subsequent read (use a non-zero waitMs in your next call to capture - future ephemerals as they happen). + future ephemerals as they happen). This applies to forward reads only: a backward read + always returns immediately and ignores `waitMs`, because backward paging covers persisted + history only while new events append at the tail (the opposite end from a backward page), + so no blocking or ephemeral delivery can occur. """ @staticmethod def from_dict(obj: Any) -> 'EventLogReadRequest': assert isinstance(obj, dict) + agent_ids = from_union([lambda x: from_list(from_str, x), from_none], obj.get("agentIds")) agent_scope = from_union([EventsAgentScope, from_none], obj.get("agentScope")) cursor = from_union([from_str, from_none], obj.get("cursor")) + direction = from_union([EventsReadDirection, from_none], obj.get("direction")) + include_ephemeral = from_union([from_bool, from_none], obj.get("includeEphemeral")) max = from_union([from_int, from_none], obj.get("max")) types = from_union([lambda x: from_list(from_str, x), EventLogTypes, from_none], obj.get("types")) wait_ms = from_union([from_int, from_none], obj.get("waitMs")) - return EventLogReadRequest(agent_scope, cursor, max, types, wait_ms) + return EventLogReadRequest(agent_ids, agent_scope, cursor, direction, include_ephemeral, max, types, wait_ms) def to_dict(self) -> dict: result: dict = {} + if self.agent_ids is not None: + result["agentIds"] = from_union([lambda x: from_list(from_str, x), from_none], self.agent_ids) if self.agent_scope is not None: result["agentScope"] = from_union([lambda x: to_enum(EventsAgentScope, x), from_none], self.agent_scope) if self.cursor is not None: result["cursor"] = from_union([from_str, from_none], self.cursor) + if self.direction is not None: + result["direction"] = from_union([lambda x: to_enum(EventsReadDirection, x), from_none], self.direction) + if self.include_ephemeral is not None: + result["includeEphemeral"] = from_union([from_bool, from_none], self.include_ephemeral) if self.max is not None: result["max"] = from_union([from_int, from_none], self.max) if self.types is not None: @@ -11490,23 +13586,34 @@ class EventsReadResult: cursor: str """Opaque cursor for the next read. Pass back unchanged in the next read.cursor to continue - from where this read left off. Always present, even when no events were returned. + from where this read left off. Always present, even when no events were returned. For a + backward read this cursor pages toward OLDER events; keep passing `direction: backward` + with it (the cursor is also self-describing, so backward paging continues correctly). """ cursor_status: EventsCursorStatus """Cursor status: 'ok' means the cursor was applied successfully; 'expired' means the cursor referred to an event that no longer exists in history (e.g. truncated or compacted away) - and the read started from the beginning of the remaining history. + and the read fell back to a boundary of the remaining history. For a forward read the + fallback starts from the beginning of the remaining history; for a backward read it falls + back to the tail (the newest window). Because the fallback page is a fresh boundary + snapshot rather than a continuation of the requested cursor, it may overlap events the + consumer has already rendered — a backward fallback to the tail in particular can repeat + the newest window. On 'expired', consumers should reset or rebase their local pagination + state (or deduplicate by event id) before continuing from the returned cursor rather than + blindly appending/prepending the fallback page. """ events: list[SessionEvent] - """Events are delivered in two batches per read: persisted events first (in append order), - then ephemeral events (in seq order). When `waitMs > 0` and the catch-up batches were - empty, post-wait events follow the same two-batch ordering. Persisted and ephemeral - events do not interleave within a single read. + """Session events for this batch, merged into a single stream in creation order: durable + (persisted) events and ephemeral events interleave exactly as they were emitted. Set + `includeEphemeral: false` to receive only durable events. Ephemeral events are never + replayable once pruned from the in-memory ring, so a consumer that needs them should keep + reading with a non-zero `waitMs`. For a backward (tail-first) read, the returned window + contains persisted events only, still in chronological (oldest-to-newest) append order. """ has_more: bool - """True when the read returned `max` events and more events are available immediately. When - false, the next read with a non-zero `waitMs` will block until a new event arrives or the - wait expires. + """True when more events are available in the read's direction. For a forward read, true + means the batch returned `max` events and more are available immediately. For a backward + read, true means older persisted events remain before the returned window. """ @staticmethod @@ -11526,6 +13633,41 @@ def to_dict(self) -> dict: result["hasMore"] = from_bool(self.has_more) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ExtensionLaunchProviderResolveRequest: + """A discovered extension entrypoint that the registered integrator may classify and resolve + to an opaque launch profile. + """ + id: str + """Source-qualified extension identifier.""" + + module_path: str + """Absolute path to the discovered extension entrypoint.""" + + name: str + """Human-readable extension name.""" + + source: ExtensionSource + """Discovery source for the extension entrypoint.""" + + @staticmethod + def from_dict(obj: Any) -> 'ExtensionLaunchProviderResolveRequest': + assert isinstance(obj, dict) + id = from_str(obj.get("id")) + module_path = from_str(obj.get("modulePath")) + name = from_str(obj.get("name")) + source = ExtensionSource(obj.get("source")) + return ExtensionLaunchProviderResolveRequest(id, module_path, name, source) + + def to_dict(self) -> dict: + result: dict = {} + result["id"] = from_str(self.id) + result["modulePath"] = from_str(self.module_path) + result["name"] = from_str(self.name) + result["source"] = to_enum(ExtensionSource, self.source) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class Extension: @@ -11597,6 +13739,27 @@ def to_dict(self) -> dict: result["type"] = self.type return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ExtensionLaunchProviderResolveResult: + """The launch profile for a supported entrypoint. Omit launch when the provider does not + support the entrypoint. + """ + launch: ExtensionLaunchProfile | None = None + """Opaque launch profile, omitted when this provider does not support the entrypoint.""" + + @staticmethod + def from_dict(obj: Any) -> 'ExtensionLaunchProviderResolveResult': + assert isinstance(obj, dict) + launch = from_union([ExtensionLaunchProfile.from_dict, from_none], obj.get("launch")) + return ExtensionLaunchProviderResolveResult(launch) + + def to_dict(self) -> dict: + result: dict = {} + if self.launch is not None: + result["launch"] = from_union([lambda x: to_class(ExtensionLaunchProfile, x), from_none], self.launch) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ExternalToolTextResultForLlmBinaryResultsForLlm: @@ -11953,6 +14116,9 @@ def to_dict(self) -> dict: class FactoryAgentRequest: """Parameters for one factory-scoped subagent call.""" + execution_token: str + """Opaque token identifying the current factory execution attempt.""" + factory_run_id: str """Factory run identifier that owns the subagent.""" @@ -11965,18 +14131,84 @@ class FactoryAgentRequest: @staticmethod def from_dict(obj: Any) -> 'FactoryAgentRequest': assert isinstance(obj, dict) + execution_token = from_str(obj.get("executionToken")) factory_run_id = from_str(obj.get("factoryRunId")) opts = FactoryAgentOptions.from_dict(obj.get("opts")) prompt = from_str(obj.get("prompt")) - return FactoryAgentRequest(factory_run_id, opts, prompt) + return FactoryAgentRequest(execution_token, factory_run_id, opts, prompt) def to_dict(self) -> dict: result: dict = {} + result["executionToken"] = from_str(self.execution_token) result["factoryRunId"] = from_str(self.factory_run_id) result["opts"] = to_class(FactoryAgentOptions, self.opts) result["prompt"] = from_str(self.prompt) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryRunFailure: + """Machine-readable factory run failure. + + Machine-readable failure details for an errored run. + + The run stopped because its usage accounting could not be completed. + """ + run_id: str + """Factory run identifier. + + Factory run identifier whose changed limits were declined. + """ + type: FactoryRunFailureType + kind: FactoryRunFailureKind | None = None + """Resource ceiling that stopped the run.""" + + value: float | None = None + """Approved effective ceiling that was reached.""" + + reason: str | None = None + """Human-readable reason the resume did not proceed.""" + + code: str | None = None + """Stable failure code.""" + + operation: FactoryDurableOperation | None = None + """Execution-critical durable operation that failed.""" + + drained_nano_aiu: int | None = None + """Confirmed usage in nano-AIU, representing the floor of what the run spent.""" + + @staticmethod + def from_dict(obj: Any) -> 'FactoryRunFailure': + assert isinstance(obj, dict) + run_id = from_str(obj.get("runId")) + type = FactoryRunFailureType(obj.get("type")) + kind = from_union([FactoryRunFailureKind, from_none], obj.get("kind")) + value = from_union([from_float, from_none], obj.get("value")) + reason = from_union([from_str, from_none], obj.get("reason")) + code = from_union([from_str, from_none], obj.get("code")) + operation = from_union([FactoryDurableOperation, from_none], obj.get("operation")) + drained_nano_aiu = from_union([from_int, from_none], obj.get("drainedNanoAiu")) + return FactoryRunFailure(run_id, type, kind, value, reason, code, operation, drained_nano_aiu) + + def to_dict(self) -> dict: + result: dict = {} + result["runId"] = from_str(self.run_id) + result["type"] = to_enum(FactoryRunFailureType, self.type) + if self.kind is not None: + result["kind"] = from_union([lambda x: to_enum(FactoryRunFailureKind, x), from_none], self.kind) + if self.value is not None: + result["value"] = from_union([to_float, from_none], self.value) + if self.reason is not None: + result["reason"] = from_union([from_str, from_none], self.reason) + if self.code is not None: + result["code"] = from_union([from_str, from_none], self.code) + if self.operation is not None: + result["operation"] = from_union([lambda x: to_enum(FactoryDurableOperation, x), from_none], self.operation) + if self.drained_nano_aiu is not None: + result["drainedNanoAiu"] = from_union([from_int, from_none], self.drained_nano_aiu) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class FactoryLogLine: @@ -12008,46 +14240,128 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class FactoryRunFailure: - """Machine-readable factory run failure. +class FactoryProgressLine: + """One durable factory progress record.""" + + attempt: int + """Resume attempt that emitted this record.""" + + kind: FactoryLogLineKind + """Progress record kind.""" + + recorded_at: int + """Epoch milliseconds when the record was persisted.""" + + seq: int + """Global monotonic sequence number within the run.""" + + text: str + """Prompt-safe progress text.""" + + phase_id: str | None = None + """Phase active when the record was emitted, or null before any phase.""" + + @staticmethod + def from_dict(obj: Any) -> 'FactoryProgressLine': + assert isinstance(obj, dict) + attempt = from_int(obj.get("attempt")) + kind = FactoryLogLineKind(obj.get("kind")) + recorded_at = from_int(obj.get("recordedAt")) + seq = from_int(obj.get("seq")) + text = from_str(obj.get("text")) + phase_id = from_union([from_none, from_str], obj.get("phaseId")) + return FactoryProgressLine(attempt, kind, recorded_at, seq, text, phase_id) + + def to_dict(self) -> dict: + result: dict = {} + result["attempt"] = from_int(self.attempt) + result["kind"] = to_enum(FactoryLogLineKind, self.kind) + result["recordedAt"] = from_int(self.recorded_at) + result["seq"] = from_int(self.seq) + result["text"] = from_str(self.text) + result["phaseId"] = from_union([from_none, from_str], self.phase_id) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryPhaseObservation: + """Durable lifecycle and timing for one factory phase.""" + + accumulated_active_ms: int + current_active_ms: int + entry_count: int + id: str + last_entered_run_attempt: int + live_agent_count: int + status: FactoryPhaseStatus + title: str + total_agent_count: int + completed_at: int | None = None + detail: str | None = None + ordinal: int | None = None + started_at: int | None = None + + @staticmethod + def from_dict(obj: Any) -> 'FactoryPhaseObservation': + assert isinstance(obj, dict) + accumulated_active_ms = from_int(obj.get("accumulatedActiveMs")) + current_active_ms = from_int(obj.get("currentActiveMs")) + entry_count = from_int(obj.get("entryCount")) + id = from_str(obj.get("id")) + last_entered_run_attempt = from_int(obj.get("lastEnteredRunAttempt")) + live_agent_count = from_int(obj.get("liveAgentCount")) + status = FactoryPhaseStatus(obj.get("status")) + title = from_str(obj.get("title")) + total_agent_count = from_int(obj.get("totalAgentCount")) + completed_at = from_union([from_int, from_none], obj.get("completedAt")) + detail = from_union([from_str, from_none], obj.get("detail")) + ordinal = from_union([from_none, from_int], obj.get("ordinal")) + started_at = from_union([from_int, from_none], obj.get("startedAt")) + return FactoryPhaseObservation(accumulated_active_ms, current_active_ms, entry_count, id, last_entered_run_attempt, live_agent_count, status, title, total_agent_count, completed_at, detail, ordinal, started_at) + + def to_dict(self) -> dict: + result: dict = {} + result["accumulatedActiveMs"] = from_int(self.accumulated_active_ms) + result["currentActiveMs"] = from_int(self.current_active_ms) + result["entryCount"] = from_int(self.entry_count) + result["id"] = from_str(self.id) + result["lastEnteredRunAttempt"] = from_int(self.last_entered_run_attempt) + result["liveAgentCount"] = from_int(self.live_agent_count) + result["status"] = to_enum(FactoryPhaseStatus, self.status) + result["title"] = from_str(self.title) + result["totalAgentCount"] = from_int(self.total_agent_count) + if self.completed_at is not None: + result["completedAt"] = from_union([from_int, from_none], self.completed_at) + if self.detail is not None: + result["detail"] = from_union([from_str, from_none], self.detail) + result["ordinal"] = from_union([from_none, from_int], self.ordinal) + if self.started_at is not None: + result["startedAt"] = from_union([from_int, from_none], self.started_at) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryResumeRequest: + """Parameters for resuming a factory run from its persisted identity.""" - Machine-readable failure details for an errored run. - """ run_id: str - """Factory run identifier. - - Factory run identifier whose changed limits were declined. - """ - type: FactoryRunFailureType - kind: FactoryRunFailureKind | None = None - """Resource ceiling that stopped the run.""" - - value: float | None = None - """Approved effective ceiling that was reached.""" + """Factory run identifier.""" - reason: str | None = None - """Human-readable reason the resume did not proceed.""" + limits: FactoryRunLimits | None = None + """Optional per-invocation resource ceiling overrides.""" @staticmethod - def from_dict(obj: Any) -> 'FactoryRunFailure': + def from_dict(obj: Any) -> 'FactoryResumeRequest': assert isinstance(obj, dict) run_id = from_str(obj.get("runId")) - type = FactoryRunFailureType(obj.get("type")) - kind = from_union([FactoryRunFailureKind, from_none], obj.get("kind")) - value = from_union([from_float, from_none], obj.get("value")) - reason = from_union([from_str, from_none], obj.get("reason")) - return FactoryRunFailure(run_id, type, kind, value, reason) + limits = from_union([FactoryRunLimits.from_dict, from_none], obj.get("limits")) + return FactoryResumeRequest(run_id, limits) def to_dict(self) -> dict: result: dict = {} result["runId"] = from_str(self.run_id) - result["type"] = to_enum(FactoryRunFailureType, self.type) - if self.kind is not None: - result["kind"] = from_union([lambda x: to_enum(FactoryRunFailureKind, x), from_none], self.kind) - if self.value is not None: - result["value"] = from_union([to_float, from_none], self.value) - if self.reason is not None: - result["reason"] = from_union([from_str, from_none], self.reason) + if self.limits is not None: + result["limits"] = from_union([lambda x: to_class(FactoryRunLimits, x), from_none], self.limits) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -12122,6 +14436,127 @@ def to_dict(self) -> dict: result["summaryContent"] = from_union([from_str, from_none], self.summary_content) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class HistorySkippedFileRestore: + """A captured file that rewind intentionally left unchanged.""" + + path: str + """Absolute path of the skipped file.""" + + reason: HistoryFileRestoreSkipReason + """Reason the file was not restored.""" + + @staticmethod + def from_dict(obj: Any) -> 'HistorySkippedFileRestore': + assert isinstance(obj, dict) + path = from_str(obj.get("path")) + reason = HistoryFileRestoreSkipReason(obj.get("reason")) + return HistorySkippedFileRestore(path, reason) + + def to_dict(self) -> dict: + result: dict = {} + result["path"] = from_str(self.path) + result["reason"] = to_enum(HistoryFileRestoreSkipReason, self.reason) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class HistoryListRewindPointsResult: + """Rewind points and file-change-tracking availability for the session.""" + + file_change_tracking_enabled: bool + """Whether this session captured file changes from its first turn.""" + + points: list[HistoryRewindPoint] + """Root user turns in chronological order. Empty when `unavailableReason` is set.""" + + unavailable_reason: HistoryRewindUnavailableReason | None = None + """Why the listed points could not be produced, when applicable; the points list is empty + whenever it is set. `unsupported-remote-session` is permanent for the session and comes + with `fileChangeTrackingEnabled: false`. `session-busy` is transient and only ever + reported by a session that *is* tracking (`fileChangeTrackingEnabled: true`), because the + file-change captures cannot be read while work that may still mutate them is in flight; + the same request succeeds once the session settles, so a client that wants points should + retry rather than treat it as a failure. It is never `file-change-tracking-disabled`: an + untracked local session still lists conversation-only points and reports that through + `fileChangeTrackingEnabled: false`. + """ + + @staticmethod + def from_dict(obj: Any) -> 'HistoryListRewindPointsResult': + assert isinstance(obj, dict) + file_change_tracking_enabled = from_bool(obj.get("fileChangeTrackingEnabled")) + points = from_list(HistoryRewindPoint.from_dict, obj.get("points")) + unavailable_reason = from_union([HistoryRewindUnavailableReason, from_none], obj.get("unavailableReason")) + return HistoryListRewindPointsResult(file_change_tracking_enabled, points, unavailable_reason) + + def to_dict(self) -> dict: + result: dict = {} + result["fileChangeTrackingEnabled"] = from_bool(self.file_change_tracking_enabled) + result["points"] = from_list(lambda x: to_class(HistoryRewindPoint, x), self.points) + if self.unavailable_reason is not None: + result["unavailableReason"] = from_union([lambda x: to_enum(HistoryRewindUnavailableReason, x), from_none], self.unavailable_reason) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class HistoryRewindFilePreview: + """A file that a conversation-and-files rewind would restore.""" + + change_type: HistoryRewindChangeType + """Aggregate change made across the discarded turns.""" + + lines_added: int + """Lines added across the discarded turns.""" + + lines_removed: int + """Lines removed across the discarded turns.""" + + path: str + """Absolute path of the captured file.""" + + @staticmethod + def from_dict(obj: Any) -> 'HistoryRewindFilePreview': + assert isinstance(obj, dict) + change_type = HistoryRewindChangeType(obj.get("changeType")) + lines_added = from_int(obj.get("linesAdded")) + lines_removed = from_int(obj.get("linesRemoved")) + path = from_str(obj.get("path")) + return HistoryRewindFilePreview(change_type, lines_added, lines_removed, path) + + def to_dict(self) -> dict: + result: dict = {} + result["changeType"] = to_enum(HistoryRewindChangeType, self.change_type) + result["linesAdded"] = from_int(self.lines_added) + result["linesRemoved"] = from_int(self.lines_removed) + result["path"] = from_str(self.path) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class HistoryRewindRequest: + """Boundary and mode for rewinding session history.""" + + event_id: str + """ID of the user.message event that begins the discarded suffix.""" + + mode: HistoryRewindMode + """Whether to rewind only conversation history or also restore captured files.""" + + @staticmethod + def from_dict(obj: Any) -> 'HistoryRewindRequest': + assert isinstance(obj, dict) + event_id = from_str(obj.get("eventId")) + mode = HistoryRewindMode(obj.get("mode")) + return HistoryRewindRequest(event_id, mode) + + def to_dict(self) -> dict: + result: dict = {} + result["eventId"] = from_str(self.event_id) + result["mode"] = to_enum(HistoryRewindMode, self.mode) + return result + # Internal: this type is an internal SDK API and is not part of the public surface. @dataclass class _HookInvokeRequest: @@ -12149,11 +14584,11 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class InstalledPluginSource: - """Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref, - and optional subpath. + """Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref or + full commit SHA, and optional subpath. - Source descriptor for a direct URL plugin install, with URL, optional ref, and optional - subpath. + Source descriptor for a direct URL plugin install, with URL, optional ref or full commit + SHA, and optional subpath. Source descriptor for a direct local plugin install, with a local filesystem path. """ @@ -12167,6 +14602,9 @@ class InstalledPluginSource: path: str | None = None ref: str | None = None repo: str | None = None + sha: str | None = None + """Optional full 40-character hexadecimal commit SHA.""" + url: str | None = None @staticmethod @@ -12176,8 +14614,9 @@ def from_dict(obj: Any) -> 'InstalledPluginSource': path = from_union([from_str, from_none], obj.get("path")) ref = from_union([from_str, from_none], obj.get("ref")) repo = from_union([from_str, from_none], obj.get("repo")) + sha = from_union([from_str, from_none], obj.get("sha")) url = from_union([from_str, from_none], obj.get("url")) - return InstalledPluginSource(source, path, ref, repo, url) + return InstalledPluginSource(source, path, ref, repo, sha, url) def to_dict(self) -> dict: result: dict = {} @@ -12188,6 +14627,8 @@ def to_dict(self) -> dict: result["ref"] = from_union([from_str, from_none], self.ref) if self.repo is not None: result["repo"] = from_union([from_str, from_none], self.repo) + if self.sha is not None: + result["sha"] = from_union([from_str, from_none], self.sha) if self.url is not None: result["url"] = from_union([from_str, from_none], self.url) return result @@ -12195,11 +14636,11 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionInstalledPluginSource: - """Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref, - and optional subpath. + """Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref or + full commit SHA, and optional subpath. - Source descriptor for a direct URL plugin install, with URL, optional ref, and optional - subpath. + Source descriptor for a direct URL plugin install, with URL, optional ref or full commit + SHA, and optional subpath. Source descriptor for a direct local plugin install, with a local filesystem path. """ @@ -12213,6 +14654,9 @@ class SessionInstalledPluginSource: path: str | None = None ref: str | None = None repo: str | None = None + sha: str | None = None + """Optional full 40-character hexadecimal commit SHA.""" + url: str | None = None @staticmethod @@ -12222,8 +14666,9 @@ def from_dict(obj: Any) -> 'SessionInstalledPluginSource': path = from_union([from_str, from_none], obj.get("path")) ref = from_union([from_str, from_none], obj.get("ref")) repo = from_union([from_str, from_none], obj.get("repo")) + sha = from_union([from_str, from_none], obj.get("sha")) url = from_union([from_str, from_none], obj.get("url")) - return SessionInstalledPluginSource(source, path, ref, repo, url) + return SessionInstalledPluginSource(source, path, ref, repo, sha, url) def to_dict(self) -> dict: result: dict = {} @@ -12234,6 +14679,8 @@ def to_dict(self) -> dict: result["ref"] = from_union([from_str, from_none], self.ref) if self.repo is not None: result["repo"] = from_union([from_str, from_none], self.repo) + if self.sha is not None: + result["sha"] = from_union([from_str, from_none], self.sha) if self.url is not None: result["url"] = from_union([from_str, from_none], self.url) return result @@ -12241,8 +14688,8 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class InstalledPluginSourceGitHub: - """Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref, - and optional subpath. + """Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref or + full commit SHA, and optional subpath. """ repo: str source: FluffySource @@ -12250,6 +14697,8 @@ class InstalledPluginSourceGitHub: path: str | None = None ref: str | None = None + sha: str | None = None + """Optional full 40-character hexadecimal commit SHA.""" @staticmethod def from_dict(obj: Any) -> 'InstalledPluginSourceGitHub': @@ -12258,7 +14707,8 @@ def from_dict(obj: Any) -> 'InstalledPluginSourceGitHub': source = FluffySource(obj.get("source")) path = from_union([from_str, from_none], obj.get("path")) ref = from_union([from_str, from_none], obj.get("ref")) - return InstalledPluginSourceGitHub(repo, source, path, ref) + sha = from_union([from_str, from_none], obj.get("sha")) + return InstalledPluginSourceGitHub(repo, source, path, ref, sha) def to_dict(self) -> dict: result: dict = {} @@ -12268,13 +14718,15 @@ def to_dict(self) -> dict: result["path"] = from_union([from_str, from_none], self.path) if self.ref is not None: result["ref"] = from_union([from_str, from_none], self.ref) + if self.sha is not None: + result["sha"] = from_union([from_str, from_none], self.sha) return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionInstalledPluginSourceGitHub: - """Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref, - and optional subpath. + """Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref or + full commit SHA, and optional subpath. """ repo: str source: FluffySource @@ -12282,6 +14734,8 @@ class SessionInstalledPluginSourceGitHub: path: str | None = None ref: str | None = None + sha: str | None = None + """Optional full 40-character hexadecimal commit SHA.""" @staticmethod def from_dict(obj: Any) -> 'SessionInstalledPluginSourceGitHub': @@ -12290,7 +14744,8 @@ def from_dict(obj: Any) -> 'SessionInstalledPluginSourceGitHub': source = FluffySource(obj.get("source")) path = from_union([from_str, from_none], obj.get("path")) ref = from_union([from_str, from_none], obj.get("ref")) - return SessionInstalledPluginSourceGitHub(repo, source, path, ref) + sha = from_union([from_str, from_none], obj.get("sha")) + return SessionInstalledPluginSourceGitHub(repo, source, path, ref, sha) def to_dict(self) -> dict: result: dict = {} @@ -12300,6 +14755,8 @@ def to_dict(self) -> dict: result["path"] = from_union([from_str, from_none], self.path) if self.ref is not None: result["ref"] = from_union([from_str, from_none], self.ref) + if self.sha is not None: + result["sha"] = from_union([from_str, from_none], self.sha) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -12349,8 +14806,8 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class InstalledPluginSourceURL: - """Source descriptor for a direct URL plugin install, with URL, optional ref, and optional - subpath. + """Source descriptor for a direct URL plugin install, with URL, optional ref or full commit + SHA, and optional subpath. """ source: StickySource """Constant value. Always "url".""" @@ -12358,6 +14815,8 @@ class InstalledPluginSourceURL: url: str path: str | None = None ref: str | None = None + sha: str | None = None + """Optional full 40-character hexadecimal commit SHA.""" @staticmethod def from_dict(obj: Any) -> 'InstalledPluginSourceURL': @@ -12366,7 +14825,8 @@ def from_dict(obj: Any) -> 'InstalledPluginSourceURL': url = from_str(obj.get("url")) path = from_union([from_str, from_none], obj.get("path")) ref = from_union([from_str, from_none], obj.get("ref")) - return InstalledPluginSourceURL(source, url, path, ref) + sha = from_union([from_str, from_none], obj.get("sha")) + return InstalledPluginSourceURL(source, url, path, ref, sha) def to_dict(self) -> dict: result: dict = {} @@ -12376,13 +14836,15 @@ def to_dict(self) -> dict: result["path"] = from_union([from_str, from_none], self.path) if self.ref is not None: result["ref"] = from_union([from_str, from_none], self.ref) + if self.sha is not None: + result["sha"] = from_union([from_str, from_none], self.sha) return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionInstalledPluginSourceURL: - """Source descriptor for a direct URL plugin install, with URL, optional ref, and optional - subpath. + """Source descriptor for a direct URL plugin install, with URL, optional ref or full commit + SHA, and optional subpath. """ source: StickySource """Constant value. Always "url".""" @@ -12390,6 +14852,8 @@ class SessionInstalledPluginSourceURL: url: str path: str | None = None ref: str | None = None + sha: str | None = None + """Optional full 40-character hexadecimal commit SHA.""" @staticmethod def from_dict(obj: Any) -> 'SessionInstalledPluginSourceURL': @@ -12398,7 +14862,8 @@ def from_dict(obj: Any) -> 'SessionInstalledPluginSourceURL': url = from_str(obj.get("url")) path = from_union([from_str, from_none], obj.get("path")) ref = from_union([from_str, from_none], obj.get("ref")) - return SessionInstalledPluginSourceURL(source, url, path, ref) + sha = from_union([from_str, from_none], obj.get("sha")) + return SessionInstalledPluginSourceURL(source, url, path, ref, sha) def to_dict(self) -> dict: result: dict = {} @@ -12408,6 +14873,8 @@ def to_dict(self) -> dict: result["path"] = from_union([from_str, from_none], self.path) if self.ref is not None: result["ref"] = from_union([from_str, from_none], self.ref) + if self.sha is not None: + result["sha"] = from_union([from_str, from_none], self.sha) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -12446,9 +14913,8 @@ class InstructionSource: project_path: str | None = None """The project path this source was discovered from. Only set by sessionless discovery for - repository/working-directory sources, where it disambiguates same-named files (e.g. - .github/copilot-instructions.md) across multiple workspace roots. The session-scoped - getSources leaves it unset. + repository, working-directory, and project-scoped plugin sources, where it disambiguates + sources across multiple workspace roots. The session-scoped getSources leaves it unset. """ @staticmethod @@ -13085,6 +15551,10 @@ class MCPServerConfigStdio: """Controls if tools provided by this server can be loaded on demand via tool search (auto) or always included in the initial tool list (never) """ + disable_tool_cache: bool | None = None + """Set to true to disable persisted MCP tool snapshots for this server. Live tool discovery + is unaffected. + """ env: dict[str, str] | None = None """Environment variables to pass to the Stdio MCP server process.""" @@ -13113,13 +15583,14 @@ def from_dict(obj: Any) -> 'MCPServerConfigStdio': auth = from_union([from_bool, MCPServerAuthConfigRedirectPort.from_dict, from_none], obj.get("auth")) cwd = from_union([from_str, from_none], obj.get("cwd")) defer_tools = from_union([MCPServerConfigDeferTools, from_none], obj.get("deferTools")) + disable_tool_cache = from_union([from_bool, from_none], obj.get("disableToolCache")) env = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("env")) filter_mapping = from_union([lambda x: from_dict(ContentFilterMode, x), ContentFilterMode, from_none], obj.get("filterMapping")) is_default_server = from_union([from_bool, from_none], obj.get("isDefaultServer")) oidc = from_union([from_bool, MCPServerAuthConfigRedirectPort.from_dict, from_none], obj.get("oidc")) timeout = from_union([from_int, from_none], obj.get("timeout")) tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("tools")) - return MCPServerConfigStdio(command, args, auth, cwd, defer_tools, env, filter_mapping, is_default_server, oidc, timeout, tools) + return MCPServerConfigStdio(command, args, auth, cwd, defer_tools, disable_tool_cache, env, filter_mapping, is_default_server, oidc, timeout, tools) def to_dict(self) -> dict: result: dict = {} @@ -13132,6 +15603,8 @@ def to_dict(self) -> dict: result["cwd"] = from_union([from_str, from_none], self.cwd) if self.defer_tools is not None: result["deferTools"] = from_union([lambda x: to_enum(MCPServerConfigDeferTools, x), from_none], self.defer_tools) + if self.disable_tool_cache is not None: + result["disableToolCache"] = from_union([from_bool, from_none], self.disable_tool_cache) if self.env is not None: result["env"] = from_union([lambda x: from_dict(from_str, x), from_none], self.env) if self.filter_mapping is not None: @@ -13231,6 +15704,9 @@ class MCPServerConfig: Replacement MCP server configuration (stdio process or remote HTTP/SSE). Omit to restart the server with its already-registered configuration (config-free restart-by-name). + MCP server configuration (stdio process or remote HTTP/SSE). Omit to start the server + with its already-registered configuration (config-free start-by-name). + Stdio MCP server configuration launched as a child process. Remote MCP server configuration accessed over HTTP or SSE. @@ -13251,6 +15727,10 @@ class MCPServerConfig: """Controls if tools provided by this server can be loaded on demand via tool search (auto) or always included in the initial tool list (never) """ + disable_tool_cache: bool | None = None + """Set to true to disable persisted MCP tool snapshots for this server. Live tool discovery + is unaffected. + """ env: dict[str, str] | None = None """Environment variables to pass to the Stdio MCP server process.""" @@ -13297,6 +15777,7 @@ def from_dict(obj: Any) -> 'MCPServerConfig': command = from_union([from_str, from_none], obj.get("command")) cwd = from_union([from_str, from_none], obj.get("cwd")) defer_tools = from_union([MCPServerConfigDeferTools, from_none], obj.get("deferTools")) + disable_tool_cache = from_union([from_bool, from_none], obj.get("disableToolCache")) env = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("env")) filter_mapping = from_union([lambda x: from_dict(ContentFilterMode, x), ContentFilterMode, from_none], obj.get("filterMapping")) is_default_server = from_union([from_bool, from_none], obj.get("isDefaultServer")) @@ -13309,7 +15790,7 @@ def from_dict(obj: Any) -> 'MCPServerConfig': oauth_public_client = from_union([from_bool, from_none], obj.get("oauthPublicClient")) type = from_union([MCPServerConfigHTTPType, from_none], obj.get("type")) url = from_union([from_str, from_none], obj.get("url")) - return MCPServerConfig(args, auth, command, cwd, defer_tools, env, filter_mapping, is_default_server, oidc, timeout, tools, headers, oauth_client_id, oauth_grant_type, oauth_public_client, type, url) + return MCPServerConfig(args, auth, command, cwd, defer_tools, disable_tool_cache, env, filter_mapping, is_default_server, oidc, timeout, tools, headers, oauth_client_id, oauth_grant_type, oauth_public_client, type, url) def to_dict(self) -> dict: result: dict = {} @@ -13323,6 +15804,8 @@ def to_dict(self) -> dict: result["cwd"] = from_union([from_str, from_none], self.cwd) if self.defer_tools is not None: result["deferTools"] = from_union([lambda x: to_enum(MCPServerConfigDeferTools, x), from_none], self.defer_tools) + if self.disable_tool_cache is not None: + result["disableToolCache"] = from_union([from_bool, from_none], self.disable_tool_cache) if self.env is not None: result["env"] = from_union([lambda x: from_dict(from_str, x), from_none], self.env) if self.filter_mapping is not None: @@ -13364,6 +15847,10 @@ class MCPServerConfigHTTP: """Controls if tools provided by this server can be loaded on demand via tool search (auto) or always included in the initial tool list (never) """ + disable_tool_cache: bool | None = None + """Set to true to disable persisted MCP tool snapshots for this server. Live tool discovery + is unaffected. + """ filter_mapping: dict[str, ContentFilterMode] | ContentFilterMode | None = None """Content filtering mode to apply to all tools, or a map of tool name to content filtering mode. @@ -13402,6 +15889,7 @@ def from_dict(obj: Any) -> 'MCPServerConfigHTTP': url = from_str(obj.get("url")) auth = from_union([from_bool, MCPServerAuthConfigRedirectPort.from_dict, from_none], obj.get("auth")) defer_tools = from_union([MCPServerConfigDeferTools, from_none], obj.get("deferTools")) + disable_tool_cache = from_union([from_bool, from_none], obj.get("disableToolCache")) filter_mapping = from_union([lambda x: from_dict(ContentFilterMode, x), ContentFilterMode, from_none], obj.get("filterMapping")) headers = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("headers")) is_default_server = from_union([from_bool, from_none], obj.get("isDefaultServer")) @@ -13412,7 +15900,7 @@ def from_dict(obj: Any) -> 'MCPServerConfigHTTP': timeout = from_union([from_int, from_none], obj.get("timeout")) tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("tools")) type = from_union([MCPServerConfigHTTPType, from_none], obj.get("type")) - return MCPServerConfigHTTP(url, auth, defer_tools, filter_mapping, headers, is_default_server, oauth_client_id, oauth_grant_type, oauth_public_client, oidc, timeout, tools, type) + return MCPServerConfigHTTP(url, auth, defer_tools, disable_tool_cache, filter_mapping, headers, is_default_server, oauth_client_id, oauth_grant_type, oauth_public_client, oidc, timeout, tools, type) def to_dict(self) -> dict: result: dict = {} @@ -13421,6 +15909,8 @@ def to_dict(self) -> dict: result["auth"] = from_union([from_bool, lambda x: to_class(MCPServerAuthConfigRedirectPort, x), from_none], self.auth) if self.defer_tools is not None: result["deferTools"] = from_union([lambda x: to_enum(MCPServerConfigDeferTools, x), from_none], self.defer_tools) + if self.disable_tool_cache is not None: + result["disableToolCache"] = from_union([from_bool, from_none], self.disable_tool_cache) if self.filter_mapping is not None: result["filterMapping"] = from_union([lambda x: from_dict(lambda x: to_enum(ContentFilterMode, x), x), lambda x: to_enum(ContentFilterMode, x), from_none], self.filter_mapping) if self.headers is not None: @@ -13508,7 +15998,7 @@ class MCPHostState: """Map of server name to recorded connection failure.""" filtered_servers: list[str] - """Configured servers filtered out by enterprise allowlist policy.""" + """Configured servers filtered out by MCP server policy.""" mcp3_p_enabled: bool """Whether third-party MCP servers are policy-enabled for this session.""" @@ -13694,13 +16184,47 @@ class SessionContextAttribution: """Per-source token attribution snapshot for the current context window. The heaviest individual messages are available separately via `metadata.getContextHeaviestMessages`. """ + buffer_tokens: int + """Output reserve plus the tokens past the buffer-exhaustion blocking threshold. Mirrors + `SessionContextInfo.bufferTokens`. + """ + categories: Categories + """The six normalized `/context` header buckets, computed from the same tokenization as + `entries` so the two never disagree. Convenience rollups: `freeSpace` and `buffer` + describe window capacity rather than occupied context, so the values do not sum to + `totalTokens`. + """ compactions: Compactions """Successful compaction history for the session.""" + compaction_threshold: int + """Token count at which background compaction starts. Mirrors + `SessionContextInfo.compactionThreshold`. + """ entries: list[Entry] """Flat list of per-source attribution entries. Group by `kind` and render unrecognized kinds generically. Nesting and rollups are expressed via `parentId`. """ + limit: int + """Prompt limit plus the model's output reserve: the full context window + `categories.freeSpace` and `categories.buffer` are measured against. Mirrors + `SessionContextInfo.limit`. + """ + model_id: str + """The concrete model id the entire breakdown was tokenized against (feeds the per-model + token multiplier). Under `Auto` (Free/Student) this is the resolved model, not the + literal `auto` sentinel, so totals are not undercounted. A single-model approximation of + a potentially multi-model Auto session. + """ + model_source: str + """How `modelId` was chosen. Not a closed set — tolerate unknown values. Known values today: + `autoResolved` (the model Auto resolved to), `selected` (the user's explicitly selected + model), `default` (a fallback before any model is known). + """ + prompt_token_limit: int + """Maximum prompt tokens the resolved model accepts — the denominator for a `##k/###k` + context-usage display. Mirrors `SessionContextInfo.promptTokenLimit`. + """ total_tokens: int """Total token count of the current context window the entries are measured against (system message + conversation messages + tool definitions — the same total reported by @@ -13710,15 +16234,29 @@ class SessionContextAttribution: @staticmethod def from_dict(obj: Any) -> 'SessionContextAttribution': assert isinstance(obj, dict) + buffer_tokens = from_int(obj.get("bufferTokens")) + categories = Categories.from_dict(obj.get("categories")) compactions = Compactions.from_dict(obj.get("compactions")) + compaction_threshold = from_int(obj.get("compactionThreshold")) entries = from_list(Entry.from_dict, obj.get("entries")) + limit = from_int(obj.get("limit")) + model_id = from_str(obj.get("modelId")) + model_source = from_str(obj.get("modelSource")) + prompt_token_limit = from_int(obj.get("promptTokenLimit")) total_tokens = from_int(obj.get("totalTokens")) - return SessionContextAttribution(compactions, entries, total_tokens) + return SessionContextAttribution(buffer_tokens, categories, compactions, compaction_threshold, entries, limit, model_id, model_source, prompt_token_limit, total_tokens) def to_dict(self) -> dict: result: dict = {} + result["bufferTokens"] = from_int(self.buffer_tokens) + result["categories"] = to_class(Categories, self.categories) result["compactions"] = to_class(Compactions, self.compactions) + result["compactionThreshold"] = from_int(self.compaction_threshold) result["entries"] = from_list(lambda x: to_class(Entry, x), self.entries) + result["limit"] = from_int(self.limit) + result["modelId"] = from_str(self.model_id) + result["modelSource"] = from_str(self.model_source) + result["promptTokenLimit"] = from_int(self.prompt_token_limit) result["totalTokens"] = from_int(self.total_tokens) return result @@ -14473,6 +17011,84 @@ def to_dict(self) -> dict: result["operation"] = from_union([from_str, from_none], self.operation) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class PermissionDecisionApproveForLocationApprovalFactory: + """Location-scoped factory approval, optionally narrowed by approval key.""" + + kind: ClassVar[str] = "factory" + """Approval covering factory operations.""" + + approval_key: str | None = None + """Optional factory operation name or canonical approval key; when omitted, the approval + covers all factory operations. + """ + + @staticmethod + def from_dict(obj: Any) -> 'PermissionDecisionApproveForLocationApprovalFactory': + assert isinstance(obj, dict) + approval_key = from_union([from_str, from_none], obj.get("approvalKey")) + return PermissionDecisionApproveForLocationApprovalFactory(approval_key) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = self.kind + if self.approval_key is not None: + result["approvalKey"] = from_union([from_str, from_none], self.approval_key) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class PermissionDecisionApproveForSessionApprovalFactory: + """Session-scoped factory approval, optionally narrowed by approval key.""" + + kind: ClassVar[str] = "factory" + """Approval covering factory operations.""" + + approval_key: str | None = None + """Optional factory operation name or canonical approval key; when omitted, the approval + covers all factory operations. + """ + + @staticmethod + def from_dict(obj: Any) -> 'PermissionDecisionApproveForSessionApprovalFactory': + assert isinstance(obj, dict) + approval_key = from_union([from_str, from_none], obj.get("approvalKey")) + return PermissionDecisionApproveForSessionApprovalFactory(approval_key) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = self.kind + if self.approval_key is not None: + result["approvalKey"] = from_union([from_str, from_none], self.approval_key) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class PermissionsLocationsAddToolApprovalDetailsFactory: + """Location-persisted factory approval, optionally narrowed by approval key.""" + + kind: ClassVar[str] = "factory" + """Approval covering factory operations.""" + + approval_key: str | None = None + """Optional factory operation name or canonical approval key; when omitted, the approval + covers all factory operations. + """ + + @staticmethod + def from_dict(obj: Any) -> 'PermissionsLocationsAddToolApprovalDetailsFactory': + assert isinstance(obj, dict) + approval_key = from_union([from_str, from_none], obj.get("approvalKey")) + return PermissionsLocationsAddToolApprovalDetailsFactory(approval_key) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = self.kind + if self.approval_key is not None: + result["approvalKey"] = from_union([from_str, from_none], self.approval_key) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionApproveForLocationApprovalMCP: @@ -14830,14 +17446,20 @@ class PermissionDecisionApproveOnce: kind: ClassVar[str] = "approve-once" """Approve this single request only""" + approved_interactively: bool | None = None + """True only when a host surfaced this request to a user who approved it.""" + @staticmethod def from_dict(obj: Any) -> 'PermissionDecisionApproveOnce': assert isinstance(obj, dict) - return PermissionDecisionApproveOnce() + approved_interactively = from_union([from_bool, from_none], obj.get("approvedInteractively")) + return PermissionDecisionApproveOnce(approved_interactively) def to_dict(self) -> dict: result: dict = {} result["kind"] = self.kind + if self.approved_interactively is not None: + result["approvedInteractively"] = from_union([from_bool, from_none], self.approved_interactively) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -14959,6 +17581,39 @@ def to_dict(self) -> dict: result["reason"] = from_union([from_str, from_none], self.reason) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class PermissionDecisionContext: + """Optional informational context describing how and where the permission decision was made. + This does not affect permission behavior. + + Optional informational context describing how and where this response was made. Omit it + to preserve legacy behavior without attributing an origin. + """ + outcome: PermissionDecisionOutcome + """Disposition of the permission request as observed by the responding client.""" + + source: PermissionDecisionSource + """Controlled reason or actor responsible for the response.""" + + surface: PermissionDecisionSurface + """Client surface that submitted the response.""" + + @staticmethod + def from_dict(obj: Any) -> 'PermissionDecisionContext': + assert isinstance(obj, dict) + outcome = PermissionDecisionOutcome(obj.get("outcome")) + source = PermissionDecisionSource(obj.get("source")) + surface = PermissionDecisionSurface(obj.get("surface")) + return PermissionDecisionContext(outcome, source, surface) + + def to_dict(self) -> dict: + result: dict = {} + result["outcome"] = to_enum(PermissionDecisionOutcome, self.outcome) + result["source"] = to_enum(PermissionDecisionSource, self.source) + result["surface"] = to_enum(PermissionDecisionSurface, self.surface) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionDecisionDeniedByContentExclusionPolicy: @@ -15325,6 +17980,30 @@ def to_dict(self) -> dict: result["rows"] = from_list(lambda x: to_class(PlanSQLTodosRow, x), self.rows) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class AgentSetPromptRequest: + """An in-memory authored prompt override for an available agent.""" + + id: str + """Stable effective agent id. Plugin namespace separators are normalized.""" + + prompt: str + """Replacement authored prompt. Empty text is valid.""" + + @staticmethod + def from_dict(obj: Any) -> 'AgentSetPromptRequest': + assert isinstance(obj, dict) + id = from_str(obj.get("id")) + prompt = from_str(obj.get("prompt")) + return AgentSetPromptRequest(id, prompt) + + def to_dict(self) -> dict: + result: dict = {} + result["id"] = from_str(self.id) + result["prompt"] = from_str(self.prompt) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class DiscoveredMCPServer: @@ -15453,8 +18132,9 @@ class MCPServer: """Server name (config key)""" status: McpServerStatus - """Connection status: connected, failed, needs-auth, pending, disabled, or not_configured""" - + """Connection status: connected, failed, needs-auth, pending, disabled, stopped, or + not_configured + """ error: str | None = None """Error message if the server failed to connect""" @@ -15708,85 +18388,37 @@ class ProviderEndpoint: """Short-lived, rotating credential the caller must send on every request, in addition to `apiKey` if one is present. Omitted when the endpoint does not require one. """ - transport: ProviderTransport | None = None - """Transport to be used for provider requests.""" - - wire_api: ProviderWireAPI | None = None - """Wire API to be used, when required for the provider type.""" - - @staticmethod - def from_dict(obj: Any) -> 'ProviderEndpoint': - assert isinstance(obj, dict) - base_url = from_str(obj.get("baseUrl")) - headers = from_dict(from_str, obj.get("headers")) - type = ProviderType(obj.get("type")) - api_key = from_union([from_str, from_none], obj.get("apiKey")) - session_token = from_union([ProviderSessionToken.from_dict, from_none], obj.get("sessionToken")) - transport = from_union([ProviderTransport, from_none], obj.get("transport")) - wire_api = from_union([ProviderWireAPI, from_none], obj.get("wireApi")) - return ProviderEndpoint(base_url, headers, type, api_key, session_token, transport, wire_api) - - def to_dict(self) -> dict: - result: dict = {} - result["baseUrl"] = from_str(self.base_url) - result["headers"] = from_dict(from_str, self.headers) - result["type"] = to_enum(ProviderType, self.type) - if self.api_key is not None: - result["apiKey"] = from_union([from_str, from_none], self.api_key) - if self.session_token is not None: - result["sessionToken"] = from_union([lambda x: to_class(ProviderSessionToken, x), from_none], self.session_token) - if self.transport is not None: - result["transport"] = from_union([lambda x: to_enum(ProviderTransport, x), from_none], self.transport) - if self.wire_api is not None: - result["wireApi"] = from_union([lambda x: to_enum(ProviderWireAPI, x), from_none], self.wire_api) - return result - -@dataclass -class PushAttachmentGitHubSide: - """File location on the base side of the diff. Absent for additions. - - One side of a file diff (head or base) - - File location on the head side of the diff. Absent for deletions. - - Base side of the comparison - - One side of a tree comparison (head or base) - - Head side of the comparison - """ - repo: PushGitHubRepoRef - """Repository the file lives in - - Repository the revision belongs to - """ - path: str | None = None - """Repository-relative path to the file""" - - ref: str | None = None - """Git ref (branch, tag, or commit SHA) the file is read at""" + transport: ProviderTransport | None = None + """Transport to be used for provider requests.""" - revision: str | None = None - """Git revision (branch, tag, or commit SHA)""" + wire_api: ProviderWireAPI | None = None + """Wire API to be used, when required for the provider type.""" @staticmethod - def from_dict(obj: Any) -> 'PushAttachmentGitHubSide': + def from_dict(obj: Any) -> 'ProviderEndpoint': assert isinstance(obj, dict) - repo = PushGitHubRepoRef.from_dict(obj.get("repo")) - path = from_union([from_str, from_none], obj.get("path")) - ref = from_union([from_str, from_none], obj.get("ref")) - revision = from_union([from_str, from_none], obj.get("revision")) - return PushAttachmentGitHubSide(repo, path, ref, revision) + base_url = from_str(obj.get("baseUrl")) + headers = from_dict(from_str, obj.get("headers")) + type = ProviderType(obj.get("type")) + api_key = from_union([from_str, from_none], obj.get("apiKey")) + session_token = from_union([ProviderSessionToken.from_dict, from_none], obj.get("sessionToken")) + transport = from_union([ProviderTransport, from_none], obj.get("transport")) + wire_api = from_union([ProviderWireAPI, from_none], obj.get("wireApi")) + return ProviderEndpoint(base_url, headers, type, api_key, session_token, transport, wire_api) def to_dict(self) -> dict: result: dict = {} - result["repo"] = to_class(PushGitHubRepoRef, self.repo) - if self.path is not None: - result["path"] = from_union([from_str, from_none], self.path) - if self.ref is not None: - result["ref"] = from_union([from_str, from_none], self.ref) - if self.revision is not None: - result["revision"] = from_union([from_str, from_none], self.revision) + result["baseUrl"] = from_str(self.base_url) + result["headers"] = from_dict(from_str, self.headers) + result["type"] = to_enum(ProviderType, self.type) + if self.api_key is not None: + result["apiKey"] = from_union([from_str, from_none], self.api_key) + if self.session_token is not None: + result["sessionToken"] = from_union([lambda x: to_class(ProviderSessionToken, x), from_none], self.session_token) + if self.transport is not None: + result["transport"] = from_union([lambda x: to_enum(ProviderTransport, x), from_none], self.transport) + if self.wire_api is not None: + result["wireApi"] = from_union([lambda x: to_enum(ProviderWireAPI, x), from_none], self.wire_api) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -16258,28 +18890,243 @@ def to_dict(self) -> dict: result["url"] = from_str(self.url) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class QueueInsertMessage: + """Serializable message fields accepted by queue.insertAt.""" + + prompt: str + """The user message text.""" + + agent_mode: SendAgentMode | None = None + """Optional explicit agent mode. When omitted, the session's current mode is assigned.""" + + attachments: list[Attachment] | None = None + """Optional attachments for the message.""" + + billable: bool | None = None + """Whether the message is billable.""" + + delivery: str | None = None + """Accepted for internal SendOptions compatibility but ignored; delivery is derived from + current session activity. + """ + display_prompt: str | None = None + """Optional user-facing display text.""" + + mode: SendMode | None = None + """Accepted for SendOptions compatibility but ignored; inserted items always use queued + delivery semantics. + """ + prepend: bool | None = None + """Accepted for SendOptions compatibility but ignored; the requested public position + controls placement. + """ + request_headers: dict[str, str] | None = None + """Per-turn request headers.""" + + required_tool: str | None = None + """Required tool name for the turn, when any.""" + + source: str | None = None + """Optional provenance source. `system` is rejected: it would hide the inserted row from + `pendingItems` and make it unaddressable while still executing, so inserted items must + stay visible. + """ + wait: bool | None = None + """Accepted for SendOptions compatibility but ignored; insertion scheduling is controlled by + the queue drain state. + """ + + @staticmethod + def from_dict(obj: Any) -> 'QueueInsertMessage': + assert isinstance(obj, dict) + prompt = from_str(obj.get("prompt")) + agent_mode = from_union([SendAgentMode, from_none], obj.get("agentMode")) + attachments = from_union([lambda x: from_list(Attachment.from_dict, x), from_none], obj.get("attachments")) + billable = from_union([from_bool, from_none], obj.get("billable")) + delivery = from_union([from_str, from_none], obj.get("delivery")) + display_prompt = from_union([from_str, from_none], obj.get("displayPrompt")) + mode = from_union([SendMode, from_none], obj.get("mode")) + prepend = from_union([from_bool, from_none], obj.get("prepend")) + request_headers = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("requestHeaders")) + required_tool = from_union([from_str, from_none], obj.get("requiredTool")) + source = from_union([from_str, from_none], obj.get("source")) + wait = from_union([from_bool, from_none], obj.get("wait")) + return QueueInsertMessage(prompt, agent_mode, attachments, billable, delivery, display_prompt, mode, prepend, request_headers, required_tool, source, wait) + + def to_dict(self) -> dict: + result: dict = {} + result["prompt"] = from_str(self.prompt) + if self.agent_mode is not None: + result["agentMode"] = from_union([lambda x: to_enum(SendAgentMode, x), from_none], self.agent_mode) + if self.attachments is not None: + result["attachments"] = from_union([lambda x: from_list(lambda x: to_class(Attachment, x), x), from_none], self.attachments) + if self.billable is not None: + result["billable"] = from_union([from_bool, from_none], self.billable) + if self.delivery is not None: + result["delivery"] = from_union([from_str, from_none], self.delivery) + if self.display_prompt is not None: + result["displayPrompt"] = from_union([from_str, from_none], self.display_prompt) + if self.mode is not None: + result["mode"] = from_union([lambda x: to_enum(SendMode, x), from_none], self.mode) + if self.prepend is not None: + result["prepend"] = from_union([from_bool, from_none], self.prepend) + if self.request_headers is not None: + result["requestHeaders"] = from_union([lambda x: from_dict(from_str, x), from_none], self.request_headers) + if self.required_tool is not None: + result["requiredTool"] = from_union([from_str, from_none], self.required_tool) + if self.source is not None: + result["source"] = from_union([from_str, from_none], self.source) + if self.wait is not None: + result["wait"] = from_union([from_bool, from_none], self.wait) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SendRequest: + """Parameters for sending a user message to the session""" + + prompt: str + """The user message text""" + + agent_mode: SendAgentMode | None = None + """The UI mode the agent was in when this message was sent. Defaults to the session's + current mode. + """ + attachments: list[Attachment] | None = None + """Optional attachments (files, directories, selections, blobs, GitHub references) to + include with the message + """ + billable: bool | None = None + """If false, this message will not trigger a Premium Request Unit charge. User messages + default to billable. + """ + display_prompt: str | None = None + """If provided, this is shown in the timeline instead of `prompt`""" + + mode: SendMode | None = None + """How to deliver the message. `enqueue` (default) appends to the message queue. `immediate` + interjects during an in-progress turn. + """ + prepend: bool | None = None + """If true, adds the message to the front of the queue instead of the end""" + + request_headers: dict[str, str] | None = None + """Custom HTTP headers to include in outbound model requests for this turn. Merged with + session-level provider headers; per-turn headers augment and overwrite session-level + headers with the same key. + """ + required_tool: str | None = None + """If set, the request will fail if the named tool is not available when this message is + among the user messages at the start of the current exchange + """ + # Internal: this field is an internal SDK API and is not part of the public surface. + source: str | None = None + """Optional provenance tag copied to the resulting user.message event. Must be `user`, + `system`, `command-` for command-originated messages, `schedule-` + for scheduled prompts, or `agent-` for prompts sent by another agent. + """ + traceparent: str | None = None + """W3C Trace Context traceparent header for distributed tracing of this agent turn""" + + tracestate: str | None = None + """W3C Trace Context tracestate header for distributed tracing""" + + wait: bool | None = None + """If true, await completion of the agentic loop for this message before returning. Defaults + to false (fire-and-forget). When true, the result still contains the same `messageId`; + the caller can rely on the agent having processed the message before the call resolves. + Transport-dependent tail semantics: on a LOCAL (in-process) session the wait additionally + blocks until the completed turn's event tail has been dispatched to this session's + in-process subscribers, so a subsequent read of subscriber state already reflects the + turn; on a REMOTE session the wait resolves once the loop completes and mirrored delivery + follows over the wire. Callers that need the stronger local guarantee on remote sessions + should await the event stream explicitly. + """ + + @staticmethod + def from_dict(obj: Any) -> 'SendRequest': + assert isinstance(obj, dict) + prompt = from_str(obj.get("prompt")) + agent_mode = from_union([SendAgentMode, from_none], obj.get("agentMode")) + attachments = from_union([lambda x: from_list(Attachment.from_dict, x), from_none], obj.get("attachments")) + billable = from_union([from_bool, from_none], obj.get("billable")) + display_prompt = from_union([from_str, from_none], obj.get("displayPrompt")) + mode = from_union([SendMode, from_none], obj.get("mode")) + prepend = from_union([from_bool, from_none], obj.get("prepend")) + request_headers = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("requestHeaders")) + required_tool = from_union([from_str, from_none], obj.get("requiredTool")) + source = from_union([from_str, from_none], obj.get("source")) + traceparent = from_union([from_str, from_none], obj.get("traceparent")) + tracestate = from_union([from_str, from_none], obj.get("tracestate")) + wait = from_union([from_bool, from_none], obj.get("wait")) + return SendRequest(prompt, agent_mode, attachments, billable, display_prompt, mode, prepend, request_headers, required_tool, source, traceparent, tracestate, wait) + + def to_dict(self) -> dict: + result: dict = {} + result["prompt"] = from_str(self.prompt) + if self.agent_mode is not None: + result["agentMode"] = from_union([lambda x: to_enum(SendAgentMode, x), from_none], self.agent_mode) + if self.attachments is not None: + result["attachments"] = from_union([lambda x: from_list(lambda x: to_class(Attachment, x), x), from_none], self.attachments) + if self.billable is not None: + result["billable"] = from_union([from_bool, from_none], self.billable) + if self.display_prompt is not None: + result["displayPrompt"] = from_union([from_str, from_none], self.display_prompt) + if self.mode is not None: + result["mode"] = from_union([lambda x: to_enum(SendMode, x), from_none], self.mode) + if self.prepend is not None: + result["prepend"] = from_union([from_bool, from_none], self.prepend) + if self.request_headers is not None: + result["requestHeaders"] = from_union([lambda x: from_dict(from_str, x), from_none], self.request_headers) + if self.required_tool is not None: + result["requiredTool"] = from_union([from_str, from_none], self.required_tool) + if self.source is not None: + result["source"] = from_union([from_str, from_none], self.source) + if self.traceparent is not None: + result["traceparent"] = from_union([from_str, from_none], self.traceparent) + if self.tracestate is not None: + result["tracestate"] = from_union([from_str, from_none], self.tracestate) + if self.wait is not None: + result["wait"] = from_union([from_bool, from_none], self.wait) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class QueuePendingItems: """User-facing pending queue entry, with kind and display text for a queued message, slash command, or model change. """ + agent_mode: SendAgentMode + """Agent mode stored on this queued entry, as stamped when it was enqueued. Items without an + explicit mode report interactive. This is not necessarily the mode that will constrain + the turn: a plan or autopilot session applies its own write gate, continuation loop and + permission posture to every drained item regardless of the mode stored here. + """ display_text: str """Human-readable text to display for this queue entry in the UI""" + id: str + """Stable opaque id for the canonical queued item. Batch rows share one id.""" + kind: QueuePendingItemsKind """Whether this item is a queued user message or a queued slash command / model change""" @staticmethod def from_dict(obj: Any) -> 'QueuePendingItems': assert isinstance(obj, dict) + agent_mode = SendAgentMode(obj.get("agentMode")) display_text = from_str(obj.get("displayText")) + id = from_str(obj.get("id")) kind = QueuePendingItemsKind(obj.get("kind")) - return QueuePendingItems(display_text, kind) + return QueuePendingItems(agent_mode, display_text, id, kind) def to_dict(self) -> dict: result: dict = {} + result["agentMode"] = to_enum(SendAgentMode, self.agent_mode) result["displayText"] = from_str(self.display_text) + result["id"] = from_str(self.id) result["kind"] = to_enum(QueuePendingItemsKind, self.kind) return result @@ -16507,31 +19354,94 @@ def from_dict(obj: Any) -> 'RemoteEnableRequest': def to_dict(self) -> dict: result: dict = {} - if self.mode is not None: - result["mode"] = from_union([lambda x: to_enum(RemoteSessionMode, x), from_none], self.mode) + if self.mode is not None: + result["mode"] = from_union([lambda x: to_enum(RemoteSessionMode, x), from_none], self.mode) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SandboxConfigUserPolicyExperimental: + """Deprecated legacy location for `seatbelt`; read only when the top-level `seatbelt` is + absent. + + Platform-specific experimental policy fields. + """ + seatbelt: SandboxConfigUserPolicyExperimentalSeatbelt | None = None + """macOS seatbelt experimental options.""" + + @staticmethod + def from_dict(obj: Any) -> 'SandboxConfigUserPolicyExperimental': + assert isinstance(obj, dict) + seatbelt = from_union([SandboxConfigUserPolicyExperimentalSeatbelt.from_dict, from_none], obj.get("seatbelt")) + return SandboxConfigUserPolicyExperimental(seatbelt) + + def to_dict(self) -> dict: + result: dict = {} + if self.seatbelt is not None: + result["seatbelt"] = from_union([lambda x: to_class(SandboxConfigUserPolicyExperimentalSeatbelt, x), from_none], self.seatbelt) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SandboxConfigUserPolicyNetwork: + """Network rules to merge into the base policy.""" + + allow_local_network: bool | None = None + """Whether traffic to local/loopback addresses is allowed.""" + + allow_outbound: bool | None = None + """Whether outbound network traffic is allowed at all.""" + + proxy: SandboxConfigUserPolicyNetworkProxy | None = None + """HTTP proxy the sandboxed process routes traffic through. Enforced on Windows and + cooperative (honored by well-behaved tools, not strictly enforced) on Linux and macOS. + Credentials go in the separate `username`/`password` fields. A credential-free http:// + loopback proxy URL is routed through the localhost proxy automatically; an https:// or + authenticated loopback URL is used as-is. + """ + + @staticmethod + def from_dict(obj: Any) -> 'SandboxConfigUserPolicyNetwork': + assert isinstance(obj, dict) + allow_local_network = from_union([from_bool, from_none], obj.get("allowLocalNetwork")) + allow_outbound = from_union([from_bool, from_none], obj.get("allowOutbound")) + proxy = from_union([SandboxConfigUserPolicyNetworkProxy.from_dict, from_none], obj.get("proxy")) + return SandboxConfigUserPolicyNetwork(allow_local_network, allow_outbound, proxy) + + def to_dict(self) -> dict: + result: dict = {} + if self.allow_local_network is not None: + result["allowLocalNetwork"] = from_union([from_bool, from_none], self.allow_local_network) + if self.allow_outbound is not None: + result["allowOutbound"] = from_union([from_bool, from_none], self.allow_outbound) + if self.proxy is not None: + result["proxy"] = from_union([lambda x: to_class(SandboxConfigUserPolicyNetworkProxy, x), from_none], self.proxy) return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SandboxConfigUserPolicyExperimental: - """Deprecated legacy location for `seatbelt`; read only when the top-level `seatbelt` is - absent. +class ScheduleAddResult: + """Result of registering or re-arming a scheduled prompt.""" - Platform-specific experimental policy fields. - """ - seatbelt: SandboxConfigUserPolicyExperimentalSeatbelt | None = None - """macOS seatbelt experimental options.""" + entry: ScheduleEntry | None = None + """The registered or updated schedule entry.""" + + error: str | None = None + """User-facing validation error, when registration failed.""" @staticmethod - def from_dict(obj: Any) -> 'SandboxConfigUserPolicyExperimental': + def from_dict(obj: Any) -> 'ScheduleAddResult': assert isinstance(obj, dict) - seatbelt = from_union([SandboxConfigUserPolicyExperimentalSeatbelt.from_dict, from_none], obj.get("seatbelt")) - return SandboxConfigUserPolicyExperimental(seatbelt) + entry = from_union([ScheduleEntry.from_dict, from_none], obj.get("entry")) + error = from_union([from_str, from_none], obj.get("error")) + return ScheduleAddResult(entry, error) def to_dict(self) -> dict: result: dict = {} - if self.seatbelt is not None: - result["seatbelt"] = from_union([lambda x: to_class(SandboxConfigUserPolicyExperimentalSeatbelt, x), from_none], self.seatbelt) + if self.entry is not None: + result["entry"] = from_union([lambda x: to_class(ScheduleEntry, x), from_none], self.entry) + if self.error is not None: + result["error"] = from_union([from_str, from_none], self.error) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -16610,6 +19520,12 @@ class SendMessagesRequest: """If true, await completion of the agentic loop for this turn before returning. Defaults to false (fire-and-forget). When true, the result still contains the same `messageIds`; the caller can rely on the agent having processed the messages before the call resolves. + Transport-dependent tail semantics: on a LOCAL (in-process) session the wait additionally + blocks until the completed turn's event tail has been dispatched to this session's + in-process subscribers, so a subsequent read of subscriber state already reflects the + turn; on a REMOTE session the wait resolves once the loop completes and mirrored delivery + follows over the wire. Callers that need the stronger local guarantee on remote sessions + should await the event stream explicitly. """ @staticmethod @@ -16644,111 +19560,6 @@ def to_dict(self) -> dict: result["wait"] = from_union([from_bool, from_none], self.wait) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class SendRequest: - """Parameters for sending a user message to the session""" - - prompt: str - """The user message text""" - - agent_mode: SendAgentMode | None = None - """The UI mode the agent was in when this message was sent. Defaults to the session's - current mode. - """ - attachments: list[Attachment] | None = None - """Optional attachments (files, directories, selections, blobs, GitHub references) to - include with the message - """ - billable: bool | None = None - """If false, this message will not trigger a Premium Request Unit charge. User messages - default to billable. - """ - display_prompt: str | None = None - """If provided, this is shown in the timeline instead of `prompt`""" - - mode: SendMode | None = None - """How to deliver the message. `enqueue` (default) appends to the message queue. `immediate` - interjects during an in-progress turn. - """ - prepend: bool | None = None - """If true, adds the message to the front of the queue instead of the end""" - - request_headers: dict[str, str] | None = None - """Custom HTTP headers to include in outbound model requests for this turn. Merged with - session-level provider headers; per-turn headers augment and overwrite session-level - headers with the same key. - """ - required_tool: str | None = None - """If set, the request will fail if the named tool is not available when this message is - among the user messages at the start of the current exchange - """ - # Internal: this field is an internal SDK API and is not part of the public surface. - source: str | None = None - """Optional provenance tag copied to the resulting user.message event. Must match one of - three forms: the literal `system`, `command-` for messages originating from a - command (e.g. slash command, Mission Control command), or `schedule-` for - messages originating from a scheduled job. - """ - traceparent: str | None = None - """W3C Trace Context traceparent header for distributed tracing of this agent turn""" - - tracestate: str | None = None - """W3C Trace Context tracestate header for distributed tracing""" - - wait: bool | None = None - """If true, await completion of the agentic loop for this message before returning. Defaults - to false (fire-and-forget). When true, the result still contains the same `messageId`; - the caller can rely on the agent having processed the message before the call resolves. - """ - - @staticmethod - def from_dict(obj: Any) -> 'SendRequest': - assert isinstance(obj, dict) - prompt = from_str(obj.get("prompt")) - agent_mode = from_union([SendAgentMode, from_none], obj.get("agentMode")) - attachments = from_union([lambda x: from_list(Attachment.from_dict, x), from_none], obj.get("attachments")) - billable = from_union([from_bool, from_none], obj.get("billable")) - display_prompt = from_union([from_str, from_none], obj.get("displayPrompt")) - mode = from_union([SendMode, from_none], obj.get("mode")) - prepend = from_union([from_bool, from_none], obj.get("prepend")) - request_headers = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("requestHeaders")) - required_tool = from_union([from_str, from_none], obj.get("requiredTool")) - source = from_union([from_str, from_none], obj.get("source")) - traceparent = from_union([from_str, from_none], obj.get("traceparent")) - tracestate = from_union([from_str, from_none], obj.get("tracestate")) - wait = from_union([from_bool, from_none], obj.get("wait")) - return SendRequest(prompt, agent_mode, attachments, billable, display_prompt, mode, prepend, request_headers, required_tool, source, traceparent, tracestate, wait) - - def to_dict(self) -> dict: - result: dict = {} - result["prompt"] = from_str(self.prompt) - if self.agent_mode is not None: - result["agentMode"] = from_union([lambda x: to_enum(SendAgentMode, x), from_none], self.agent_mode) - if self.attachments is not None: - result["attachments"] = from_union([lambda x: from_list(lambda x: to_class(Attachment, x), x), from_none], self.attachments) - if self.billable is not None: - result["billable"] = from_union([from_bool, from_none], self.billable) - if self.display_prompt is not None: - result["displayPrompt"] = from_union([from_str, from_none], self.display_prompt) - if self.mode is not None: - result["mode"] = from_union([lambda x: to_enum(SendMode, x), from_none], self.mode) - if self.prepend is not None: - result["prepend"] = from_union([from_bool, from_none], self.prepend) - if self.request_headers is not None: - result["requestHeaders"] = from_union([lambda x: from_dict(from_str, x), from_none], self.request_headers) - if self.required_tool is not None: - result["requiredTool"] = from_union([from_str, from_none], self.required_tool) - if self.source is not None: - result["source"] = from_union([from_str, from_none], self.source) - if self.traceparent is not None: - result["traceparent"] = from_union([from_str, from_none], self.traceparent) - if self.tracestate is not None: - result["tracestate"] = from_union([from_str, from_none], self.tracestate) - if self.wait is not None: - result["wait"] = from_union([from_bool, from_none], self.wait) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ServerSkillList: @@ -16839,39 +19650,200 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SessionFSSqliteQueryRequest: - """SQL query, query type, and optional bind parameters for executing a SQLite query against - the per-session database. - """ - query: str - """SQL query to execute""" +class SessionFSSqliteQueryRequest: + """SQL query, query type, and optional bind parameters for executing a SQLite query against + the per-session database. The provider applies its SQLite busy timeout for every call. + """ + query: str + """SQL query to execute""" + + query_type: SessionFSSqliteQueryType + """How to execute the query: 'exec' for DDL/multi-statement (no results), 'query' for SELECT + (returns rows), 'run' for INSERT/UPDATE/DELETE (returns rowsAffected) + """ + session_id: str + """Target session identifier""" + + params: dict[str, Any] | None = None + """Optional named bind parameters""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionFSSqliteQueryRequest': + assert isinstance(obj, dict) + query = from_str(obj.get("query")) + query_type = SessionFSSqliteQueryType(obj.get("queryType")) + session_id = from_str(obj.get("sessionId")) + params = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("params")) + return SessionFSSqliteQueryRequest(query, query_type, session_id, params) + + def to_dict(self) -> dict: + result: dict = {} + result["query"] = from_str(self.query) + result["queryType"] = to_enum(SessionFSSqliteQueryType, self.query_type) + result["sessionId"] = from_str(self.session_id) + if self.params is not None: + result["params"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.params) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionFSSqliteTransactionStatement: + """One statement in an atomic SQLite transaction.""" + + query: str + """SQL statement to execute.""" + + query_type: SessionFSSqliteQueryType + """How to execute the statement.""" + + params: dict[str, Any] | None = None + """Optional named bind parameters.""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionFSSqliteTransactionStatement': + assert isinstance(obj, dict) + query = from_str(obj.get("query")) + query_type = SessionFSSqliteQueryType(obj.get("queryType")) + params = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("params")) + return SessionFSSqliteTransactionStatement(query, query_type, params) + + def to_dict(self) -> dict: + result: dict = {} + result["query"] = from_str(self.query) + result["queryType"] = to_enum(SessionFSSqliteQueryType, self.query_type) + if self.params is not None: + result["params"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.params) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionFSSqliteTransactionError: + """Classified SQLite transaction failure. busyOrLocked guarantees rollback; + postCommitAmbiguous must never be retried. + """ + error_class: SessionFSSqliteTransactionErrorClass + message: str + + @staticmethod + def from_dict(obj: Any) -> 'SessionFSSqliteTransactionError': + assert isinstance(obj, dict) + error_class = SessionFSSqliteTransactionErrorClass(obj.get("errorClass")) + message = from_str(obj.get("message")) + return SessionFSSqliteTransactionError(error_class, message) + + def to_dict(self) -> dict: + result: dict = {} + result["errorClass"] = to_enum(SessionFSSqliteTransactionErrorClass, self.error_class) + result["message"] = from_str(self.message) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class CompletionsGetTriggerCharactersResult: + """Characters that, when typed in the composer, should trigger a `completions.request`. + Empty when the session has no host-driven completions (e.g. local sessions, or a relay + host that does not advertise `completionTriggerCharacters`). + """ + trigger_characters: list[str] + """Trigger characters advertised by the host (e.g. `["@", "#"]`). Empty disables host-driven + completions for the session. + """ + + @staticmethod + def from_dict(obj: Any) -> 'CompletionsGetTriggerCharactersResult': + assert isinstance(obj, dict) + trigger_characters = from_list(from_str, obj.get("triggerCharacters")) + return CompletionsGetTriggerCharactersResult(trigger_characters) + + def to_dict(self) -> dict: + result: dict = {} + result["triggerCharacters"] = from_list(from_str, self.trigger_characters) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionHistoryCompactRequest: + custom_instructions: str | None = None + """Optional user-provided instructions to focus the compaction summary""" + + token_limit: int | None = None + """Context window token limit this compaction is targeting, recorded as the `tokenLimit` on + the persisted `session.compaction_start` / `session.compaction_complete` events. Set it + when the compaction targets a window other than the compacting model's own, e.g. + switching to a model with a smaller context window: the compaction still runs on the + current model, so the limit that motivated it would otherwise be lost. When absent, the + events record the compacting model's own resolved limit. Attribution metadata only - it + does not change how much the compaction removes. + """ + trigger: Trigger | None = None + """What initiated this compaction request, recorded as the `trigger` on the persisted + `session.compaction_start` / `session.compaction_complete` events. When absent, the + compaction is persisted without trigger attribution (initiator unknown). + """ + + @staticmethod + def from_dict(obj: Any) -> 'SessionHistoryCompactRequest': + assert isinstance(obj, dict) + custom_instructions = from_union([from_str, from_none], obj.get("customInstructions")) + token_limit = from_union([from_int, from_none], obj.get("tokenLimit")) + trigger = from_union([Trigger, from_none], obj.get("trigger")) + return SessionHistoryCompactRequest(custom_instructions, token_limit, trigger) + + def to_dict(self) -> dict: + result: dict = {} + if self.custom_instructions is not None: + result["customInstructions"] = from_union([from_str, from_none], self.custom_instructions) + if self.token_limit is not None: + result["tokenLimit"] = from_union([from_int, from_none], self.token_limit) + if self.trigger is not None: + result["trigger"] = from_union([lambda x: to_enum(Trigger, x), from_none], self.trigger) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionLimitPredictionPredictRequest: + client_type: SessionLimitPredictionClientType | None = None + """Client type to size for. Defaults to `cli-interactive`.""" + + model_id: str | None = None + """Optional model identifier override. If omitted, the session's current model is used.""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionLimitPredictionPredictRequest': + assert isinstance(obj, dict) + client_type = from_union([SessionLimitPredictionClientType, from_none], obj.get("clientType")) + model_id = from_union([from_str, from_none], obj.get("modelId")) + return SessionLimitPredictionPredictRequest(client_type, model_id) + + def to_dict(self) -> dict: + result: dict = {} + if self.client_type is not None: + result["clientType"] = from_union([lambda x: to_enum(SessionLimitPredictionClientType, x), from_none], self.client_type) + if self.model_id is not None: + result["modelId"] = from_union([from_str, from_none], self.model_id) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionLimitPredictionTierOption: + """Semantic usage tier and its AI-credit cap.""" - query_type: SessionFSSqliteQueryType - """How to execute the query: 'exec' for DDL/multi-statement (no results), 'query' for SELECT - (returns rows), 'run' for INSERT/UPDATE/DELETE (returns rowsAffected) - """ - session_id: str - """Target session identifier""" + cap: float + """AI-credit cap for this tier.""" - params: dict[str, Any] | None = None - """Optional named bind parameters""" + tier: SessionLimitPredictionTier @staticmethod - def from_dict(obj: Any) -> 'SessionFSSqliteQueryRequest': + def from_dict(obj: Any) -> 'SessionLimitPredictionTierOption': assert isinstance(obj, dict) - query = from_str(obj.get("query")) - query_type = SessionFSSqliteQueryType(obj.get("queryType")) - session_id = from_str(obj.get("sessionId")) - params = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("params")) - return SessionFSSqliteQueryRequest(query, query_type, session_id, params) + cap = from_float(obj.get("cap")) + tier = SessionLimitPredictionTier(obj.get("tier")) + return SessionLimitPredictionTierOption(cap, tier) def to_dict(self) -> dict: result: dict = {} - result["query"] = from_str(self.query) - result["queryType"] = to_enum(SessionFSSqliteQueryType, self.query_type) - result["sessionId"] = from_str(self.session_id) - if self.params is not None: - result["params"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.params) + result["cap"] = to_float(self.cap) + result["tier"] = to_enum(SessionLimitPredictionTier, self.tier) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -16906,6 +19878,31 @@ def to_dict(self) -> dict: result["ifNoneMatch"] = from_union([lambda x: from_list(from_str, x), from_none], self.if_none_match) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ShellInitScript: + """A host-provided script sourced before each built-in shell command when its shell target + matches the active shell. + """ + path: str + """Path to the script to source.""" + + shell: ShellInitScriptShell + """Built-in shell that may source this script.""" + + @staticmethod + def from_dict(obj: Any) -> 'ShellInitScript': + assert isinstance(obj, dict) + path = from_str(obj.get("path")) + shell = ShellInitScriptShell(obj.get("shell")) + return ShellInitScript(path, shell) + + def to_dict(self) -> dict: + result: dict = {} + result["path"] = from_str(self.path) + result["shell"] = to_enum(ShellInitScriptShell, self.shell) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionsOpenProgress: @@ -17161,7 +20158,7 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class AgentInfo: - """Custom agent metadata, including identifiers, display details, source, tools, model, MCP + """Agent metadata, including identifiers, display details, source, tools, model, MCP servers, skills, and file path. The newly selected custom agent @@ -17178,19 +20175,24 @@ class AgentInfo: distinct id was assigned. """ name: str - """Unique identifier of the custom agent""" + """Name of the agent. Use `id` as the stable selection identifier.""" mcp_servers: dict[str, Any] | None = None """MCP server configurations attached to this agent, keyed by server name. Server config shape mirrors the MCP `mcpServers` schema. """ model: str | None = None - """Preferred model id for this agent. When omitted, inherits the outer agent's model.""" - + """Authored preferred model id for this agent. Runtime model selection may choose a + different model; omitted means no authored preference. + """ path: str | None = None """Absolute local file path of the agent definition. Only set for file-based agents loaded from disk; remote agents do not have a path. """ + prompt: str | None = None + """Authored base prompt for the agent. Runtime prompt assembly may add dynamic context at + invocation time. Omitted from `session.agent.list` unless `includePrompt` is true. + """ skills: list[str] | None = None """Skill names preloaded into this agent's context. Omitted means none.""" @@ -17215,11 +20217,12 @@ def from_dict(obj: Any) -> 'AgentInfo': mcp_servers = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("mcpServers")) model = from_union([from_str, from_none], obj.get("model")) path = from_union([from_str, from_none], obj.get("path")) + prompt = from_union([from_str, from_none], obj.get("prompt")) skills = from_union([lambda x: from_list(from_str, x), from_none], obj.get("skills")) source = from_union([AgentInfoSource, from_none], obj.get("source")) tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("tools")) user_invocable = from_union([from_bool, from_none], obj.get("userInvocable")) - return AgentInfo(description, display_name, id, name, mcp_servers, model, path, skills, source, tools, user_invocable) + return AgentInfo(description, display_name, id, name, mcp_servers, model, path, prompt, skills, source, tools, user_invocable) def to_dict(self) -> dict: result: dict = {} @@ -17233,6 +20236,8 @@ def to_dict(self) -> dict: result["model"] = from_union([from_str, from_none], self.model) if self.path is not None: result["path"] = from_union([from_str, from_none], self.path) + if self.prompt is not None: + result["prompt"] = from_union([from_str, from_none], self.prompt) if self.skills is not None: result["skills"] = from_union([lambda x: from_list(from_str, x), from_none], self.skills) if self.source is not None: @@ -18339,6 +21344,12 @@ class UIExitPlanModeResponse: auto_approve_edits: bool | None = None """Whether subsequent edits should be auto-approved without confirmation.""" + defer_implementation: bool | None = None + """When true, the agent is instructed to end its turn without starting implementation so the + client can restore the session model and auto-submit a fresh implementation turn on it. + Set only when a distinct plan configuration (a different model, reasoning effort, or + context tier) actually ran the planning turn. + """ feedback: str | None = None """Feedback from the user when they declined the plan or requested changes.""" @@ -18352,15 +21363,18 @@ def from_dict(obj: Any) -> 'UIExitPlanModeResponse': assert isinstance(obj, dict) approved = from_bool(obj.get("approved")) auto_approve_edits = from_union([from_bool, from_none], obj.get("autoApproveEdits")) + defer_implementation = from_union([from_bool, from_none], obj.get("deferImplementation")) feedback = from_union([from_str, from_none], obj.get("feedback")) selected_action = from_union([UIExitPlanModeAction, from_none], obj.get("selectedAction")) - return UIExitPlanModeResponse(approved, auto_approve_edits, feedback, selected_action) + return UIExitPlanModeResponse(approved, auto_approve_edits, defer_implementation, feedback, selected_action) def to_dict(self) -> dict: result: dict = {} result["approved"] = from_bool(self.approved) if self.auto_approve_edits is not None: result["autoApproveEdits"] = from_union([from_bool, from_none], self.auto_approve_edits) + if self.defer_implementation is not None: + result["deferImplementation"] = from_union([from_bool, from_none], self.defer_implementation) if self.feedback is not None: result["feedback"] = from_union([from_str, from_none], self.feedback) if self.selected_action is not None: @@ -18505,8 +21519,10 @@ class WorkspaceDiffFileChange: """Unified diff content for the file. Empty when the diff was truncated.""" path: str - """Path to the changed file, relative to the workspace root.""" - + """Path to the changed file, relative to the workspace root when the file lives under it. A + file changed outside the workspace root keeps a `../`-relative path, or an absolute path + when no relative path exists (for example a different Windows drive). + """ is_truncated: bool | None = None """Whether the diff content was omitted because it exceeded the per-file size limit.""" @@ -18812,142 +21828,69 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class ExtensionList: - """Extensions discovered for the session, with their current status.""" +class SessionManagedSettings: + """Managed settings an SDK host may inject at session startup. Only permissions are accepted + in this initial contract. - extensions: list[Extension] - """Discovered extensions and their current status""" + Permissions-only enterprise policy injected by the SDK host at session create or resume. + Composes restrictively with self-fetched and device policy and is not persisted. + """ + permissions: SessionManagedPermissions | None = None @staticmethod - def from_dict(obj: Any) -> 'ExtensionList': + def from_dict(obj: Any) -> 'SessionManagedSettings': assert isinstance(obj, dict) - extensions = from_list(Extension.from_dict, obj.get("extensions")) - return ExtensionList(extensions) + permissions = from_union([SessionManagedPermissions.from_dict, from_none], obj.get("permissions")) + return SessionManagedSettings(permissions) def to_dict(self) -> dict: result: dict = {} - result["extensions"] = from_list(lambda x: to_class(Extension, x), self.extensions) + if self.permissions is not None: + result["permissions"] = from_union([lambda x: to_class(SessionManagedPermissions, x), from_none], self.permissions) return result +# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class PermissionDecisionApproveForIonApproval: - """Session-scoped approval to remember (tool prompts only; omitted for path/url prompts) - - Session-scoped approval details for specific command identifiers. - - Session-scoped approval details for read-only filesystem operations. - - Session-scoped approval details for filesystem write operations. - - Session-scoped approval details for an MCP server tool, or all tools on the server when - `toolName` is null. - - Session-scoped approval details for MCP sampling requests from a server. - - Session-scoped approval details for writes to long-term memory. - - Session-scoped approval details for a custom tool, keyed by tool name. - - Session-scoped approval details for extension-management operations, optionally narrowed - by operation. - - Session-scoped approval details for an extension's permission-gated capability access, - keyed by extension name. - - Approval to persist for this location - - Location-scoped approval details for specific command identifiers. - - Location-scoped approval details for read-only filesystem operations. - - Location-scoped approval details for filesystem write operations. - - Location-scoped approval details for an MCP server tool, or all tools on the server when - `toolName` is null. - - Location-scoped approval details for MCP sampling requests from a server. - - Location-scoped approval details for writes to long-term memory. - - Location-scoped approval details for a custom tool, keyed by tool name. - - Location-scoped approval details for extension-management operations, optionally narrowed - by operation. - - Location-scoped approval details for an extension's permission-gated capability access, - keyed by extension name. - - The approval to add as a session-scoped rule - - The approval to persist for this location +class DiscoveredExtensions: + """Extensions discovered from persisted Copilot home state and their effective loading mode. + Launch-scoped additional plugins are not included. """ - command_identifiers: list[str] | None = None - """Command identifiers covered by this approval.""" - - kind: ApprovalKind | None = None - """Approval scoped to specific command identifiers. - - Approval covering read-only filesystem operations. - - Approval covering filesystem write operations. - - Approval covering an MCP tool. + extensions: list[DiscoveredExtension] + """Discovered user and enabled installed-plugin extensions from persisted Copilot home state""" - Approval covering MCP sampling requests for a server. + mode: DiscoveredExtensionMode + """Effective extension loading mode. Defaults to load_and_augment when unset.""" - Approval covering writes to long-term memory. - - Approval covering a custom tool. - - Approval covering extension lifecycle operations such as enable, disable, or reload. - - Approval covering an extension's request to access a permission-gated capability. - """ - server_name: str | None = None - """MCP server name.""" + @staticmethod + def from_dict(obj: Any) -> 'DiscoveredExtensions': + assert isinstance(obj, dict) + extensions = from_list(DiscoveredExtension.from_dict, obj.get("extensions")) + mode = DiscoveredExtensionMode(obj.get("mode")) + return DiscoveredExtensions(extensions, mode) - tool_name: str | None = None - """MCP tool name, or null to cover every tool on the server. + def to_dict(self) -> dict: + result: dict = {} + result["extensions"] = from_list(lambda x: to_class(DiscoveredExtension, x), self.extensions) + result["mode"] = to_enum(DiscoveredExtensionMode, self.mode) + return result - Custom tool name. - """ - operation: str | None = None - """Optional operation identifier; when omitted, the approval covers all extension management - operations. - """ - extension_name: str | None = None - """Extension name.""" +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ExtensionList: + """Extensions discovered for the session, with their current status.""" - external_ref_marker_external_ref_user_tool_session_approval: str | None = None + extensions: list[Extension] + """Discovered extensions and their current status""" @staticmethod - def from_dict(obj: Any) -> 'PermissionDecisionApproveForIonApproval': + def from_dict(obj: Any) -> 'ExtensionList': assert isinstance(obj, dict) - command_identifiers = from_union([lambda x: from_list(from_str, x), from_none], obj.get("commandIdentifiers")) - kind = from_union([ApprovalKind, from_none], obj.get("kind")) - server_name = from_union([from_str, from_none], obj.get("serverName")) - tool_name = from_union([from_none, from_str], obj.get("toolName")) - operation = from_union([from_str, from_none], obj.get("operation")) - extension_name = from_union([from_str, from_none], obj.get("extensionName")) - external_ref_marker_external_ref_user_tool_session_approval = from_union([from_str, from_none], obj.get("__externalRefMarker___ExternalRef_UserToolSessionApproval")) - return PermissionDecisionApproveForIonApproval(command_identifiers, kind, server_name, tool_name, operation, extension_name, external_ref_marker_external_ref_user_tool_session_approval) + extensions = from_list(Extension.from_dict, obj.get("extensions")) + return ExtensionList(extensions) def to_dict(self) -> dict: result: dict = {} - if self.command_identifiers is not None: - result["commandIdentifiers"] = from_union([lambda x: from_list(from_str, x), from_none], self.command_identifiers) - if self.kind is not None: - result["kind"] = from_union([lambda x: to_enum(ApprovalKind, x), from_none], self.kind) - if self.server_name is not None: - result["serverName"] = from_union([from_str, from_none], self.server_name) - if self.tool_name is not None: - result["toolName"] = from_union([from_none, from_str], self.tool_name) - if self.operation is not None: - result["operation"] = from_union([from_str, from_none], self.operation) - if self.extension_name is not None: - result["extensionName"] = from_union([from_str, from_none], self.extension_name) - if self.external_ref_marker_external_ref_user_tool_session_approval is not None: - result["__externalRefMarker___ExternalRef_UserToolSessionApproval"] = from_union([from_str, from_none], self.external_ref_marker_external_ref_user_tool_session_approval) + result["extensions"] = from_list(lambda x: to_class(Extension, x), self.extensions) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -19146,33 +22089,42 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class FactoryLogRequest: - """Parameters for recording factory progress.""" - - lines: list[FactoryLogLine] - """Ordered progress lines to append.""" +class FactoryRunTerminal: + """Prompt-safe terminal factory outcome.""" - run_id: str - """Factory run identifier.""" + error: str | None = None + failure: FactoryRunFailure | None = None + reason: str | None = None + result_preview: str | None = None @staticmethod - def from_dict(obj: Any) -> 'FactoryLogRequest': + def from_dict(obj: Any) -> 'FactoryRunTerminal': assert isinstance(obj, dict) - lines = from_list(FactoryLogLine.from_dict, obj.get("lines")) - run_id = from_str(obj.get("runId")) - return FactoryLogRequest(lines, run_id) + error = from_union([from_str, from_none], obj.get("error")) + failure = from_union([FactoryRunFailure.from_dict, from_none], obj.get("failure")) + reason = from_union([from_str, from_none], obj.get("reason")) + result_preview = from_union([from_str, from_none], obj.get("resultPreview")) + return FactoryRunTerminal(error, failure, reason, result_preview) def to_dict(self) -> dict: result: dict = {} - result["lines"] = from_list(lambda x: to_class(FactoryLogLine, x), self.lines) - result["runId"] = from_str(self.run_id) + if self.error is not None: + result["error"] = from_union([from_str, from_none], self.error) + if self.failure is not None: + result["failure"] = from_union([lambda x: to_class(FactoryRunFailure, x), from_none], self.failure) + if self.reason is not None: + result["reason"] = from_union([from_str, from_none], self.reason) + if self.result_preview is not None: + result["resultPreview"] = from_union([from_str, from_none], self.result_preview) return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class FactoryRunResult: - """Complete current or terminal factory run envelope.""" + """Terminal resumed run envelope. + Complete current or terminal factory run envelope. + """ run_id: str """Factory run identifier.""" @@ -19222,6 +22174,70 @@ def to_dict(self) -> dict: result["snapshot"] = self.snapshot return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryLogRequest: + """Parameters for recording factory progress.""" + + execution_token: str + """Opaque token identifying the current factory execution attempt.""" + + lines: list[FactoryLogLine] + """Ordered progress lines to append.""" + + run_id: str + """Factory run identifier.""" + + @staticmethod + def from_dict(obj: Any) -> 'FactoryLogRequest': + assert isinstance(obj, dict) + execution_token = from_str(obj.get("executionToken")) + lines = from_list(FactoryLogLine.from_dict, obj.get("lines")) + run_id = from_str(obj.get("runId")) + return FactoryLogRequest(execution_token, lines, run_id) + + def to_dict(self) -> dict: + result: dict = {} + result["executionToken"] = from_str(self.execution_token) + result["lines"] = from_list(lambda x: to_class(FactoryLogLine, x), self.lines) + result["runId"] = from_str(self.run_id) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryProgressPage: + """A bidirectional page of factory progress.""" + + has_more_newer: bool + has_more_older: bool + records: list[FactoryProgressLine] + revision: int + """Run revision reflected by this page.""" + + newest_seq: int | None = None + oldest_seq: int | None = None + + @staticmethod + def from_dict(obj: Any) -> 'FactoryProgressPage': + assert isinstance(obj, dict) + has_more_newer = from_bool(obj.get("hasMoreNewer")) + has_more_older = from_bool(obj.get("hasMoreOlder")) + records = from_list(FactoryProgressLine.from_dict, obj.get("records")) + revision = from_int(obj.get("revision")) + newest_seq = from_union([from_int, from_none], obj.get("newestSeq")) + oldest_seq = from_union([from_int, from_none], obj.get("oldestSeq")) + return FactoryProgressPage(has_more_newer, has_more_older, records, revision, newest_seq, oldest_seq) + + def to_dict(self) -> dict: + result: dict = {} + result["hasMoreNewer"] = from_bool(self.has_more_newer) + result["hasMoreOlder"] = from_bool(self.has_more_older) + result["records"] = from_list(lambda x: to_class(FactoryProgressLine, x), self.records) + result["revision"] = from_int(self.revision) + result["newestSeq"] = from_union([from_int, from_none], self.newest_seq) + result["oldestSeq"] = from_union([from_int, from_none], self.oldest_seq) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class FactoryRunRequest: @@ -19252,6 +22268,103 @@ def to_dict(self) -> dict: result["options"] = from_union([lambda x: to_class(RunOptions, x), from_none], self.options) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class HistoryRewindResult: + """Structured outcome of a rewind request.""" + + outcome: HistoryRewindOutcome + """Overall rewind outcome. This discriminates the result: it governs which of the remaining + fields are populated, so consumers must switch on it before reading `eventsRemoved`, + `restoredFiles`, `skippedFiles`, or `error`. See each field for the outcomes that + populate it. + """ + restored_files: list[str] + """Absolute paths restored to their captured preimages. Always empty for conversation-only + rewinds and for the unavailable outcomes (`session-busy`, + `file-change-tracking-disabled`, `unsupported-remote-session`); only + conversation-and-files outcomes that reached the file-restore stage populate it. + """ + skipped_files: list[HistorySkippedFileRestore] + """Captured files intentionally left unchanged. Always empty for conversation-only rewinds + and for the unavailable outcomes (`session-busy`, `file-change-tracking-disabled`, + `unsupported-remote-session`); only conversation-and-files outcomes that reached the + file-restore stage populate it. + """ + error: str | None = None + """Failure detail. Set only for the failure and partial-failure outcomes + (`files-rolled-back`, `rollback-incomplete`, `truncation-failed`, + `checkpoint-cleanup-failed`, `snapshot-prune-failed`); omitted for `success` and for the + unavailable outcomes (`session-busy`, `file-change-tracking-disabled`, + `unsupported-remote-session`). + """ + events_removed: int | None = None + """Number of persisted events removed by conversation truncation. Present only when + truncation succeeded (outcomes `success`, `checkpoint-cleanup-failed`, and + `snapshot-prune-failed`); omitted for every unavailable outcome (`session-busy`, + `file-change-tracking-disabled`, `unsupported-remote-session`) and for + `truncation-failed`, `files-rolled-back`, and `rollback-incomplete`. + """ + + @staticmethod + def from_dict(obj: Any) -> 'HistoryRewindResult': + assert isinstance(obj, dict) + outcome = HistoryRewindOutcome(obj.get("outcome")) + restored_files = from_list(from_str, obj.get("restoredFiles")) + skipped_files = from_list(HistorySkippedFileRestore.from_dict, obj.get("skippedFiles")) + error = from_union([from_str, from_none], obj.get("error")) + events_removed = from_union([from_int, from_none], obj.get("eventsRemoved")) + return HistoryRewindResult(outcome, restored_files, skipped_files, error, events_removed) + + def to_dict(self) -> dict: + result: dict = {} + result["outcome"] = to_enum(HistoryRewindOutcome, self.outcome) + result["restoredFiles"] = from_list(from_str, self.restored_files) + result["skippedFiles"] = from_list(lambda x: to_class(HistorySkippedFileRestore, x), self.skipped_files) + if self.error is not None: + result["error"] = from_union([from_str, from_none], self.error) + if self.events_removed is not None: + result["eventsRemoved"] = from_union([from_int, from_none], self.events_removed) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class HistoryPreviewRewindResult: + """Files and aggregate changes for a prospective rewind.""" + + available: bool + """Whether file restore is available for this session. This is authoritative: switch on it + and read `reason` only when it is false. + """ + file_count: int + """Number of unique files in the preview.""" + + files: list[HistoryRewindFilePreview] + """Files ordered by path.""" + + reason: HistoryRewindUnavailableReason | None = None + """Why file restore is unavailable, when applicable. Populated only when `available` is + false and never set when `available` is true. + """ + + @staticmethod + def from_dict(obj: Any) -> 'HistoryPreviewRewindResult': + assert isinstance(obj, dict) + available = from_bool(obj.get("available")) + file_count = from_int(obj.get("fileCount")) + files = from_list(HistoryRewindFilePreview.from_dict, obj.get("files")) + reason = from_union([HistoryRewindUnavailableReason, from_none], obj.get("reason")) + return HistoryPreviewRewindResult(available, file_count, files, reason) + + def to_dict(self) -> dict: + result: dict = {} + result["available"] = from_bool(self.available) + result["fileCount"] = from_int(self.file_count) + result["files"] = from_list(lambda x: to_class(HistoryRewindFilePreview, x), self.files) + if self.reason is not None: + result["reason"] = from_union([lambda x: to_enum(HistoryRewindUnavailableReason, x), from_none], self.reason) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class InstalledPlugin: @@ -19276,6 +22389,13 @@ class InstalledPlugin: source: InstalledPluginSource | str | None = None """Source for direct repo installs (when marketplace is empty)""" + source_sha: str | None = None + """Per-plugin source fingerprint (a SHA-256 hash of the plugin's catalog source spec plus + its resolved source subtree — NOT a Git commit SHA) captured at marketplace + install/update time. Auto-update compares it against the freshly recomputed fingerprint + to detect a content change that does not bump the version. Absent for pre-existing + installs and for direct (non-marketplace) installs. + """ version: str | None = None """Version installed (if available)""" @@ -19288,8 +22408,9 @@ def from_dict(obj: Any) -> 'InstalledPlugin': name = from_str(obj.get("name")) cache_path = from_union([from_str, from_none], obj.get("cache_path")) source = from_union([InstalledPluginSource.from_dict, from_str, from_none], obj.get("source")) + source_sha = from_union([from_str, from_none], obj.get("source_sha")) version = from_union([from_str, from_none], obj.get("version")) - return InstalledPlugin(enabled, installed_at, marketplace, name, cache_path, source, version) + return InstalledPlugin(enabled, installed_at, marketplace, name, cache_path, source, source_sha, version) def to_dict(self) -> dict: result: dict = {} @@ -19301,6 +22422,8 @@ def to_dict(self) -> dict: result["cache_path"] = from_union([from_str, from_none], self.cache_path) if self.source is not None: result["source"] = from_union([lambda x: to_class(InstalledPluginSource, x), from_str, from_none], self.source) + if self.source_sha is not None: + result["source_sha"] = from_union([from_str, from_none], self.source_sha) if self.version is not None: result["version"] = from_union([from_str, from_none], self.version) return result @@ -19329,6 +22452,13 @@ class SessionInstalledPlugin: source: SessionInstalledPluginSource | str | None = None """Source descriptor for direct repo installs (when marketplace is empty)""" + source_sha: str | None = None + """Per-plugin source fingerprint (a SHA-256 hash of the plugin's catalog source spec plus + its resolved source subtree — NOT a Git commit SHA) captured at marketplace + install/update time. Auto-update compares it against the freshly recomputed fingerprint + to detect a content change that does not bump the version. Absent for pre-existing + installs and for direct (non-marketplace) installs. + """ version: str | None = None """Installed version, if known""" @@ -19341,8 +22471,9 @@ def from_dict(obj: Any) -> 'SessionInstalledPlugin': name = from_str(obj.get("name")) cache_path = from_union([from_str, from_none], obj.get("cache_path")) source = from_union([SessionInstalledPluginSource.from_dict, from_str, from_none], obj.get("source")) + source_sha = from_union([from_str, from_none], obj.get("source_sha")) version = from_union([from_str, from_none], obj.get("version")) - return SessionInstalledPlugin(enabled, installed_at, marketplace, name, cache_path, source, version) + return SessionInstalledPlugin(enabled, installed_at, marketplace, name, cache_path, source, source_sha, version) def to_dict(self) -> dict: result: dict = {} @@ -19354,6 +22485,8 @@ def to_dict(self) -> dict: result["cache_path"] = from_union([from_str, from_none], self.cache_path) if self.source is not None: result["source"] = from_union([lambda x: to_class(SessionInstalledPluginSource, x), from_str, from_none], self.source) + if self.source_sha is not None: + result["source_sha"] = from_union([from_str, from_none], self.source_sha) if self.version is not None: result["version"] = from_union([from_str, from_none], self.version) return result @@ -19401,6 +22534,8 @@ def to_dict(self) -> dict: class LocalSessionMetadataValue: """Persisted local session metadata, including identifiers, timestamps, summary/name, client, context, detached state, and task ID. + + Local session metadata, omitted when the session does not exist. """ is_remote: bool """Always false for local sessions.""" @@ -19775,15 +22910,41 @@ class WorkspacesGetWorkspaceResult: @staticmethod def from_dict(obj: Any) -> 'WorkspacesGetWorkspaceResult': assert isinstance(obj, dict) - path = from_union([from_str, from_none], obj.get("path")) - workspace = from_union([Workspace.from_dict, from_none], obj.get("workspace")) - return WorkspacesGetWorkspaceResult(path, workspace) + path = from_union([from_str, from_none], obj.get("path")) + workspace = from_union([Workspace.from_dict, from_none], obj.get("workspace")) + return WorkspacesGetWorkspaceResult(path, workspace) + + def to_dict(self) -> dict: + result: dict = {} + if self.path is not None: + result["path"] = from_union([from_str, from_none], self.path) + result["workspace"] = from_union([lambda x: to_class(Workspace, x), from_none], self.workspace) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class WorkspacesUpdateMetadataRequest: + """Workspace metadata fields to update.""" + + context: Any = None + """Opaque workspace context supplied by the session host.""" + + name: str | None = None + """Optional workspace display name override.""" + + @staticmethod + def from_dict(obj: Any) -> 'WorkspacesUpdateMetadataRequest': + assert isinstance(obj, dict) + context = obj.get("context") + name = from_union([from_str, from_none], obj.get("name")) + return WorkspacesUpdateMetadataRequest(context, name) def to_dict(self) -> dict: result: dict = {} - if self.path is not None: - result["path"] = from_union([from_str, from_none], self.path) - result["workspace"] = from_union([lambda x: to_class(Workspace, x), from_none], self.workspace) + if self.context is not None: + result["context"] = self.context + if self.name is not None: + result["name"] = from_union([from_str, from_none], self.name) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -19922,25 +23083,29 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MCPStartServerRequest: - """Server name and configuration for an individual MCP server start.""" - - config: MCPServerConfig - """MCP server configuration (stdio process or remote HTTP/SSE)""" - + """Server name and optional configuration for an individual MCP server start. Omit `config` + for a config-free start-by-name of an already-configured server. + """ server_name: str """Name of the MCP server to start""" + config: MCPServerConfig | None = None + """MCP server configuration (stdio process or remote HTTP/SSE). Omit to start the server + with its already-registered configuration (config-free start-by-name). + """ + @staticmethod def from_dict(obj: Any) -> 'MCPStartServerRequest': assert isinstance(obj, dict) - config = MCPServerConfig.from_dict(obj.get("config")) server_name = from_str(obj.get("serverName")) - return MCPStartServerRequest(config, server_name) + config = from_union([MCPServerConfig.from_dict, from_none], obj.get("config")) + return MCPStartServerRequest(server_name, config) def to_dict(self) -> dict: result: dict = {} - result["config"] = to_class(MCPServerConfig, self.config) result["serverName"] = from_str(self.server_name) + if self.config is not None: + result["config"] = from_union([lambda x: to_class(MCPServerConfig, x), from_none], self.config) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -20139,7 +23304,7 @@ class ModelBilling: promo: ModelBillingPromo | None = None """Active server-driven promotion for this model, if any. Present when the model is being - promoted with a time-boxed discount. + promoted with a discount, which may be time-boxed or open-ended. """ token_prices: ModelBillingTokenPrices | None = None """Token-level pricing information for this model""" @@ -20284,6 +23449,38 @@ def to_dict(self) -> dict: result["scope"] = to_enum(AdditionalContentExclusionPolicyScope, self.scope) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class PermissionDecisionRequest: + """Pending permission request ID and the decision to apply (approve/reject and scope).""" + + request_id: str + """Request ID of the pending permission request""" + + result: PermissionDecision + """The client's response to the pending permission prompt""" + + decision_context: PermissionDecisionContext | None = None + """Optional informational context describing how and where this response was made. Omit it + to preserve legacy behavior without attributing an origin. + """ + + @staticmethod + def from_dict(obj: Any) -> 'PermissionDecisionRequest': + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + result = _load_PermissionDecision(obj.get("result")) + decision_context = from_union([PermissionDecisionContext.from_dict, from_none], obj.get("decisionContext")) + return PermissionDecisionRequest(request_id, result, decision_context) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + result["result"] = (self.result).to_dict() + if self.decision_context is not None: + result["decisionContext"] = from_union([lambda x: to_class(PermissionDecisionContext, x), from_none], self.decision_context) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class PermissionsConfigureAdditionalContentExclusionPolicy: @@ -20556,6 +23753,28 @@ def to_dict(self) -> dict: result["type"] = self.type return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class QueueInsertAtRequest: + """Parameters for inserting a queued message at a public visible position.""" + + message: QueueInsertMessage + position: int + """Zero-based position in the public visible queue. Values outside the queue clamp to an end.""" + + @staticmethod + def from_dict(obj: Any) -> 'QueueInsertAtRequest': + assert isinstance(obj, dict) + message = QueueInsertMessage.from_dict(obj.get("message")) + position = from_int(obj.get("position")) + return QueueInsertAtRequest(message, position) + + def to_dict(self) -> dict: + result: dict = {} + result["message"] = to_class(QueueInsertMessage, self.message) + result["position"] = from_int(self.position) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class QueuePendingItemsResult: @@ -20583,6 +23802,42 @@ def to_dict(self) -> dict: result["steeringMessages"] = from_list(from_str, self.steering_messages) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class QueueSnapshotResult: + """Internal snapshot of native queue state for local session orchestration.""" + + items: list[QueuePendingItems] + """User-facing pending items in FIFO order.""" + + steering_messages: list[str] + """Immediate steering messages waiting for an active turn.""" + + item_orders: list[int] | None = None + """Insertion orders for queued items, aligned with `items`.""" + + steering_message_orders: list[int] | None = None + """Insertion orders for immediate steering messages, aligned with `steeringMessages`.""" + + @staticmethod + def from_dict(obj: Any) -> 'QueueSnapshotResult': + assert isinstance(obj, dict) + items = from_list(QueuePendingItems.from_dict, obj.get("items")) + steering_messages = from_list(from_str, obj.get("steeringMessages")) + item_orders = from_union([lambda x: from_list(from_int, x), from_none], obj.get("itemOrders")) + steering_message_orders = from_union([lambda x: from_list(from_int, x), from_none], obj.get("steeringMessageOrders")) + return QueueSnapshotResult(items, steering_messages, item_orders, steering_message_orders) + + def to_dict(self) -> dict: + result: dict = {} + result["items"] = from_list(lambda x: to_class(QueuePendingItems, x), self.items) + result["steeringMessages"] = from_list(from_str, self.steering_messages) + if self.item_orders is not None: + result["itemOrders"] = from_union([lambda x: from_list(from_int, x), from_none], self.item_orders) + if self.steering_message_orders is not None: + result["steeringMessageOrders"] = from_union([lambda x: from_list(from_int, x), from_none], self.steering_message_orders) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionsStartRemoteControlRequest: @@ -20783,6 +24038,29 @@ def to_dict(self) -> dict: result["error"] = from_union([lambda x: to_class(SessionFSError, x), from_none], self.error) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionFSSqliteTransactionRequest: + """Statements to execute atomically. Providers apply busy handling for every call.""" + + session_id: str + """Target session identifier""" + + statements: list[SessionFSSqliteTransactionStatement] + + @staticmethod + def from_dict(obj: Any) -> 'SessionFSSqliteTransactionRequest': + assert isinstance(obj, dict) + session_id = from_str(obj.get("sessionId")) + statements = from_list(SessionFSSqliteTransactionStatement.from_dict, obj.get("statements")) + return SessionFSSqliteTransactionRequest(session_id, statements) + + def to_dict(self) -> dict: + result: dict = {} + result["sessionId"] = from_str(self.session_id) + result["statements"] = from_list(lambda x: to_class(SessionFSSqliteTransactionStatement, x), self.statements) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionOpenOptionsAdditionalContentExclusionPolicy: @@ -20811,6 +24089,57 @@ def to_dict(self) -> dict: result["scope"] = to_enum(AdditionalContentExclusionPolicyScope, self.scope) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ShellOptions: + """Per-session settings for built-in shell tools.""" + + init_profile: ShellInitProfile | None = None + """Controls automatic non-interactive profile loading where supported. Explicit initScripts + are unaffected. + """ + init_scripts: list[ShellInitScript] | None = None + """Ordered host-provided script paths sourced before each built-in shell command when the + entry's shell target matches the active shell. Use these for rc files, environment setup + scripts, + or other custom scripts. A script that returns a nonzero status is reported, and later + scripts + and the user command continue while the shell remains running. Because scripts are + sourced into + the command shell, `exit`, `exec`, failures under `set -e`, or other shell-terminating + behavior + can prevent continuation. Script standard output is preserved; Bash script stderr is + discarded, + PowerShell exception messages are replaced, and runtime-generated failure notices omit + configured script paths. When sandboxing is enabled, each script must already be readable + under + the active sandbox filesystem policy. Pass an empty array to clear the list. + """ + process_flags: list[str] | None = None + """Flags passed to the active built-in shell process on startup, replacing its default + flags. + When omitted, the built-in Bash shell uses `--norc --noprofile`, + and the built-in PowerShell shell uses `-NoProfile -NoLogo`. + """ + + @staticmethod + def from_dict(obj: Any) -> 'ShellOptions': + assert isinstance(obj, dict) + init_profile = from_union([ShellInitProfile, from_none], obj.get("initProfile")) + init_scripts = from_union([lambda x: from_list(ShellInitScript.from_dict, x), from_none], obj.get("initScripts")) + process_flags = from_union([lambda x: from_list(from_str, x), from_none], obj.get("processFlags")) + return ShellOptions(init_profile, init_scripts, process_flags) + + def to_dict(self) -> dict: + result: dict = {} + if self.init_profile is not None: + result["initProfile"] = from_union([lambda x: to_enum(ShellInitProfile, x), from_none], self.init_profile) + if self.init_scripts is not None: + result["initScripts"] = from_union([lambda x: from_list(lambda x: to_class(ShellInitScript, x), x), from_none], self.init_scripts) + if self.process_flags is not None: + result["processFlags"] = from_union([lambda x: from_list(from_str, x), from_none], self.process_flags) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionSettingsSnapshot: @@ -20881,10 +24210,10 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class AgentList: - """Custom agents available to the session.""" + """Agents available to the session.""" agents: list[AgentInfo] - """Available custom agents""" + """Available agents""" @staticmethod def from_dict(obj: Any) -> 'AgentList': @@ -20954,6 +24283,36 @@ def to_dict(self) -> dict: result["agents"] = from_list(lambda x: to_class(AgentInfo, x), self.agents) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionAgentListRequest: + include_built_in_agents: bool | None = None + """When true, request the session's configured built-in agents alongside custom agents. + Listing applies feature, context, inclusion, exclusion, and user-disabled-agent policy, + but does not evaluate transient invocation requirements such as model availability. + Built-in metadata may be omitted when the session cannot project it, such as a relay + session. + """ + include_prompt: bool | None = None + """When true, request authored base prompt text on each AgentInfo. Prompt text may be + omitted when unavailable, such as for agents projected through a relay session. + """ + + @staticmethod + def from_dict(obj: Any) -> 'SessionAgentListRequest': + assert isinstance(obj, dict) + include_built_in_agents = from_union([from_bool, from_none], obj.get("includeBuiltInAgents")) + include_prompt = from_union([from_bool, from_none], obj.get("includePrompt")) + return SessionAgentListRequest(include_built_in_agents, include_prompt) + + def to_dict(self) -> dict: + result: dict = {} + if self.include_built_in_agents is not None: + result["includeBuiltInAgents"] = from_union([from_bool, from_none], self.include_built_in_agents) + if self.include_prompt is not None: + result["includePrompt"] = from_union([from_bool, from_none], self.include_prompt) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SkillsGetInvokedResult: @@ -21442,8 +24801,9 @@ class WorkspaceDiffResult: """Changed files and their unified diffs.""" is_fallback: bool - """Whether a requested branch diff fell back to unstaged changes because branch diff failed.""" - + """Whether the requested diff fell back to unstaged changes, either because branch diff + failed or session diff was unavailable. + """ mode: WorkspaceDiffMode """Effective mode used for the returned changes.""" @@ -21453,6 +24813,16 @@ class WorkspaceDiffResult: base_branch: str | None = None """Default branch used for a branch diff, when branch mode was requested.""" + unavailable_reason: HistoryRewindUnavailableReason | None = None + """Why the session diff could not be produced, when applicable. Set only when `session` mode + was requested and `isFallback` is true, so a client can tell the permanent + `file-change-tracking-disabled` apart from the transient `session-busy`, which the same + request answers once the session settles. Never set for `unstaged` or `branch` mode, and + never `unsupported-remote-session`: a remote session's captures live on its own host, so + a `session`-mode diff is rejected for one rather than answered with a controller-side + fallback. + """ + @staticmethod def from_dict(obj: Any) -> 'WorkspaceDiffResult': assert isinstance(obj, dict) @@ -21461,7 +24831,8 @@ def from_dict(obj: Any) -> 'WorkspaceDiffResult': mode = WorkspaceDiffMode(obj.get("mode")) requested_mode = WorkspaceDiffMode(obj.get("requestedMode")) base_branch = from_union([from_str, from_none], obj.get("baseBranch")) - return WorkspaceDiffResult(changes, is_fallback, mode, requested_mode, base_branch) + unavailable_reason = from_union([HistoryRewindUnavailableReason, from_none], obj.get("unavailableReason")) + return WorkspaceDiffResult(changes, is_fallback, mode, requested_mode, base_branch, unavailable_reason) def to_dict(self) -> dict: result: dict = {} @@ -21471,6 +24842,8 @@ def to_dict(self) -> dict: result["requestedMode"] = to_enum(WorkspaceDiffMode, self.requested_mode) if self.base_branch is not None: result["baseBranch"] = from_union([from_str, from_none], self.base_branch) + if self.unavailable_reason is not None: + result["unavailableReason"] = from_union([lambda x: to_enum(HistoryRewindUnavailableReason, x), from_none], self.unavailable_reason) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -21679,6 +25052,183 @@ def to_dict(self) -> dict: result["result"] = from_union([lambda x: to_class(ExternalToolTextResultForLlm, x), from_str, from_none], self.result) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryRunSummary: + """Durable factory run summary with read-time live overlays.""" + + consumed: FactoryRunConsumed + created_at: int + declared_limits: FactoryDeclaredLimits + declared_phase_count: int + description: str + factory_name: str + live_agent_count: int + observed_at: int + revision: int + run_id: str + status: FactoryRunStatus + total_spawned_agent_count: int + updated_at: int + active_segment_started_at: int | None = None + approved: FactoryDeclaredLimits | None = None + completed_at: int | None = None + current_phase: FactoryCurrentPhase | None = None + started_at: int | None = None + terminal: FactoryRunTerminal | None = None + + @staticmethod + def from_dict(obj: Any) -> 'FactoryRunSummary': + assert isinstance(obj, dict) + consumed = FactoryRunConsumed.from_dict(obj.get("consumed")) + created_at = from_int(obj.get("createdAt")) + declared_limits = FactoryDeclaredLimits.from_dict(obj.get("declaredLimits")) + declared_phase_count = from_int(obj.get("declaredPhaseCount")) + description = from_str(obj.get("description")) + factory_name = from_str(obj.get("factoryName")) + live_agent_count = from_int(obj.get("liveAgentCount")) + observed_at = from_int(obj.get("observedAt")) + revision = from_int(obj.get("revision")) + run_id = from_str(obj.get("runId")) + status = FactoryRunStatus(obj.get("status")) + total_spawned_agent_count = from_int(obj.get("totalSpawnedAgentCount")) + updated_at = from_int(obj.get("updatedAt")) + active_segment_started_at = from_union([from_int, from_none], obj.get("activeSegmentStartedAt")) + approved = from_union([FactoryDeclaredLimits.from_dict, from_none], obj.get("approved")) + completed_at = from_union([from_int, from_none], obj.get("completedAt")) + current_phase = from_union([FactoryCurrentPhase.from_dict, from_none], obj.get("currentPhase")) + started_at = from_union([from_int, from_none], obj.get("startedAt")) + terminal = from_union([FactoryRunTerminal.from_dict, from_none], obj.get("terminal")) + return FactoryRunSummary(consumed, created_at, declared_limits, declared_phase_count, description, factory_name, live_agent_count, observed_at, revision, run_id, status, total_spawned_agent_count, updated_at, active_segment_started_at, approved, completed_at, current_phase, started_at, terminal) + + def to_dict(self) -> dict: + result: dict = {} + result["consumed"] = to_class(FactoryRunConsumed, self.consumed) + result["createdAt"] = from_int(self.created_at) + result["declaredLimits"] = to_class(FactoryDeclaredLimits, self.declared_limits) + result["declaredPhaseCount"] = from_int(self.declared_phase_count) + result["description"] = from_str(self.description) + result["factoryName"] = from_str(self.factory_name) + result["liveAgentCount"] = from_int(self.live_agent_count) + result["observedAt"] = from_int(self.observed_at) + result["revision"] = from_int(self.revision) + result["runId"] = from_str(self.run_id) + result["status"] = to_enum(FactoryRunStatus, self.status) + result["totalSpawnedAgentCount"] = from_int(self.total_spawned_agent_count) + result["updatedAt"] = from_int(self.updated_at) + result["activeSegmentStartedAt"] = from_union([from_int, from_none], self.active_segment_started_at) + result["approved"] = from_union([lambda x: to_class(FactoryDeclaredLimits, x), from_none], self.approved) + result["completedAt"] = from_union([from_int, from_none], self.completed_at) + result["currentPhase"] = from_union([lambda x: to_class(FactoryCurrentPhase, x), from_none], self.current_phase) + result["startedAt"] = from_union([from_int, from_none], self.started_at) + result["terminal"] = from_union([lambda x: to_class(FactoryRunTerminal, x), from_none], self.terminal) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryResumeResult: + """Resolved persisted factory identity and resumed run envelope.""" + + factory_name: str + """Persisted factory name resolved for the resumed run.""" + + run: FactoryRunResult + """Terminal resumed run envelope.""" + + @staticmethod + def from_dict(obj: Any) -> 'FactoryResumeResult': + assert isinstance(obj, dict) + factory_name = from_str(obj.get("factoryName")) + run = FactoryRunResult.from_dict(obj.get("run")) + return FactoryResumeResult(factory_name, run) + + def to_dict(self) -> dict: + result: dict = {} + result["factoryName"] = from_str(self.factory_name) + result["run"] = to_class(FactoryRunResult, self.run) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryRunDetail: + """Full factory run observability detail.""" + + agents: list[FactoryAgentSummary] + consumed: FactoryRunConsumed + created_at: int + declared_limits: FactoryDeclaredLimits + declared_phase_count: int + description: str + factory_name: str + live_agent_count: int + observed_at: int + phases: list[FactoryPhaseObservation] + progress: FactoryProgressPage + revision: int + run_id: str + status: FactoryRunStatus + total_spawned_agent_count: int + updated_at: int + active_segment_started_at: int | None = None + approved: FactoryDeclaredLimits | None = None + completed_at: int | None = None + current_phase: FactoryCurrentPhase | None = None + started_at: int | None = None + terminal: FactoryRunTerminal | None = None + + @staticmethod + def from_dict(obj: Any) -> 'FactoryRunDetail': + assert isinstance(obj, dict) + agents = from_list(FactoryAgentSummary.from_dict, obj.get("agents")) + consumed = FactoryRunConsumed.from_dict(obj.get("consumed")) + created_at = from_int(obj.get("createdAt")) + declared_limits = FactoryDeclaredLimits.from_dict(obj.get("declaredLimits")) + declared_phase_count = from_int(obj.get("declaredPhaseCount")) + description = from_str(obj.get("description")) + factory_name = from_str(obj.get("factoryName")) + live_agent_count = from_int(obj.get("liveAgentCount")) + observed_at = from_int(obj.get("observedAt")) + phases = from_list(FactoryPhaseObservation.from_dict, obj.get("phases")) + progress = FactoryProgressPage.from_dict(obj.get("progress")) + revision = from_int(obj.get("revision")) + run_id = from_str(obj.get("runId")) + status = FactoryRunStatus(obj.get("status")) + total_spawned_agent_count = from_int(obj.get("totalSpawnedAgentCount")) + updated_at = from_int(obj.get("updatedAt")) + active_segment_started_at = from_union([from_int, from_none], obj.get("activeSegmentStartedAt")) + approved = from_union([FactoryDeclaredLimits.from_dict, from_none], obj.get("approved")) + completed_at = from_union([from_int, from_none], obj.get("completedAt")) + current_phase = from_union([FactoryCurrentPhase.from_dict, from_none], obj.get("currentPhase")) + started_at = from_union([from_int, from_none], obj.get("startedAt")) + terminal = from_union([FactoryRunTerminal.from_dict, from_none], obj.get("terminal")) + return FactoryRunDetail(agents, consumed, created_at, declared_limits, declared_phase_count, description, factory_name, live_agent_count, observed_at, phases, progress, revision, run_id, status, total_spawned_agent_count, updated_at, active_segment_started_at, approved, completed_at, current_phase, started_at, terminal) + + def to_dict(self) -> dict: + result: dict = {} + result["agents"] = from_list(lambda x: to_class(FactoryAgentSummary, x), self.agents) + result["consumed"] = to_class(FactoryRunConsumed, self.consumed) + result["createdAt"] = from_int(self.created_at) + result["declaredLimits"] = to_class(FactoryDeclaredLimits, self.declared_limits) + result["declaredPhaseCount"] = from_int(self.declared_phase_count) + result["description"] = from_str(self.description) + result["factoryName"] = from_str(self.factory_name) + result["liveAgentCount"] = from_int(self.live_agent_count) + result["observedAt"] = from_int(self.observed_at) + result["phases"] = from_list(lambda x: to_class(FactoryPhaseObservation, x), self.phases) + result["progress"] = to_class(FactoryProgressPage, self.progress) + result["revision"] = from_int(self.revision) + result["runId"] = from_str(self.run_id) + result["status"] = to_enum(FactoryRunStatus, self.status) + result["totalSpawnedAgentCount"] = from_int(self.total_spawned_agent_count) + result["updatedAt"] = from_int(self.updated_at) + result["activeSegmentStartedAt"] = from_union([from_int, from_none], self.active_segment_started_at) + result["approved"] = from_union([lambda x: to_class(FactoryDeclaredLimits, x), from_none], self.approved) + result["completedAt"] = from_union([from_int, from_none], self.completed_at) + result["currentPhase"] = from_union([lambda x: to_class(FactoryCurrentPhase, x), from_none], self.current_phase) + result["startedAt"] = from_union([from_int, from_none], self.started_at) + result["terminal"] = from_union([lambda x: to_class(FactoryRunTerminal, x), from_none], self.terminal) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionsSetAdditionalPluginsRequest: @@ -21743,6 +25293,26 @@ def to_dict(self) -> dict: result["sessions"] = from_list(lambda x: to_class(LocalSessionMetadataValue, x), self.sessions) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionsGetMetadataResult: + """Persisted local session metadata when the session exists.""" + + session: LocalSessionMetadataValue | None = None + """Local session metadata, omitted when the session does not exist.""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionsGetMetadataResult': + assert isinstance(obj, dict) + session = from_union([LocalSessionMetadataValue.from_dict, from_none], obj.get("session")) + return SessionsGetMetadataResult(session) + + def to_dict(self) -> dict: + result: dict = {} + if self.session is not None: + result["session"] = from_union([lambda x: to_class(LocalSessionMetadataValue, x), from_none], self.session) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionOpenResult: @@ -22148,15 +25718,18 @@ class SandboxConfig: add_current_working_directory: bool | None = None """Whether to auto-add the current working directory to readwritePaths. Default: true.""" - gh_auth: bool | None = None - """Whether to export `GH_TOKEN` so the `gh` CLI authenticates inside the sandbox without the - OS keyring the sandbox blocks. Default: false (opt-in). - """ - git_auth: bool | None = None - """Whether to inject the Copilot GitHub token as an `http..extraheader` so - authenticated HTTPS git works inside the sandbox without the shell-based credential - helper the sandbox blocks. Default: false (opt-in). + allow_dev_tool_access: bool | None = None + """Whether to auto-grant read access to common developer-tool caches, registries, and + toolchains in their default home locations (cargo, go, npm, Maven, and more), plus + read-write access to (and, on Unix, up-front creation of) the scratch caches builds write + on every run (go-build, ccache, sccache, Gradle caches, Cargo lock/tracker files), so + builds work without extra configuration; a relocated CARGO_HOME additionally gets its + Cargo lock files granted read-write. Default: true (enabled by default; set to false to + opt out). """ + auth: SandboxConfigAuth | None = None + """Credential-injection capability flags.""" + user_policy: SandboxConfigUserPolicy | None = None """User-managed sandbox policy fragment merged into the auto-discovered base policy.""" @@ -22165,24 +25738,46 @@ def from_dict(obj: Any) -> 'SandboxConfig': assert isinstance(obj, dict) enabled = from_bool(obj.get("enabled")) add_current_working_directory = from_union([from_bool, from_none], obj.get("addCurrentWorkingDirectory")) - gh_auth = from_union([from_bool, from_none], obj.get("ghAuth")) - git_auth = from_union([from_bool, from_none], obj.get("gitAuth")) + allow_dev_tool_access = from_union([from_bool, from_none], obj.get("allowDevToolAccess")) + auth = from_union([SandboxConfigAuth.from_dict, from_none], obj.get("auth")) user_policy = from_union([SandboxConfigUserPolicy.from_dict, from_none], obj.get("userPolicy")) - return SandboxConfig(enabled, add_current_working_directory, gh_auth, git_auth, user_policy) + return SandboxConfig(enabled, add_current_working_directory, allow_dev_tool_access, auth, user_policy) def to_dict(self) -> dict: result: dict = {} result["enabled"] = from_bool(self.enabled) if self.add_current_working_directory is not None: result["addCurrentWorkingDirectory"] = from_union([from_bool, from_none], self.add_current_working_directory) - if self.gh_auth is not None: - result["ghAuth"] = from_union([from_bool, from_none], self.gh_auth) - if self.git_auth is not None: - result["gitAuth"] = from_union([from_bool, from_none], self.git_auth) + if self.allow_dev_tool_access is not None: + result["allowDevToolAccess"] = from_union([from_bool, from_none], self.allow_dev_tool_access) + if self.auth is not None: + result["auth"] = from_union([lambda x: to_class(SandboxConfigAuth, x), from_none], self.auth) if self.user_policy is not None: result["userPolicy"] = from_union([lambda x: to_class(SandboxConfigUserPolicy, x), from_none], self.user_policy) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionFSSqliteTransactionResult: + """Per-statement results, or a classified transaction error.""" + + results: list[SessionFSSqliteQueryResult] + error: SessionFSSqliteTransactionError | None = None + + @staticmethod + def from_dict(obj: Any) -> 'SessionFSSqliteTransactionResult': + assert isinstance(obj, dict) + results = from_list(SessionFSSqliteQueryResult.from_dict, obj.get("results")) + error = from_union([SessionFSSqliteTransactionError.from_dict, from_none], obj.get("error")) + return SessionFSSqliteTransactionResult(results, error) + + def to_dict(self) -> dict: + result: dict = {} + result["results"] = from_list(lambda x: to_class(SessionFSSqliteQueryResult, x), self.results) + if self.error is not None: + result["error"] = from_union([lambda x: to_class(SessionFSSqliteTransactionError, x), from_none], self.error) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class MCPListToolsResult: @@ -22232,6 +25827,69 @@ def to_dict(self) -> dict: result["required"] = from_union([lambda x: from_list(from_str, x), from_none], self.required) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryListRunsResult: + """A page of factory runs in durable creation order.""" + + runs: list[FactoryRunSummary] + has_more_newer: bool | None = None + """Whether terminal runs newer than this page exist.""" + + newest_seq: int | None = None + """Newest terminal-run cursor in this page, or null when the terminal window is empty.""" + + oldest_seq: int | None = None + """Oldest terminal-run cursor in this page, or null when the terminal window is empty.""" + + omitted_older: int | None = None + """Number of terminal runs older than this page.""" + + @staticmethod + def from_dict(obj: Any) -> 'FactoryListRunsResult': + assert isinstance(obj, dict) + runs = from_list(FactoryRunSummary.from_dict, obj.get("runs")) + has_more_newer = from_union([from_bool, from_none], obj.get("hasMoreNewer")) + newest_seq = from_union([from_int, from_none], obj.get("newestSeq")) + oldest_seq = from_union([from_int, from_none], obj.get("oldestSeq")) + omitted_older = from_union([from_int, from_none], obj.get("omittedOlder")) + return FactoryListRunsResult(runs, has_more_newer, newest_seq, oldest_seq, omitted_older) + + def to_dict(self) -> dict: + result: dict = {} + result["runs"] = from_list(lambda x: to_class(FactoryRunSummary, x), self.runs) + if self.has_more_newer is not None: + result["hasMoreNewer"] = from_union([from_bool, from_none], self.has_more_newer) + if self.newest_seq is not None: + result["newestSeq"] = from_union([from_int, from_none], self.newest_seq) + if self.oldest_seq is not None: + result["oldestSeq"] = from_union([from_int, from_none], self.oldest_seq) + if self.omitted_older is not None: + result["omittedOlder"] = from_union([from_int, from_none], self.omitted_older) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class BuiltInModelCatalogEntry: + """A well-known model in the runtime's built-in catalog.""" + + id: str + """Well-known runtime model ID suitable for `ProviderConfig.modelId` or + `ProviderModelConfig.modelId`. This is not necessarily the provider-facing deployment or + model name and does not indicate CAPI entitlement or provider availability. + """ + + @staticmethod + def from_dict(obj: Any) -> 'BuiltInModelCatalogEntry': + assert isinstance(obj, dict) + id = from_str(obj.get("id")) + return BuiltInModelCatalogEntry(id) + + def to_dict(self) -> dict: + result: dict = {} + result["id"] = from_str(self.id) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ProviderAddRequest: @@ -22279,6 +25937,15 @@ class SessionOpenOptions: additional_content_exclusion_policies: list[SessionOpenOptionsAdditionalContentExclusionPolicy] | None = None """Additional content-exclusion policies to merge into the session policy set.""" + additional_directories: list[str] | None = None + """Additional directories the agent may access beyond the working directory. Each entry is + granted to the session's file-access allow-list and surfaced to the model (system prompt + context and `@`-mention completion). Absolute paths are recommended; a relative path is + resolved against the session's working directory. Nonexistent or unresolvable entries are + skipped with a warning. This is applied on both session creation and resume, and is not + persisted: a resumed session that omits this option does not retain previously supplied + directories (re-supply them, exactly as the CLI re-passes `--add-dir`). + """ agent_context: str | None = None """Runtime context discriminator for agent filtering.""" @@ -22328,6 +25995,10 @@ class SessionOpenOptions: disabled_instruction_sources: list[str] | None = None """Instruction source IDs disabled for this session.""" + disabled_mcp_servers: list[str] | None = None + """MCP server names disabled for this session. Disabled servers are not started or + authenticated on create or cold resume. + """ disabled_skills: list[str] | None = None """Skill IDs disabled for this session.""" @@ -22336,6 +26007,28 @@ class SessionOpenOptions: `assistant.message` event. Off by default; may change or be removed while the citations surface is experimental. """ + enable_file_change_tracking: bool | None = None + """Opt in to capturing file changes for session rewind and session diff. Capture cannot + reconstruct changes made before it was enabled. On create it starts capture from the + first turn. It is also honored on resume: for a session that already has tracked prior + turns, tracking continues automatically even if this is omitted; passing it on resume + additionally enables tracking for an eligible session that has no prior root turn yet. + Resuming a session whose prior root turns were never tracked has no restorable baseline, + so tracking stays disabled for it and rewind reports file change tracking as unavailable; + the resume itself still succeeds, so sessions that predate tracking remain loadable. The + opt-in is only rejected when the session can never track (a subagent session, or one + without local session storage). It is intentionally absent from the mutable options + update because enabling it after edits have occurred would create an incomplete, + misleading baseline. Subagents share the parent session's capture store and are not + tracked as separate rewind points: a file a subagent writes is attributed to whichever + root user turn was open when the capture was staged, just before the tool body ran. A + turn cannot open while a staged capture is still in flight, so a subagent tool that + staged under the spawning turn stays attributed to it however late the write lands, while + a capture it stages after the user's next message belongs to that later turn. Attribution + decides which turn's rewind point counts and file preview include that write; it does not + narrow which rewinds revert it, because a rewind restores every capture from the selected + turn onward, so the earlier spawning turn reverts it as well. + """ enable_managed_settings: bool | None = None """Opt-in: self-fetch and enforce enterprise managed settings at session bootstrap.""" @@ -22354,6 +26047,9 @@ class SessionOpenOptions: events_log_directory: str | None = None """Override directory for session event logs.""" + events_log_includes_subagents: bool | None = None + """Whether subagent callback events should be forwarded into the session event log sink.""" + excluded_builtin_agents: list[str] | None = None """Built-in subagent names to exclude from this session. Excluded built-ins are hidden from agent discovery and cannot be dispatched unless a custom agent with the same name is @@ -22392,6 +26088,10 @@ class SessionOpenOptions: lsp_client_name: str | None = None """Identifier sent to LSP-style integrations.""" + managed_settings: SessionManagedSettings | None = None + """Permissions-only enterprise policy injected by the SDK host at session create or resume. + Composes restrictively with self-fetched and device policy and is not persisted. + """ max_inline_binary_bytes: int | None = None """Maximum decoded byte size of a single inline model-facing binary tool result persisted in session events (default 10 MB). @@ -22420,8 +26120,10 @@ class SessionOpenOptions: rejected. """ reasoning_effort: str | None = None - """Initial reasoning effort level.""" - + """Initial reasoning effort level. CAPI values are model-defined and validated against the + selected model; BYOK providers may define additional values. When omitted, no effort + override is applied. + """ reasoning_summary: ReasoningSummary | None = None """Initial reasoning summary mode for supported model clients.""" @@ -22449,11 +26151,14 @@ class SessionOpenOptions: session_limits: SessionLimitsConfig | None = None """Initial session limits.""" + shell: ShellOptions | None = None + """Per-session settings for built-in shell tools.""" + shell_init_profile: str | None = None - """Shell init profile.""" + """Use shell.initProfile instead. Shell init profile.""" shell_process_flags: list[str] | None = None - """Per-shell process flags.""" + """PowerShell process flags applied to built-in and user-requested shell commands.""" skill_directories: list[str] | None = None """Additional directories to search for skills.""" @@ -22477,6 +26182,7 @@ class SessionOpenOptions: def from_dict(obj: Any) -> 'SessionOpenOptions': assert isinstance(obj, dict) additional_content_exclusion_policies = from_union([lambda x: from_list(SessionOpenOptionsAdditionalContentExclusionPolicy.from_dict, x), from_none], obj.get("additionalContentExclusionPolicies")) + additional_directories = from_union([lambda x: from_list(from_str, x), from_none], obj.get("additionalDirectories")) agent_context = from_union([from_str, from_none], obj.get("agentContext")) allow_all_mcp_server_instructions = from_union([from_bool, from_none], obj.get("allowAllMcpServerInstructions")) ask_user_disabled = from_union([from_bool, from_none], obj.get("askUserDisabled")) @@ -22493,14 +26199,17 @@ def from_dict(obj: Any) -> 'SessionOpenOptions': detached_from_spawning_parent_engagement_id = from_union([from_str, from_none], obj.get("detachedFromSpawningParentEngagementId")) detached_from_spawning_parent_session_id = from_union([from_str, from_none], obj.get("detachedFromSpawningParentSessionId")) disabled_instruction_sources = from_union([lambda x: from_list(from_str, x), from_none], obj.get("disabledInstructionSources")) + disabled_mcp_servers = from_union([lambda x: from_list(from_str, x), from_none], obj.get("disabledMcpServers")) disabled_skills = from_union([lambda x: from_list(from_str, x), from_none], obj.get("disabledSkills")) enable_citations = from_union([from_bool, from_none], obj.get("enableCitations")) + enable_file_change_tracking = from_union([from_bool, from_none], obj.get("enableFileChangeTracking")) enable_managed_settings = from_union([from_bool, from_none], obj.get("enableManagedSettings")) enable_on_demand_instruction_discovery = from_union([from_bool, from_none], obj.get("enableOnDemandInstructionDiscovery")) enable_script_safety = from_union([from_bool, from_none], obj.get("enableScriptSafety")) enable_streaming = from_union([from_bool, from_none], obj.get("enableStreaming")) env_value_mode = from_union([MCPSetEnvValueModeDetails, from_none], obj.get("envValueMode")) events_log_directory = from_union([from_str, from_none], obj.get("eventsLogDirectory")) + events_log_includes_subagents = from_union([from_bool, from_none], obj.get("eventsLogIncludesSubagents")) excluded_builtin_agents = from_union([lambda x: from_list(from_str, x), from_none], obj.get("excludedBuiltinAgents")) excluded_tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("excludedTools")) exp_assignments = obj.get("expAssignments") @@ -22511,6 +26220,7 @@ def from_dict(obj: Any) -> 'SessionOpenOptions': is_experimental_mode = from_union([from_bool, from_none], obj.get("isExperimentalMode")) log_interactive_shells = from_union([from_bool, from_none], obj.get("logInteractiveShells")) lsp_client_name = from_union([from_str, from_none], obj.get("lspClientName")) + managed_settings = from_union([SessionManagedSettings.from_dict, from_none], obj.get("managedSettings")) max_inline_binary_bytes = from_union([from_int, from_none], obj.get("maxInlineBinaryBytes")) memory = from_union([MemoryConfiguration.from_dict, from_none], obj.get("memory")) model = from_union([from_str, from_none], obj.get("model")) @@ -22529,6 +26239,7 @@ def from_dict(obj: Any) -> 'SessionOpenOptions': session_capabilities = from_union([lambda x: from_list(SessionCapability, x), from_none], obj.get("sessionCapabilities")) session_id = from_union([from_str, from_none], obj.get("sessionId")) session_limits = from_union([SessionLimitsConfig.from_dict, from_none], obj.get("sessionLimits")) + shell = from_union([ShellOptions.from_dict, from_none], obj.get("shell")) shell_init_profile = from_union([from_str, from_none], obj.get("shellInitProfile")) shell_process_flags = from_union([lambda x: from_list(from_str, x), from_none], obj.get("shellProcessFlags")) skill_directories = from_union([lambda x: from_list(from_str, x), from_none], obj.get("skillDirectories")) @@ -22537,12 +26248,14 @@ def from_dict(obj: Any) -> 'SessionOpenOptions': verbosity = from_union([Verbosity, from_none], obj.get("verbosity")) working_directory = from_union([from_str, from_none], obj.get("workingDirectory")) working_directory_context = from_union([SessionContext.from_dict, from_none], obj.get("workingDirectoryContext")) - return SessionOpenOptions(additional_content_exclusion_policies, agent_context, allow_all_mcp_server_instructions, ask_user_disabled, auth_info, available_tools, capi, client_kind, client_name, coauthor_enabled, config_dir, continue_on_auto_mode, copilot_url, custom_agents_local_only, detached_from_spawning_parent_engagement_id, detached_from_spawning_parent_session_id, disabled_instruction_sources, disabled_skills, enable_citations, enable_managed_settings, enable_on_demand_instruction_discovery, enable_script_safety, enable_streaming, env_value_mode, events_log_directory, excluded_builtin_agents, excluded_tools, exp_assignments, feature_flags, included_builtin_agents, installed_plugins, integration_id, is_experimental_mode, log_interactive_shells, lsp_client_name, max_inline_binary_bytes, memory, model, model_capabilities_overrides, models, name, provider, providers, reasoning_effort, reasoning_summary, remote_defaulted_on, remote_exporting, remote_steerable, running_in_interactive_mode, sandbox_config, session_capabilities, session_id, session_limits, shell_init_profile, shell_process_flags, skill_directories, skip_custom_instructions, trajectory_file, verbosity, working_directory, working_directory_context) + return SessionOpenOptions(additional_content_exclusion_policies, additional_directories, agent_context, allow_all_mcp_server_instructions, ask_user_disabled, auth_info, available_tools, capi, client_kind, client_name, coauthor_enabled, config_dir, continue_on_auto_mode, copilot_url, custom_agents_local_only, detached_from_spawning_parent_engagement_id, detached_from_spawning_parent_session_id, disabled_instruction_sources, disabled_mcp_servers, disabled_skills, enable_citations, enable_file_change_tracking, enable_managed_settings, enable_on_demand_instruction_discovery, enable_script_safety, enable_streaming, env_value_mode, events_log_directory, events_log_includes_subagents, excluded_builtin_agents, excluded_tools, exp_assignments, feature_flags, included_builtin_agents, installed_plugins, integration_id, is_experimental_mode, log_interactive_shells, lsp_client_name, managed_settings, max_inline_binary_bytes, memory, model, model_capabilities_overrides, models, name, provider, providers, reasoning_effort, reasoning_summary, remote_defaulted_on, remote_exporting, remote_steerable, running_in_interactive_mode, sandbox_config, session_capabilities, session_id, session_limits, shell, shell_init_profile, shell_process_flags, skill_directories, skip_custom_instructions, trajectory_file, verbosity, working_directory, working_directory_context) def to_dict(self) -> dict: result: dict = {} if self.additional_content_exclusion_policies is not None: result["additionalContentExclusionPolicies"] = from_union([lambda x: from_list(lambda x: to_class(SessionOpenOptionsAdditionalContentExclusionPolicy, x), x), from_none], self.additional_content_exclusion_policies) + if self.additional_directories is not None: + result["additionalDirectories"] = from_union([lambda x: from_list(from_str, x), from_none], self.additional_directories) if self.agent_context is not None: result["agentContext"] = from_union([from_str, from_none], self.agent_context) if self.allow_all_mcp_server_instructions is not None: @@ -22575,10 +26288,14 @@ def to_dict(self) -> dict: result["detachedFromSpawningParentSessionId"] = from_union([from_str, from_none], self.detached_from_spawning_parent_session_id) if self.disabled_instruction_sources is not None: result["disabledInstructionSources"] = from_union([lambda x: from_list(from_str, x), from_none], self.disabled_instruction_sources) + if self.disabled_mcp_servers is not None: + result["disabledMcpServers"] = from_union([lambda x: from_list(from_str, x), from_none], self.disabled_mcp_servers) if self.disabled_skills is not None: result["disabledSkills"] = from_union([lambda x: from_list(from_str, x), from_none], self.disabled_skills) if self.enable_citations is not None: result["enableCitations"] = from_union([from_bool, from_none], self.enable_citations) + if self.enable_file_change_tracking is not None: + result["enableFileChangeTracking"] = from_union([from_bool, from_none], self.enable_file_change_tracking) if self.enable_managed_settings is not None: result["enableManagedSettings"] = from_union([from_bool, from_none], self.enable_managed_settings) if self.enable_on_demand_instruction_discovery is not None: @@ -22591,6 +26308,8 @@ def to_dict(self) -> dict: result["envValueMode"] = from_union([lambda x: to_enum(MCPSetEnvValueModeDetails, x), from_none], self.env_value_mode) if self.events_log_directory is not None: result["eventsLogDirectory"] = from_union([from_str, from_none], self.events_log_directory) + if self.events_log_includes_subagents is not None: + result["eventsLogIncludesSubagents"] = from_union([from_bool, from_none], self.events_log_includes_subagents) if self.excluded_builtin_agents is not None: result["excludedBuiltinAgents"] = from_union([lambda x: from_list(from_str, x), from_none], self.excluded_builtin_agents) if self.excluded_tools is not None: @@ -22611,6 +26330,8 @@ def to_dict(self) -> dict: result["logInteractiveShells"] = from_union([from_bool, from_none], self.log_interactive_shells) if self.lsp_client_name is not None: result["lspClientName"] = from_union([from_str, from_none], self.lsp_client_name) + if self.managed_settings is not None: + result["managedSettings"] = from_union([lambda x: to_class(SessionManagedSettings, x), from_none], self.managed_settings) if self.max_inline_binary_bytes is not None: result["maxInlineBinaryBytes"] = from_union([from_int, from_none], self.max_inline_binary_bytes) if self.memory is not None: @@ -22647,6 +26368,8 @@ def to_dict(self) -> dict: result["sessionId"] = from_union([from_str, from_none], self.session_id) if self.session_limits is not None: result["sessionLimits"] = from_union([lambda x: to_class(SessionLimitsConfig, x), from_none], self.session_limits) + if self.shell is not None: + result["shell"] = from_union([lambda x: to_class(ShellOptions, x), from_none], self.shell) if self.shell_init_profile is not None: result["shellInitProfile"] = from_union([from_str, from_none], self.shell_init_profile) if self.shell_process_flags is not None: @@ -22726,7 +26449,7 @@ class SessionUpdateOptionsParams: enable_on_demand_instruction_discovery: bool | None = None """Whether to discover custom instructions on demand after successful file views (AGENTS.md / CLAUDE.md / .github/copilot-instructions.md surfacing). Combined with - `skipCustomInstructions` and the runtime-side `ON_DEMAND_INSTRUCTIONS` feature flag. + `skipCustomInstructions`. """ enable_reasoning_summaries: bool | None = None """Whether to surface reasoning-summary events from the model.""" @@ -22752,6 +26475,9 @@ class SessionUpdateOptionsParams: """Override directory for the session-events log. When unset, the runtime's default events log directory is used. """ + events_log_includes_subagents: bool | None = None + """Whether subagent callback events should be forwarded into the session event log sink.""" + excluded_builtin_agents: list[str] | None = None """Built-in subagent names to exclude from this session. Excluded built-ins are hidden from agent discovery and cannot be dispatched unless a custom agent with the same name is @@ -22808,8 +26534,10 @@ class SessionUpdateOptionsParams: """Custom model-provider configuration (BYOK).""" reasoning_effort: str | None = None - """Reasoning effort for the selected model (model-defined enum).""" - + """Reasoning effort for the selected model. CAPI values are model-defined and validated + against the selected model; BYOK providers may define additional values. When omitted, no + effort override is applied. + """ reasoning_summary: ReasoningSummary | None = None """Reasoning summary mode for supported model clients.""" @@ -22827,11 +26555,14 @@ class SessionUpdateOptionsParams: session_limits: SessionLimitsConfig | None = None """Optional session limits. Pass null to clear the session limits.""" + shell: ShellOptions | None = None + """Per-session settings for built-in shell tools.""" + shell_init_profile: str | None = None - """Shell init profile (`None` or `NonInteractive`).""" + """Use shell.initProfile instead. Shell init profile (`None` or `NonInteractive`).""" shell_process_flags: list[str] | None = None - """Per-shell process flags (e.g., `pwsh` arguments).""" + """PowerShell process flags applied to built-in and user-requested shell commands.""" skill_directories: list[str] | None = None """Additional directories to search for skills.""" @@ -22887,6 +26618,7 @@ def from_dict(obj: Any) -> 'SessionUpdateOptionsParams': enable_streaming = from_union([from_bool, from_none], obj.get("enableStreaming")) env_value_mode = from_union([MCPSetEnvValueModeDetails, from_none], obj.get("envValueMode")) events_log_directory = from_union([from_str, from_none], obj.get("eventsLogDirectory")) + events_log_includes_subagents = from_union([from_bool, from_none], obj.get("eventsLogIncludesSubagents")) excluded_builtin_agents = from_union([lambda x: from_list(from_str, x), from_none], obj.get("excludedBuiltinAgents")) excluded_tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("excludedTools")) feature_flags = from_union([lambda x: from_dict(from_bool, x), from_none], obj.get("featureFlags")) @@ -22908,6 +26640,7 @@ def from_dict(obj: Any) -> 'SessionUpdateOptionsParams': sandbox_config = from_union([SandboxConfig.from_dict, from_none], obj.get("sandboxConfig")) session_capabilities = from_union([lambda x: from_list(SessionCapability, x), from_none], obj.get("sessionCapabilities")) session_limits = from_union([SessionLimitsConfig.from_dict, from_none], obj.get("sessionLimits")) + shell = from_union([ShellOptions.from_dict, from_none], obj.get("shell")) shell_init_profile = from_union([from_str, from_none], obj.get("shellInitProfile")) shell_process_flags = from_union([lambda x: from_list(from_str, x), from_none], obj.get("shellProcessFlags")) skill_directories = from_union([lambda x: from_list(from_str, x), from_none], obj.get("skillDirectories")) @@ -22918,7 +26651,7 @@ def from_dict(obj: Any) -> 'SessionUpdateOptionsParams': trajectory_file = from_union([from_str, from_none], obj.get("trajectoryFile")) verbosity = from_union([Verbosity, from_none], obj.get("verbosity")) working_directory = from_union([from_str, from_none], obj.get("workingDirectory")) - return SessionUpdateOptionsParams(additional_content_exclusion_policies, agent_context, allow_all_mcp_server_instructions, ask_user_disabled, available_tools, capi, client_name, coauthor_enabled, context_tier, continue_on_auto_mode, copilot_url, custom_agents_local_only, disabled_instruction_sources, disabled_skills, enable_file_hooks, enable_host_git_operations, enable_on_demand_instruction_discovery, enable_reasoning_summaries, enable_script_safety, enable_session_store, enable_skills, enable_streaming, env_value_mode, events_log_directory, excluded_builtin_agents, excluded_tools, feature_flags, included_builtin_agents, installed_plugins, integration_id, is_experimental_mode, log_interactive_shells, lsp_client_name, manage_schedule_enabled, max_inline_binary_bytes, model, model_capabilities_overrides, organization_custom_instructions, provider, reasoning_effort, reasoning_summary, running_in_interactive_mode, sandbox_config, session_capabilities, session_limits, shell_init_profile, shell_process_flags, skill_directories, skip_custom_instructions, skip_embedding_retrieval, suppress_custom_agent_prompt, tool_filter_precedence, trajectory_file, verbosity, working_directory) + return SessionUpdateOptionsParams(additional_content_exclusion_policies, agent_context, allow_all_mcp_server_instructions, ask_user_disabled, available_tools, capi, client_name, coauthor_enabled, context_tier, continue_on_auto_mode, copilot_url, custom_agents_local_only, disabled_instruction_sources, disabled_skills, enable_file_hooks, enable_host_git_operations, enable_on_demand_instruction_discovery, enable_reasoning_summaries, enable_script_safety, enable_session_store, enable_skills, enable_streaming, env_value_mode, events_log_directory, events_log_includes_subagents, excluded_builtin_agents, excluded_tools, feature_flags, included_builtin_agents, installed_plugins, integration_id, is_experimental_mode, log_interactive_shells, lsp_client_name, manage_schedule_enabled, max_inline_binary_bytes, model, model_capabilities_overrides, organization_custom_instructions, provider, reasoning_effort, reasoning_summary, running_in_interactive_mode, sandbox_config, session_capabilities, session_limits, shell, shell_init_profile, shell_process_flags, skill_directories, skip_custom_instructions, skip_embedding_retrieval, suppress_custom_agent_prompt, tool_filter_precedence, trajectory_file, verbosity, working_directory) def to_dict(self) -> dict: result: dict = {} @@ -22970,6 +26703,8 @@ def to_dict(self) -> dict: result["envValueMode"] = from_union([lambda x: to_enum(MCPSetEnvValueModeDetails, x), from_none], self.env_value_mode) if self.events_log_directory is not None: result["eventsLogDirectory"] = from_union([from_str, from_none], self.events_log_directory) + if self.events_log_includes_subagents is not None: + result["eventsLogIncludesSubagents"] = from_union([from_bool, from_none], self.events_log_includes_subagents) if self.excluded_builtin_agents is not None: result["excludedBuiltinAgents"] = from_union([lambda x: from_list(from_str, x), from_none], self.excluded_builtin_agents) if self.excluded_tools is not None: @@ -23012,6 +26747,8 @@ def to_dict(self) -> dict: result["sessionCapabilities"] = from_union([lambda x: from_list(lambda x: to_enum(SessionCapability, x), x), from_none], self.session_capabilities) if self.session_limits is not None: result["sessionLimits"] = from_union([lambda x: to_class(SessionLimitsConfig, x), from_none], self.session_limits) + if self.shell is not None: + result["shell"] = from_union([lambda x: to_class(ShellOptions, x), from_none], self.shell) if self.shell_init_profile is not None: result["shellInitProfile"] = from_union([from_str, from_none], self.shell_init_profile) if self.shell_process_flags is not None: @@ -23048,14 +26785,34 @@ class UIElicitationRequest: @staticmethod def from_dict(obj: Any) -> 'UIElicitationRequest': assert isinstance(obj, dict) - message = from_str(obj.get("message")) - requested_schema = UIElicitationSchema.from_dict(obj.get("requestedSchema")) - return UIElicitationRequest(message, requested_schema) + message = from_str(obj.get("message")) + requested_schema = UIElicitationSchema.from_dict(obj.get("requestedSchema")) + return UIElicitationRequest(message, requested_schema) + + def to_dict(self) -> dict: + result: dict = {} + result["message"] = from_str(self.message) + result["requestedSchema"] = to_class(UIElicitationSchema, self.requested_schema) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class BuiltInModelCatalog: + """The running runtime's complete catalog of well-known built-in model IDs, including + supported models and additional IDs with built-in metadata. + """ + models: list[BuiltInModelCatalogEntry] + """Built-in model entries.""" + + @staticmethod + def from_dict(obj: Any) -> 'BuiltInModelCatalog': + assert isinstance(obj, dict) + models = from_list(BuiltInModelCatalogEntry.from_dict, obj.get("models")) + return BuiltInModelCatalog(models) def to_dict(self) -> dict: result: dict = {} - result["message"] = from_str(self.message) - result["requestedSchema"] = to_class(UIElicitationSchema, self.requested_schema) + result["models"] = from_list(lambda x: to_class(BuiltInModelCatalogEntry, x), self.models) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -24388,9 +28145,6 @@ class Model: billing: ModelBilling | None = None """Billing information""" - default_reasoning_effort: str | None = None - """Default reasoning effort level (only present if model supports reasoning effort)""" - model_picker_category: ModelPickerCategory | None = None """Model capability category for grouping in the model picker""" @@ -24410,12 +28164,11 @@ def from_dict(obj: Any) -> 'Model': id = from_str(obj.get("id")) name = from_str(obj.get("name")) billing = from_union([ModelBilling.from_dict, from_none], obj.get("billing")) - default_reasoning_effort = from_union([from_str, from_none], obj.get("defaultReasoningEffort")) model_picker_category = from_union([ModelPickerCategory, from_none], obj.get("modelPickerCategory")) model_picker_price_category = from_union([ModelPickerPriceCategory, from_none], obj.get("modelPickerPriceCategory")) policy = from_union([ModelPolicy.from_dict, from_none], obj.get("policy")) supported_reasoning_efforts = from_union([lambda x: from_list(from_str, x), from_none], obj.get("supportedReasoningEfforts")) - return Model(capabilities, id, name, billing, default_reasoning_effort, model_picker_category, model_picker_price_category, policy, supported_reasoning_efforts) + return Model(capabilities, id, name, billing, model_picker_category, model_picker_price_category, policy, supported_reasoning_efforts) def to_dict(self) -> dict: result: dict = {} @@ -24424,8 +28177,6 @@ def to_dict(self) -> dict: result["name"] = from_str(self.name) if self.billing is not None: result["billing"] = from_union([lambda x: to_class(ModelBilling, x), from_none], self.billing) - if self.default_reasoning_effort is not None: - result["defaultReasoningEffort"] = from_union([from_str, from_none], self.default_reasoning_effort) if self.model_picker_category is not None: result["modelPickerCategory"] = from_union([lambda x: to_enum(ModelPickerCategory, x), from_none], self.model_picker_category) if self.model_picker_price_category is not None: @@ -24471,12 +28222,21 @@ class ModelSwitchToRequest: """Explicit context tier for the selected model. `"default"` / `"long_context"` apply the requested tier; omit this field to use normal model behavior with no explicit tier. """ + defer_if_model_change_queued: bool | None = None + """When true, defer this switch (enqueue it) if another model change is already queued, even + when no turn is active — so it drains last (FIFO) and wins over the already-queued + change. Intended for genuine user-initiated model selections; internal restore/reapply + switches omit it and apply immediately when no turn is active. When no other model change + is queued this has no effect (a switch still applies immediately unless a turn is active). + """ model_capabilities: ModelCapabilitiesOverride | None = None """Override individual model capabilities resolved by the runtime""" reasoning_effort: str | None = None - """Reasoning effort level to use for the model. "none" disables reasoning.""" - + """Reasoning effort level to use for the model. CAPI values are model-defined and validated + against the selected model; BYOK providers may define additional values. "none" disables + reasoning. When omitted, no effort override is applied. + """ reasoning_summary: ReasoningSummary | None = None """Reasoning summary mode to request for supported model clients""" @@ -24488,17 +28248,20 @@ def from_dict(obj: Any) -> 'ModelSwitchToRequest': assert isinstance(obj, dict) model_id = from_str(obj.get("modelId")) context_tier = from_union([ContextTier, from_none], obj.get("contextTier")) + defer_if_model_change_queued = from_union([from_bool, from_none], obj.get("deferIfModelChangeQueued")) model_capabilities = from_union([ModelCapabilitiesOverride.from_dict, from_none], obj.get("modelCapabilities")) reasoning_effort = from_union([from_str, from_none], obj.get("reasoningEffort")) reasoning_summary = from_union([ReasoningSummary, from_none], obj.get("reasoningSummary")) verbosity = from_union([Verbosity, from_none], obj.get("verbosity")) - return ModelSwitchToRequest(model_id, context_tier, model_capabilities, reasoning_effort, reasoning_summary, verbosity) + return ModelSwitchToRequest(model_id, context_tier, defer_if_model_change_queued, model_capabilities, reasoning_effort, reasoning_summary, verbosity) def to_dict(self) -> dict: result: dict = {} result["modelId"] = from_str(self.model_id) if self.context_tier is not None: result["contextTier"] = from_union([lambda x: to_enum(ContextTier, x), from_none], self.context_tier) + if self.defer_if_model_change_queued is not None: + result["deferIfModelChangeQueued"] = from_union([from_bool, from_none], self.defer_if_model_change_queued) if self.model_capabilities is not None: result["modelCapabilities"] = from_union([lambda x: to_class(ModelCapabilitiesOverride, x), from_none], self.model_capabilities) if self.reasoning_effort is not None: @@ -24533,7 +28296,8 @@ class PermissionsSetAllowAllRequest: """ model: str | None = None """Optional model id for the `auto` mode auto-approval LLM judging. Only meaningful when - `mode` is `auto`; ignored otherwise. When omitted, the session's active model is used. + `mode` is `auto`; ignored otherwise. When omitted, the session resolves a default judge + model: `gpt-5.5` for CAPI sessions and the session's active model for BYOK sessions. """ source: PermissionsSetAAllSource | None = None """Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers.""" @@ -24585,47 +28349,137 @@ def to_dict(self) -> dict: return result # Experimental: this type is part of an experimental API and may change or be removed. +# Internal: this type is an internal SDK API and is not part of the public surface. @dataclass -class ProviderGetEndpointRequest: - """Optional model identifier to scope the endpoint snapshot to.""" +class _RegisterExtensionToolsResult: + """Handle for releasing the extension tool registration.""" - model_id: str | None = None - """Model identifier the caller intends to use against the returned endpoint. Used to pick - the correct wire shape. Omit to use whichever model the session is currently using. + unsubscribe: Any + """In-process unsubscribe function (CLI-only optimization). Marked internal: replaced by an + explicit `extensions.unregister` RPC in the SDK migration. """ @staticmethod - def from_dict(obj: Any) -> 'ProviderGetEndpointRequest': + def from_dict(obj: Any) -> '_RegisterExtensionToolsResult': assert isinstance(obj, dict) - model_id = from_union([from_str, from_none], obj.get("modelId")) - return ProviderGetEndpointRequest(model_id) + unsubscribe = obj.get("unsubscribe") + return _RegisterExtensionToolsResult(unsubscribe) def to_dict(self) -> dict: result: dict = {} - if self.model_id is not None: - result["modelId"] = from_union([from_str, from_none], self.model_id) + result["unsubscribe"] = self.unsubscribe return result # Experimental: this type is part of an experimental API and may change or be removed. -# Internal: this type is an internal SDK API and is not part of the public surface. @dataclass -class _RegisterExtensionToolsResult: - """Handle for releasing the extension tool registration.""" +class SessionLimitPredictionDetails: + """Explainable AI-credit session-limit prediction. - unsubscribe: Any - """In-process unsubscribe function (CLI-only optimization). Marked internal: replaced by an - explicit `extensions.unregister` RPC in the SDK migration. + Predicted session limit details. """ + baseline_data: SessionLimitPredictionBaselineData + """Baseline data provenance.""" + + client_type: SessionLimitPredictionClientType + """Client population used for the prediction.""" + + model_id: str + """Model identifier used for lookup.""" + + recommended_cap: float + """Recommended maximum AI credits for this session.""" + + recommended_tier: SessionLimitPredictionTier + """Tier chosen as the recommended cap.""" + + source: SessionLimitPredictionSource + """Baseline fallback level used to create the prediction.""" + + source_key: str + """Key matched at the source level, such as a model id, family id, or `global`.""" + + tiers: list[SessionLimitPredictionTierOption] + """Ordered usage tiers and their AI-credit caps.""" + + family: str | None = None + """Resolved model family when known.""" @staticmethod - def from_dict(obj: Any) -> '_RegisterExtensionToolsResult': + def from_dict(obj: Any) -> 'SessionLimitPredictionDetails': assert isinstance(obj, dict) - unsubscribe = obj.get("unsubscribe") - return _RegisterExtensionToolsResult(unsubscribe) + baseline_data = SessionLimitPredictionBaselineData.from_dict(obj.get("baselineData")) + client_type = SessionLimitPredictionClientType(obj.get("clientType")) + model_id = from_str(obj.get("modelId")) + recommended_cap = from_float(obj.get("recommendedCap")) + recommended_tier = SessionLimitPredictionTier(obj.get("recommendedTier")) + source = SessionLimitPredictionSource(obj.get("source")) + source_key = from_str(obj.get("sourceKey")) + tiers = from_list(SessionLimitPredictionTierOption.from_dict, obj.get("tiers")) + family = from_union([from_str, from_none], obj.get("family")) + return SessionLimitPredictionDetails(baseline_data, client_type, model_id, recommended_cap, recommended_tier, source, source_key, tiers, family) def to_dict(self) -> dict: result: dict = {} - result["unsubscribe"] = self.unsubscribe + result["baselineData"] = to_class(SessionLimitPredictionBaselineData, self.baseline_data) + result["clientType"] = to_enum(SessionLimitPredictionClientType, self.client_type) + result["modelId"] = from_str(self.model_id) + result["recommendedCap"] = to_float(self.recommended_cap) + result["recommendedTier"] = to_enum(SessionLimitPredictionTier, self.recommended_tier) + result["source"] = to_enum(SessionLimitPredictionSource, self.source) + result["sourceKey"] = from_str(self.source_key) + result["tiers"] = from_list(lambda x: to_class(SessionLimitPredictionTierOption, x), self.tiers) + if self.family is not None: + result["family"] = from_union([from_str, from_none], self.family) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionLimitPredictionResult: + """Prediction result. Available results include prediction details; unavailable results + include an explicit reason. + """ + kind: SessionLimitPredictionResultKind + prediction: SessionLimitPredictionDetails | None = None + """Predicted session limit details.""" + + reason: SessionLimitPredictionUnavailableReason | None = None + """Reason no prediction is available.""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionLimitPredictionResult': + assert isinstance(obj, dict) + kind = SessionLimitPredictionResultKind(obj.get("kind")) + prediction = from_union([SessionLimitPredictionDetails.from_dict, from_none], obj.get("prediction")) + reason = from_union([SessionLimitPredictionUnavailableReason, from_none], obj.get("reason")) + return SessionLimitPredictionResult(kind, prediction, reason) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = to_enum(SessionLimitPredictionResultKind, self.kind) + if self.prediction is not None: + result["prediction"] = from_union([lambda x: to_class(SessionLimitPredictionDetails, x), from_none], self.prediction) + if self.reason is not None: + result["reason"] = from_union([lambda x: to_enum(SessionLimitPredictionUnavailableReason, x), from_none], self.reason) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionProviderGetEndpointRequest: + model_id: str | None = None + """Model identifier the caller intends to use against the returned endpoint. Used to pick + the correct wire shape. Omit to use whichever model the session is currently using. + """ + + @staticmethod + def from_dict(obj: Any) -> 'SessionProviderGetEndpointRequest': + assert isinstance(obj, dict) + model_id = from_union([from_str, from_none], obj.get("modelId")) + return SessionProviderGetEndpointRequest(model_id) + + def to_dict(self) -> dict: + result: dict = {} + if self.model_id is not None: + result["modelId"] = from_union([from_str, from_none], self.model_id) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -24985,6 +28839,7 @@ class RPC: agent_info: AgentInfo agent_info_source: AgentInfoSource agent_list: AgentList + agent_list_request: Any agent_registry_live_target_entry: AgentRegistryLiveTargetEntry agent_registry_live_target_entry_attention_kind: AgentRegistryLiveTargetEntryAttentionKind agent_registry_live_target_entry_kind: AgentRegistryLiveTargetEntryKind @@ -25005,12 +28860,15 @@ class RPC: agents_discover_request: AgentsDiscoverRequest agent_select_request: AgentSelectRequest agent_select_result: AgentSelectResult + agent_set_prompt_request: AgentSetPromptRequest agents_get_discovery_paths_request: AgentsGetDiscoveryPathsRequest allow_all_permission_set_result: AllowAllPermissionSetResult allow_all_permission_state: AllowAllPermissionState api_key_auth_info: APIKeyAuthInfo auth_info: AuthInfo auth_info_type: AuthInfoType + built_in_model_catalog: BuiltInModelCatalog + built_in_model_catalog_entry: BuiltInModelCatalogEntry cancel_user_requested_shell_command_result: CancelUserRequestedShellCommandResult canvas_action: CanvasAction canvas_action_invoke_request: CanvasActionInvokeRequest @@ -25032,7 +28890,7 @@ class RPC: commands_handle_pending_command_request: CommandsHandlePendingCommandRequest commands_handle_pending_command_result: CommandsHandlePendingCommandResult commands_invoke_request: CommandsInvokeRequest - commands_list_request: CommandsListRequest + commands_list_request: Any commands_respond_to_queued_command_request: CommandsRespondToQueuedCommandRequest commands_respond_to_queued_command_result: CommandsRespondToQueuedCommandResult completions_get_trigger_characters_result: CompletionsGetTriggerCharactersResult @@ -25045,6 +28903,9 @@ class RPC: connect_remote_session_params: ConnectRemoteSessionParams connect_request: _ConnectRequest connect_result: _ConnectResult + content_exclusion_check_paths_request: ContentExclusionCheckPathsRequest + content_exclusion_check_paths_result: ContentExclusionCheckPathsResult + content_exclusion_path_check: ContentExclusionPathCheck content_filter_mode: ContentFilterMode context_heaviest_message: ContextHeaviestMessage copilot_api_token_auth_info: CopilotAPITokenAuthInfo @@ -25067,7 +28928,15 @@ class RPC: debug_collect_logs_result_kind: DebugCollectLogsResultKind debug_collect_logs_skipped_entry: DebugCollectLogsSkippedEntry debug_collect_logs_source: DebugCollectLogsSource + disable_bypass_permissions_mode: DisableBypassPermissionsMode discovered_canvas: DiscoveredCanvas + discovered_extension: DiscoveredExtension + discovered_extension_mode: DiscoveredExtensionMode + discovered_extension_plugin: DiscoveredExtensionPlugin + discovered_extensions: DiscoveredExtensions + discovered_extensions_disable_request: DiscoveredExtensionsDisableRequest + discovered_extensions_enable_request: DiscoveredExtensionsEnableRequest + discovered_extension_source: DiscoveredExtensionSource discovered_mcp_server: DiscoveredMCPServer discovered_mcp_server_type: DiscoveredMCPServerType enqueue_command_params: EnqueueCommandParams @@ -25079,11 +28948,15 @@ class RPC: event_log_types: list[str] | EventLogTypes events_agent_scope: EventsAgentScope events_cursor_status: EventsCursorStatus + events_read_direction: EventsReadDirection events_read_result: EventsReadResult execute_command_params: ExecuteCommandParams execute_command_result: ExecuteCommandResult extension: Extension extension_context_push_input: ExtensionContextPushInput + extension_launch_profile: ExtensionLaunchProfile + extension_launch_provider_resolve_request: ExtensionLaunchProviderResolveRequest + extension_launch_provider_resolve_result: ExtensionLaunchProviderResolveResult extension_list: ExtensionList extensions_disable_request: ExtensionsDisableRequest extensions_enable_request: ExtensionsEnableRequest @@ -25109,22 +28982,39 @@ class RPC: factory_agent_options: FactoryAgentOptions factory_agent_request: FactoryAgentRequest factory_agent_result: FactoryAgentResult + factory_agent_summary: FactoryAgentSummary factory_cancel_request: FactoryCancelRequest + factory_current_phase: FactoryCurrentPhase + factory_declared_limits: FactoryDeclaredLimits + factory_durable_operation: FactoryDurableOperation factory_execute_request: FactoryExecuteRequest factory_execute_result: FactoryExecuteResult + factory_get_run_progress_request: FactoryGetRunProgressRequest factory_get_run_request: FactoryGetRunRequest factory_journal_get_request: FactoryJournalGetRequest factory_journal_get_result: FactoryJournalGetResult factory_journal_put_request: FactoryJournalPutRequest + factory_list_runs_request: FactoryListRunsRequest + factory_list_runs_result: FactoryListRunsResult factory_log_line: FactoryLogLine factory_log_line_kind: FactoryLogLineKind factory_log_request: FactoryLogRequest + factory_phase_observation: FactoryPhaseObservation + factory_phase_status: FactoryPhaseStatus + factory_progress_line: FactoryProgressLine + factory_progress_page: FactoryProgressPage + factory_resume_request: FactoryResumeRequest + factory_resume_result: FactoryResumeResult + factory_run_consumed: FactoryRunConsumed + factory_run_detail: FactoryRunDetail factory_run_failure: FactoryRunFailure factory_run_failure_kind: FactoryRunFailureKind factory_run_limits: FactoryRunLimits factory_run_request: FactoryRunRequest factory_run_result: FactoryRunResult factory_run_status: FactoryRunStatus + factory_run_summary: FactoryRunSummary + factory_run_terminal: FactoryRunTerminal filter_mapping: dict[str, ContentFilterMode] | ContentFilterMode fleet_start_request: FleetStartRequest fleet_start_result: FleetStartResult @@ -25139,9 +29029,24 @@ class RPC: handle_pending_tool_call_result: HandlePendingToolCallResult history_abort_manual_compaction_result: HistoryAbortManualCompactionResult history_cancel_background_compaction_result: HistoryCancelBackgroundCompactionResult + history_clear_context_request: HistoryClearContextRequest + history_clear_context_result: HistoryClearContextResult history_compact_context_window: HistoryCompactContextWindow - history_compact_request: HistoryCompactRequest + history_compact_request: Any history_compact_result: HistoryCompactResult + history_file_restore_skip_reason: HistoryFileRestoreSkipReason + history_list_rewind_points_result: HistoryListRewindPointsResult + history_preview_rewind_request: HistoryPreviewRewindRequest + history_preview_rewind_result: HistoryPreviewRewindResult + history_rewind_change_type: HistoryRewindChangeType + history_rewind_file_preview: HistoryRewindFilePreview + history_rewind_mode: HistoryRewindMode + history_rewind_outcome: HistoryRewindOutcome + history_rewind_point: HistoryRewindPoint + history_rewind_request: HistoryRewindRequest + history_rewind_result: HistoryRewindResult + history_rewind_unavailable_reason: HistoryRewindUnavailableReason + history_skipped_file_restore: HistorySkippedFileRestore history_summarize_for_handoff_result: HistorySummarizeForHandoffResult history_truncate_request: HistoryTruncateRequest history_truncate_result: HistoryTruncateResult @@ -25165,6 +29070,8 @@ class RPC: instruction_source: InstructionSource instruction_source_location: InstructionLocation instruction_source_type: InstructionSourceType + interrupt_main_turn_request: InterruptMainTurnRequest + interrupt_main_turn_result: InterruptMainTurnResult llm_inference_headers: dict[str, list[str]] llm_inference_http_request_chunk_request: LlmInferenceHTTPRequestChunkRequest llm_inference_http_request_chunk_result: LlmInferenceHTTPRequestChunkResult @@ -25181,6 +29088,7 @@ class RPC: log_request: LogRequest log_result: LogResult lsp_initialize_request: LspInitializeRequest + managed_settings_read_result: ManagedSettingsReadResult marketplace_add_result: MarketplaceAddResult marketplace_browse_result: MarketplaceBrowseResult marketplace_info: MarketplaceInfo @@ -25238,12 +29146,15 @@ class RPC: mcp_is_server_running_result: MCPIsServerRunningResult mcp_list_tools_request: MCPListToolsRequest mcp_list_tools_result: MCPListToolsResult + mcp_oauth_authentication_state_changed_request: MCPOauthAuthenticationStateChangedRequest mcp_oauth_handle_pending_request: MCPOauthHandlePendingRequest mcp_oauth_handle_pending_result: MCPOauthHandlePendingResult mcp_oauth_login_grant_type: MCPGrantType mcp_oauth_login_request: MCPOauthLoginRequest mcp_oauth_login_result: MCPOauthLoginResult mcp_oauth_pending_request_response: MCPOauthPendingRequestResponse + mcp_oauth_respond_request: MCPOauthRespondRequest + mcp_oauth_respond_result: MCPOauthRespondResult mcp_register_external_client_request: MCPRegisterExternalClientRequest mcp_reload_with_config_request: MCPReloadWithConfigRequest mcp_remove_git_hub_result: MCPRemoveGitHubResult @@ -25314,7 +29225,7 @@ class RPC: model_capabilities_override_supports: ModelCapabilitiesOverrideSupports model_capabilities_supports: ModelCapabilitiesSupports model_list: ModelList - model_list_request: ModelListRequest + model_list_request: Any model_picker_category: ModelPickerCategory model_picker_price_category: ModelPickerPriceCategory model_policy: ModelPolicy @@ -25351,6 +29262,7 @@ class RPC: permission_decision_approve_for_location_approval_custom_tool: PermissionDecisionApproveForLocationApprovalCustomTool permission_decision_approve_for_location_approval_extension_management: PermissionDecisionApproveForLocationApprovalExtensionManagement permission_decision_approve_for_location_approval_extension_permission_access: PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess + permission_decision_approve_for_location_approval_factory: PermissionDecisionApproveForLocationApprovalFactory permission_decision_approve_for_location_approval_mcp: PermissionDecisionApproveForLocationApprovalMCP permission_decision_approve_for_location_approval_mcp_sampling: PermissionDecisionApproveForLocationApprovalMCPSampling permission_decision_approve_for_location_approval_memory: PermissionDecisionApproveForLocationApprovalMemory @@ -25362,6 +29274,7 @@ class RPC: permission_decision_approve_for_session_approval_custom_tool: PermissionDecisionApproveForSessionApprovalCustomTool permission_decision_approve_for_session_approval_extension_management: PermissionDecisionApproveForSessionApprovalExtensionManagement permission_decision_approve_for_session_approval_extension_permission_access: PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess + permission_decision_approve_for_session_approval_factory: PermissionDecisionApproveForSessionApprovalFactory permission_decision_approve_for_session_approval_mcp: PermissionDecisionApproveForSessionApprovalMCP permission_decision_approve_for_session_approval_mcp_sampling: PermissionDecisionApproveForSessionApprovalMCPSampling permission_decision_approve_for_session_approval_memory: PermissionDecisionApproveForSessionApprovalMemory @@ -25370,13 +29283,17 @@ class RPC: permission_decision_approve_once: PermissionDecisionApproveOnce permission_decision_approve_permanently: PermissionDecisionApprovePermanently permission_decision_cancelled: PermissionDecisionCancelled + permission_decision_context: PermissionDecisionContext permission_decision_denied_by_content_exclusion_policy: PermissionDecisionDeniedByContentExclusionPolicy permission_decision_denied_by_permission_request_hook: PermissionDecisionDeniedByPermissionRequestHook permission_decision_denied_by_rules: PermissionDecisionDeniedByRules permission_decision_denied_interactively_by_user: PermissionDecisionDeniedInteractivelyByUser permission_decision_denied_no_approval_rule_and_could_not_request_from_user: PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser + permission_decision_outcome: PermissionDecisionOutcome permission_decision_reject: PermissionDecisionReject permission_decision_request: PermissionDecisionRequest + permission_decision_source: PermissionDecisionSource + permission_decision_surface: PermissionDecisionSurface permission_decision_user_not_available: PermissionDecisionUserNotAvailable permission_location_add_tool_approval_params: PermissionLocationAddToolApprovalParams permission_location_apply_params: PermissionLocationApplyParams @@ -25409,6 +29326,7 @@ class RPC: permissions_locations_add_tool_approval_details_custom_tool: PermissionsLocationsAddToolApprovalDetailsCustomTool permissions_locations_add_tool_approval_details_extension_management: PermissionsLocationsAddToolApprovalDetailsExtensionManagement permissions_locations_add_tool_approval_details_extension_permission_access: PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess + permissions_locations_add_tool_approval_details_factory: PermissionsLocationsAddToolApprovalDetailsFactory permissions_locations_add_tool_approval_details_mcp: PermissionsLocationsAddToolApprovalDetailsMCP permissions_locations_add_tool_approval_details_mcp_sampling: PermissionsLocationsAddToolApprovalDetailsMCPSampling permissions_locations_add_tool_approval_details_memory: PermissionsLocationsAddToolApprovalDetailsMemory @@ -25454,7 +29372,7 @@ class RPC: plugins_marketplaces_browse_request: PluginsMarketplacesBrowseRequest plugins_marketplaces_refresh_request: PluginsMarketplacesRefreshRequest plugins_marketplaces_remove_request: PluginsMarketplacesRemoveRequest - plugins_reload_request: PluginsReloadRequest + plugins_reload_request: Any plugins_uninstall_request: PluginsUninstallRequest plugins_update_request: PluginsUpdateRequest plugin_update_all_entry: PluginUpdateAllEntry @@ -25471,7 +29389,7 @@ class RPC: provider_endpoint_transport: ProviderTransport provider_endpoint_type: ProviderType provider_endpoint_wire_api: ProviderWireAPI - provider_get_endpoint_request: ProviderGetEndpointRequest + provider_get_endpoint_request: Any provider_model_config: ProviderModelConfig provider_session_token: ProviderSessionToken provider_token_acquire_request: ProviderTokenAcquireRequest @@ -25499,13 +29417,36 @@ class RPC: push_attachment_selection_details_end: PushAttachmentSelectionDetailsEnd push_attachment_selection_details_start: PushAttachmentSelectionDetailsStart push_git_hub_repo_ref: PushGitHubRepoRef + queue_begin_deferred_idle_drain_request: QueueBeginDeferredIdleDrainRequest + queue_begin_deferred_idle_drain_result: QueueBeginDeferredIdleDrainResult + queue_consume_system_notifications_request: QueueConsumeSystemNotificationsRequest queued_command_handled: QueuedCommandHandled queued_command_not_handled: QueuedCommandNotHandled queued_command_result: QueuedCommandResult + queue_defer_session_idle_request: QueueDeferSessionIdleRequest + queue_duplicate_at_request: QueueDuplicateAtRequest + queue_duplicate_at_result: QueueDuplicateAtResult + queue_enqueue_resume_pending_result: QueueEnqueueResumePendingResult + queue_finish_deferred_idle_drain_request: QueueFinishDeferredIdleDrainRequest + queue_finish_deferred_idle_drain_result: QueueFinishDeferredIdleDrainResult + queue_has_pending_result: QueueHasPendingResult + queue_insert_at_request: QueueInsertAtRequest + queue_insert_at_result: QueueInsertAtResult + queue_insert_message: QueueInsertMessage + queue_move_item_request: QueueMoveItemRequest + queue_move_item_result: QueueMoveItemResult queue_pending_items: QueuePendingItems queue_pending_items_kind: QueuePendingItemsKind queue_pending_items_result: QueuePendingItemsResult + queue_remove_at_request: QueueRemoveAtRequest + queue_remove_at_result: QueueRemoveAtResult queue_remove_most_recent_result: QueueRemoveMostRecentResult + queue_send_now_request: QueueSendNowRequest + queue_send_now_result: QueueSendNowResult + queue_set_drain_paused_request: QueueSetDrainPausedRequest + queue_snapshot_result: QueueSnapshotResult + queue_update_text_request: QueueUpdateTextRequest + queue_update_text_result: QueueUpdateTextResult register_event_interest_params: RegisterEventInterestParams register_event_interest_result: RegisterEventInterestResult register_extension_tools_params: _RegisterExtensionToolsParams @@ -25533,14 +29474,23 @@ class RPC: remote_session_repository: RemoteSessionRepository run_options: RunOptions sandbox_config: SandboxConfig + sandbox_config_auth: SandboxConfigAuth sandbox_config_user_policy: SandboxConfigUserPolicy sandbox_config_user_policy_experimental: SandboxConfigUserPolicyExperimental sandbox_config_user_policy_experimental_seatbelt: SandboxConfigUserPolicyExperimentalSeatbelt sandbox_config_user_policy_filesystem: SandboxConfigUserPolicyFilesystem sandbox_config_user_policy_network: SandboxConfigUserPolicyNetwork + sandbox_config_user_policy_network_proxy: SandboxConfigUserPolicyNetworkProxy sandbox_config_user_policy_seatbelt: SandboxConfigUserPolicySeatbelt + schedule_add_at_request: ScheduleAddAtRequest + schedule_add_cron_request: ScheduleAddCronRequest + schedule_add_request: ScheduleAddRequest + schedule_add_result: ScheduleAddResult + schedule_add_self_paced_request: ScheduleAddSelfPacedRequest schedule_entry: ScheduleEntry + schedule_has_self_paced_result: ScheduleHasSelfPacedResult schedule_list: ScheduleList + schedule_rearm_self_paced_request: ScheduleRearmSelfPacedRequest schedule_stop_request: ScheduleStopRequest schedule_stop_result: ScheduleStopResult secrets_add_filter_values_request: SecretsAddFilterValuesRequest @@ -25553,14 +29503,18 @@ class RPC: send_mode: SendMode send_request: SendRequest send_result: SendResult + send_system_notification_request: SendSystemNotificationRequest server_agent_list: ServerAgentList server_instruction_source_list: ServerInstructionSourceList server_skill: ServerSkill server_skill_list: ServerSkillList session_activity: SessionActivity + session_agent_list_request: SessionAgentListRequest session_auth_status: SessionAuthStatus session_bulk_delete_result: SessionBulkDeleteResult + session_cancel_all_background_agents_result: int session_capability: SessionCapability + session_commands_list_request: SessionCommandsListRequest session_completion_item: SessionCompletionItem session_context: SessionContext session_context_host_type: HostType @@ -25590,23 +29544,42 @@ class RPC: session_fs_sqlite_query_request: SessionFSSqliteQueryRequest session_fs_sqlite_query_result: SessionFSSqliteQueryResult session_fs_sqlite_query_type: SessionFSSqliteQueryType + session_fs_sqlite_transaction_error: SessionFSSqliteTransactionError + session_fs_sqlite_transaction_error_class: SessionFSSqliteTransactionErrorClass + session_fs_sqlite_transaction_request: SessionFSSqliteTransactionRequest + session_fs_sqlite_transaction_result: SessionFSSqliteTransactionResult + session_fs_sqlite_transaction_statement: SessionFSSqliteTransactionStatement session_fs_stat_request: SessionFSStatRequest session_fs_stat_result: SessionFSStatResult session_fs_write_file_request: SessionFSWriteFileRequest + session_history_compact_request: SessionHistoryCompactRequest session_installed_plugin: SessionInstalledPlugin session_installed_plugin_source: SessionInstalledPluginSource | str session_installed_plugin_source_git_hub: SessionInstalledPluginSourceGitHub session_installed_plugin_source_local: SessionInstalledPluginSourceLocal session_installed_plugin_source_url: SessionInstalledPluginSourceURL + session_limit_prediction_baseline_data: SessionLimitPredictionBaselineData + session_limit_prediction_client_type: SessionLimitPredictionClientType + session_limit_prediction_details: SessionLimitPredictionDetails + session_limit_prediction_predict_request: SessionLimitPredictionPredictRequest + session_limit_prediction_request: Any + session_limit_prediction_result: SessionLimitPredictionResult + session_limit_prediction_source: SessionLimitPredictionSource + session_limit_prediction_tier: SessionLimitPredictionTier + session_limit_prediction_tier_option: SessionLimitPredictionTierOption + session_limit_prediction_unavailable_reason: SessionLimitPredictionUnavailableReason session_list: SessionList session_list_entry: SessionListEntry session_list_filter: SessionListFilter session_load_deferred_repo_hooks_result: SessionLoadDeferredRepoHooksResult session_log_level: SessionLogLevel + session_managed_permissions: SessionManagedPermissions + session_managed_settings: SessionManagedSettings session_mcp_apps_call_tool_result: dict[str, Any] session_metadata_snapshot: SessionMetadataSnapshot session_mode: SessionMode session_model_list: SessionModelList + session_model_list_request: SessionModelListRequest session_model_price_category: SessionModelPriceCategory session_open_options: SessionOpenOptions session_open_options_additional_content_exclusion_policy: SessionOpenOptionsAdditionalContentExclusionPolicy @@ -25617,12 +29590,15 @@ class RPC: session_open_options_reasoning_summary: ReasoningSummary session_open_params: SessionOpenParams session_open_result: SessionOpenResult + session_plugins_reload_request: SessionPluginsReloadRequest + session_provider_get_endpoint_request: SessionProviderGetEndpointRequest session_prune_result: SessionPruneResult sessions_bulk_delete_request: SessionsBulkDeleteRequest sessions_check_in_use_request: SessionsCheckInUseRequest sessions_check_in_use_result: SessionsCheckInUseResult sessions_close_request: SessionsCloseRequest sessions_close_result: SessionsCloseResult + sessions_delete_request: SessionsDeleteRequest sessions_enrich_metadata_request: SessionsEnrichMetadataRequest session_set_credentials_params: SessionSetCredentialsParams session_set_credentials_result: SessionSetCredentialsResult @@ -25648,9 +29624,13 @@ class RPC: sessions_get_event_file_path_result: SessionsGetEventFilePathResult sessions_get_last_for_context_request: SessionsGetLastForContextRequest sessions_get_last_for_context_result: SessionsGetLastForContextResult + sessions_get_metadata_request: SessionsGetMetadataRequest + sessions_get_metadata_result: SessionsGetMetadataResult sessions_get_persisted_remote_steerable_request: SessionsGetPersistedRemoteSteerableRequest sessions_get_persisted_remote_steerable_result: SessionsGetPersistedRemoteSteerableResult session_sizes: SessionSizes + sessions_list_non_empty_session_ids_request: SessionsListNonEmptySessionIDSRequest + sessions_list_non_empty_session_ids_result: SessionsListNonEmptySessionIDSResult sessions_list_request: SessionsListRequest sessions_load_deferred_repo_hooks_request: SessionsLoadDeferredRepoHooksRequest sessions_open_attach: SessionsOpenAttach @@ -25690,9 +29670,13 @@ class RPC: shell_exec_request: ShellExecRequest shell_exec_result: ShellExecResult shell_execute_user_requested_request: ShellExecuteUserRequestedRequest + shell_init_profile: ShellInitProfile + shell_init_script: ShellInitScript + shell_init_script_shell: ShellInitScriptShell shell_kill_request: ShellKillRequest shell_kill_result: ShellKillResult shell_kill_signal: ShellKillSignal + shell_options: ShellOptions shutdown_request: ShutdownRequest skill: Skill skill_discovery_path: SkillDiscoveryPath @@ -25815,20 +29799,30 @@ class RPC: workspace_diff_file_change_type: WorkspaceDiffFileChangeType workspace_diff_mode: WorkspaceDiffMode workspace_diff_result: WorkspaceDiffResult + workspaces_add_summary_request: WorkspacesAddSummaryRequest + workspaces_add_summary_result: WorkspacesAddSummaryResult + workspaces_autopilot_objective_exists_result: WorkspacesAutopilotObjectiveExistsResult workspaces_checkpoints: WorkspacesCheckpoints workspaces_create_file_request: WorkspacesCreateFileRequest + workspaces_delete_autopilot_objective_result: WorkspacesDeleteAutopilotObjectiveResult workspaces_diff_request: WorkspacesDiffRequest + workspaces_ensure_request: WorkspacesEnsureRequest workspaces_get_workspace_result: WorkspacesGetWorkspaceResult workspaces_list_checkpoints_result: WorkspacesListCheckpointsResult workspaces_list_files_result: WorkspacesListFilesResult + workspaces_read_autopilot_objective_result: WorkspacesReadAutopilotObjectiveResult workspaces_read_checkpoint_request: WorkspacesReadCheckpointRequest workspaces_read_checkpoint_result: WorkspacesReadCheckpointResult workspaces_read_file_request: WorkspacesReadFileRequest workspaces_read_file_result: WorkspacesReadFileResult workspaces_save_large_paste_request: WorkspacesSaveLargePasteRequest workspaces_save_large_paste_result: WorkspacesSaveLargePasteResult + workspaces_truncate_summaries_request: WorkspacesTruncateSummariesRequest workspace_summary_host_type: HostType + workspaces_update_metadata_request: WorkspacesUpdateMetadataRequest workspaces_workspace_details_host_type: HostType + workspaces_write_autopilot_objective_request: WorkspacesWriteAutopilotObjectiveRequest + workspaces_write_autopilot_objective_result: WorkspacesWriteAutopilotObjectiveResult session_context_attribution: SessionContextAttribution | None = None session_context_info: SessionContextInfo | None = None subagent_settings: SubagentSettings | None = None @@ -25858,6 +29852,7 @@ def from_dict(obj: Any) -> 'RPC': agent_info = AgentInfo.from_dict(obj.get("AgentInfo")) agent_info_source = AgentInfoSource(obj.get("AgentInfoSource")) agent_list = AgentList.from_dict(obj.get("AgentList")) + agent_list_request = obj.get("AgentListRequest") agent_registry_live_target_entry = AgentRegistryLiveTargetEntry.from_dict(obj.get("AgentRegistryLiveTargetEntry")) agent_registry_live_target_entry_attention_kind = AgentRegistryLiveTargetEntryAttentionKind(obj.get("AgentRegistryLiveTargetEntryAttentionKind")) agent_registry_live_target_entry_kind = AgentRegistryLiveTargetEntryKind(obj.get("AgentRegistryLiveTargetEntryKind")) @@ -25878,12 +29873,15 @@ def from_dict(obj: Any) -> 'RPC': agents_discover_request = AgentsDiscoverRequest.from_dict(obj.get("AgentsDiscoverRequest")) agent_select_request = AgentSelectRequest.from_dict(obj.get("AgentSelectRequest")) agent_select_result = AgentSelectResult.from_dict(obj.get("AgentSelectResult")) + agent_set_prompt_request = AgentSetPromptRequest.from_dict(obj.get("AgentSetPromptRequest")) agents_get_discovery_paths_request = AgentsGetDiscoveryPathsRequest.from_dict(obj.get("AgentsGetDiscoveryPathsRequest")) allow_all_permission_set_result = AllowAllPermissionSetResult.from_dict(obj.get("AllowAllPermissionSetResult")) allow_all_permission_state = AllowAllPermissionState.from_dict(obj.get("AllowAllPermissionState")) api_key_auth_info = APIKeyAuthInfo.from_dict(obj.get("ApiKeyAuthInfo")) auth_info = _load_AuthInfo(obj.get("AuthInfo")) auth_info_type = AuthInfoType(obj.get("AuthInfoType")) + built_in_model_catalog = BuiltInModelCatalog.from_dict(obj.get("BuiltInModelCatalog")) + built_in_model_catalog_entry = BuiltInModelCatalogEntry.from_dict(obj.get("BuiltInModelCatalogEntry")) cancel_user_requested_shell_command_result = CancelUserRequestedShellCommandResult.from_dict(obj.get("CancelUserRequestedShellCommandResult")) canvas_action = CanvasAction.from_dict(obj.get("CanvasAction")) canvas_action_invoke_request = CanvasActionInvokeRequest.from_dict(obj.get("CanvasActionInvokeRequest")) @@ -25905,7 +29903,7 @@ def from_dict(obj: Any) -> 'RPC': commands_handle_pending_command_request = CommandsHandlePendingCommandRequest.from_dict(obj.get("CommandsHandlePendingCommandRequest")) commands_handle_pending_command_result = CommandsHandlePendingCommandResult.from_dict(obj.get("CommandsHandlePendingCommandResult")) commands_invoke_request = CommandsInvokeRequest.from_dict(obj.get("CommandsInvokeRequest")) - commands_list_request = CommandsListRequest.from_dict(obj.get("CommandsListRequest")) + commands_list_request = obj.get("CommandsListRequest") commands_respond_to_queued_command_request = CommandsRespondToQueuedCommandRequest.from_dict(obj.get("CommandsRespondToQueuedCommandRequest")) commands_respond_to_queued_command_result = CommandsRespondToQueuedCommandResult.from_dict(obj.get("CommandsRespondToQueuedCommandResult")) completions_get_trigger_characters_result = CompletionsGetTriggerCharactersResult.from_dict(obj.get("CompletionsGetTriggerCharactersResult")) @@ -25918,6 +29916,9 @@ def from_dict(obj: Any) -> 'RPC': connect_remote_session_params = ConnectRemoteSessionParams.from_dict(obj.get("ConnectRemoteSessionParams")) connect_request = _ConnectRequest.from_dict(obj.get("ConnectRequest")) connect_result = _ConnectResult.from_dict(obj.get("ConnectResult")) + content_exclusion_check_paths_request = ContentExclusionCheckPathsRequest.from_dict(obj.get("ContentExclusionCheckPathsRequest")) + content_exclusion_check_paths_result = ContentExclusionCheckPathsResult.from_dict(obj.get("ContentExclusionCheckPathsResult")) + content_exclusion_path_check = ContentExclusionPathCheck.from_dict(obj.get("ContentExclusionPathCheck")) content_filter_mode = ContentFilterMode(obj.get("ContentFilterMode")) context_heaviest_message = ContextHeaviestMessage.from_dict(obj.get("ContextHeaviestMessage")) copilot_api_token_auth_info = CopilotAPITokenAuthInfo.from_dict(obj.get("CopilotApiTokenAuthInfo")) @@ -25940,7 +29941,15 @@ def from_dict(obj: Any) -> 'RPC': debug_collect_logs_result_kind = DebugCollectLogsResultKind(obj.get("DebugCollectLogsResultKind")) debug_collect_logs_skipped_entry = DebugCollectLogsSkippedEntry.from_dict(obj.get("DebugCollectLogsSkippedEntry")) debug_collect_logs_source = DebugCollectLogsSource(obj.get("DebugCollectLogsSource")) + disable_bypass_permissions_mode = DisableBypassPermissionsMode(obj.get("DisableBypassPermissionsMode")) discovered_canvas = DiscoveredCanvas.from_dict(obj.get("DiscoveredCanvas")) + discovered_extension = DiscoveredExtension.from_dict(obj.get("DiscoveredExtension")) + discovered_extension_mode = DiscoveredExtensionMode(obj.get("DiscoveredExtensionMode")) + discovered_extension_plugin = DiscoveredExtensionPlugin.from_dict(obj.get("DiscoveredExtensionPlugin")) + discovered_extensions = DiscoveredExtensions.from_dict(obj.get("DiscoveredExtensions")) + discovered_extensions_disable_request = DiscoveredExtensionsDisableRequest.from_dict(obj.get("DiscoveredExtensionsDisableRequest")) + discovered_extensions_enable_request = DiscoveredExtensionsEnableRequest.from_dict(obj.get("DiscoveredExtensionsEnableRequest")) + discovered_extension_source = DiscoveredExtensionSource(obj.get("DiscoveredExtensionSource")) discovered_mcp_server = DiscoveredMCPServer.from_dict(obj.get("DiscoveredMcpServer")) discovered_mcp_server_type = DiscoveredMCPServerType(obj.get("DiscoveredMcpServerType")) enqueue_command_params = EnqueueCommandParams.from_dict(obj.get("EnqueueCommandParams")) @@ -25952,11 +29961,15 @@ def from_dict(obj: Any) -> 'RPC': event_log_types = from_union([lambda x: from_list(from_str, x), EventLogTypes], obj.get("EventLogTypes")) events_agent_scope = EventsAgentScope(obj.get("EventsAgentScope")) events_cursor_status = EventsCursorStatus(obj.get("EventsCursorStatus")) + events_read_direction = EventsReadDirection(obj.get("EventsReadDirection")) events_read_result = EventsReadResult.from_dict(obj.get("EventsReadResult")) execute_command_params = ExecuteCommandParams.from_dict(obj.get("ExecuteCommandParams")) execute_command_result = ExecuteCommandResult.from_dict(obj.get("ExecuteCommandResult")) extension = Extension.from_dict(obj.get("Extension")) extension_context_push_input = ExtensionContextPushInput.from_dict(obj.get("ExtensionContextPushInput")) + extension_launch_profile = ExtensionLaunchProfile.from_dict(obj.get("ExtensionLaunchProfile")) + extension_launch_provider_resolve_request = ExtensionLaunchProviderResolveRequest.from_dict(obj.get("ExtensionLaunchProviderResolveRequest")) + extension_launch_provider_resolve_result = ExtensionLaunchProviderResolveResult.from_dict(obj.get("ExtensionLaunchProviderResolveResult")) extension_list = ExtensionList.from_dict(obj.get("ExtensionList")) extensions_disable_request = ExtensionsDisableRequest.from_dict(obj.get("ExtensionsDisableRequest")) extensions_enable_request = ExtensionsEnableRequest.from_dict(obj.get("ExtensionsEnableRequest")) @@ -25982,22 +29995,39 @@ def from_dict(obj: Any) -> 'RPC': factory_agent_options = FactoryAgentOptions.from_dict(obj.get("FactoryAgentOptions")) factory_agent_request = FactoryAgentRequest.from_dict(obj.get("FactoryAgentRequest")) factory_agent_result = FactoryAgentResult.from_dict(obj.get("FactoryAgentResult")) + factory_agent_summary = FactoryAgentSummary.from_dict(obj.get("FactoryAgentSummary")) factory_cancel_request = FactoryCancelRequest.from_dict(obj.get("FactoryCancelRequest")) + factory_current_phase = FactoryCurrentPhase.from_dict(obj.get("FactoryCurrentPhase")) + factory_declared_limits = FactoryDeclaredLimits.from_dict(obj.get("FactoryDeclaredLimits")) + factory_durable_operation = FactoryDurableOperation(obj.get("FactoryDurableOperation")) factory_execute_request = FactoryExecuteRequest.from_dict(obj.get("FactoryExecuteRequest")) factory_execute_result = FactoryExecuteResult.from_dict(obj.get("FactoryExecuteResult")) + factory_get_run_progress_request = FactoryGetRunProgressRequest.from_dict(obj.get("FactoryGetRunProgressRequest")) factory_get_run_request = FactoryGetRunRequest.from_dict(obj.get("FactoryGetRunRequest")) factory_journal_get_request = FactoryJournalGetRequest.from_dict(obj.get("FactoryJournalGetRequest")) factory_journal_get_result = FactoryJournalGetResult.from_dict(obj.get("FactoryJournalGetResult")) factory_journal_put_request = FactoryJournalPutRequest.from_dict(obj.get("FactoryJournalPutRequest")) + factory_list_runs_request = FactoryListRunsRequest.from_dict(obj.get("FactoryListRunsRequest")) + factory_list_runs_result = FactoryListRunsResult.from_dict(obj.get("FactoryListRunsResult")) factory_log_line = FactoryLogLine.from_dict(obj.get("FactoryLogLine")) factory_log_line_kind = FactoryLogLineKind(obj.get("FactoryLogLineKind")) factory_log_request = FactoryLogRequest.from_dict(obj.get("FactoryLogRequest")) + factory_phase_observation = FactoryPhaseObservation.from_dict(obj.get("FactoryPhaseObservation")) + factory_phase_status = FactoryPhaseStatus(obj.get("FactoryPhaseStatus")) + factory_progress_line = FactoryProgressLine.from_dict(obj.get("FactoryProgressLine")) + factory_progress_page = FactoryProgressPage.from_dict(obj.get("FactoryProgressPage")) + factory_resume_request = FactoryResumeRequest.from_dict(obj.get("FactoryResumeRequest")) + factory_resume_result = FactoryResumeResult.from_dict(obj.get("FactoryResumeResult")) + factory_run_consumed = FactoryRunConsumed.from_dict(obj.get("FactoryRunConsumed")) + factory_run_detail = FactoryRunDetail.from_dict(obj.get("FactoryRunDetail")) factory_run_failure = FactoryRunFailure.from_dict(obj.get("FactoryRunFailure")) factory_run_failure_kind = FactoryRunFailureKind(obj.get("FactoryRunFailureKind")) factory_run_limits = FactoryRunLimits.from_dict(obj.get("FactoryRunLimits")) factory_run_request = FactoryRunRequest.from_dict(obj.get("FactoryRunRequest")) factory_run_result = FactoryRunResult.from_dict(obj.get("FactoryRunResult")) factory_run_status = FactoryRunStatus(obj.get("FactoryRunStatus")) + factory_run_summary = FactoryRunSummary.from_dict(obj.get("FactoryRunSummary")) + factory_run_terminal = FactoryRunTerminal.from_dict(obj.get("FactoryRunTerminal")) filter_mapping = from_union([lambda x: from_dict(ContentFilterMode, x), ContentFilterMode], obj.get("FilterMapping")) fleet_start_request = FleetStartRequest.from_dict(obj.get("FleetStartRequest")) fleet_start_result = FleetStartResult.from_dict(obj.get("FleetStartResult")) @@ -26012,9 +30042,24 @@ def from_dict(obj: Any) -> 'RPC': handle_pending_tool_call_result = HandlePendingToolCallResult.from_dict(obj.get("HandlePendingToolCallResult")) history_abort_manual_compaction_result = HistoryAbortManualCompactionResult.from_dict(obj.get("HistoryAbortManualCompactionResult")) history_cancel_background_compaction_result = HistoryCancelBackgroundCompactionResult.from_dict(obj.get("HistoryCancelBackgroundCompactionResult")) + history_clear_context_request = HistoryClearContextRequest.from_dict(obj.get("HistoryClearContextRequest")) + history_clear_context_result = HistoryClearContextResult.from_dict(obj.get("HistoryClearContextResult")) history_compact_context_window = HistoryCompactContextWindow.from_dict(obj.get("HistoryCompactContextWindow")) - history_compact_request = HistoryCompactRequest.from_dict(obj.get("HistoryCompactRequest")) + history_compact_request = obj.get("HistoryCompactRequest") history_compact_result = HistoryCompactResult.from_dict(obj.get("HistoryCompactResult")) + history_file_restore_skip_reason = HistoryFileRestoreSkipReason(obj.get("HistoryFileRestoreSkipReason")) + history_list_rewind_points_result = HistoryListRewindPointsResult.from_dict(obj.get("HistoryListRewindPointsResult")) + history_preview_rewind_request = HistoryPreviewRewindRequest.from_dict(obj.get("HistoryPreviewRewindRequest")) + history_preview_rewind_result = HistoryPreviewRewindResult.from_dict(obj.get("HistoryPreviewRewindResult")) + history_rewind_change_type = HistoryRewindChangeType(obj.get("HistoryRewindChangeType")) + history_rewind_file_preview = HistoryRewindFilePreview.from_dict(obj.get("HistoryRewindFilePreview")) + history_rewind_mode = HistoryRewindMode(obj.get("HistoryRewindMode")) + history_rewind_outcome = HistoryRewindOutcome(obj.get("HistoryRewindOutcome")) + history_rewind_point = HistoryRewindPoint.from_dict(obj.get("HistoryRewindPoint")) + history_rewind_request = HistoryRewindRequest.from_dict(obj.get("HistoryRewindRequest")) + history_rewind_result = HistoryRewindResult.from_dict(obj.get("HistoryRewindResult")) + history_rewind_unavailable_reason = HistoryRewindUnavailableReason(obj.get("HistoryRewindUnavailableReason")) + history_skipped_file_restore = HistorySkippedFileRestore.from_dict(obj.get("HistorySkippedFileRestore")) history_summarize_for_handoff_result = HistorySummarizeForHandoffResult.from_dict(obj.get("HistorySummarizeForHandoffResult")) history_truncate_request = HistoryTruncateRequest.from_dict(obj.get("HistoryTruncateRequest")) history_truncate_result = HistoryTruncateResult.from_dict(obj.get("HistoryTruncateResult")) @@ -26038,6 +30083,8 @@ def from_dict(obj: Any) -> 'RPC': instruction_source = InstructionSource.from_dict(obj.get("InstructionSource")) instruction_source_location = InstructionLocation(obj.get("InstructionSourceLocation")) instruction_source_type = InstructionSourceType(obj.get("InstructionSourceType")) + interrupt_main_turn_request = InterruptMainTurnRequest.from_dict(obj.get("InterruptMainTurnRequest")) + interrupt_main_turn_result = InterruptMainTurnResult.from_dict(obj.get("InterruptMainTurnResult")) llm_inference_headers = from_dict(lambda x: from_list(from_str, x), obj.get("LlmInferenceHeaders")) llm_inference_http_request_chunk_request = LlmInferenceHTTPRequestChunkRequest.from_dict(obj.get("LlmInferenceHttpRequestChunkRequest")) llm_inference_http_request_chunk_result = LlmInferenceHTTPRequestChunkResult.from_dict(obj.get("LlmInferenceHttpRequestChunkResult")) @@ -26054,6 +30101,7 @@ def from_dict(obj: Any) -> 'RPC': log_request = LogRequest.from_dict(obj.get("LogRequest")) log_result = LogResult.from_dict(obj.get("LogResult")) lsp_initialize_request = LspInitializeRequest.from_dict(obj.get("LspInitializeRequest")) + managed_settings_read_result = ManagedSettingsReadResult.from_dict(obj.get("ManagedSettingsReadResult")) marketplace_add_result = MarketplaceAddResult.from_dict(obj.get("MarketplaceAddResult")) marketplace_browse_result = MarketplaceBrowseResult.from_dict(obj.get("MarketplaceBrowseResult")) marketplace_info = MarketplaceInfo.from_dict(obj.get("MarketplaceInfo")) @@ -26111,12 +30159,15 @@ def from_dict(obj: Any) -> 'RPC': mcp_is_server_running_result = MCPIsServerRunningResult.from_dict(obj.get("McpIsServerRunningResult")) mcp_list_tools_request = MCPListToolsRequest.from_dict(obj.get("McpListToolsRequest")) mcp_list_tools_result = MCPListToolsResult.from_dict(obj.get("McpListToolsResult")) + mcp_oauth_authentication_state_changed_request = MCPOauthAuthenticationStateChangedRequest.from_dict(obj.get("McpOauthAuthenticationStateChangedRequest")) mcp_oauth_handle_pending_request = MCPOauthHandlePendingRequest.from_dict(obj.get("McpOauthHandlePendingRequest")) mcp_oauth_handle_pending_result = MCPOauthHandlePendingResult.from_dict(obj.get("McpOauthHandlePendingResult")) mcp_oauth_login_grant_type = MCPGrantType(obj.get("McpOauthLoginGrantType")) mcp_oauth_login_request = MCPOauthLoginRequest.from_dict(obj.get("McpOauthLoginRequest")) mcp_oauth_login_result = MCPOauthLoginResult.from_dict(obj.get("McpOauthLoginResult")) mcp_oauth_pending_request_response = MCPOauthPendingRequestResponse.from_dict(obj.get("McpOauthPendingRequestResponse")) + mcp_oauth_respond_request = MCPOauthRespondRequest.from_dict(obj.get("McpOauthRespondRequest")) + mcp_oauth_respond_result = MCPOauthRespondResult.from_dict(obj.get("McpOauthRespondResult")) mcp_register_external_client_request = MCPRegisterExternalClientRequest.from_dict(obj.get("McpRegisterExternalClientRequest")) mcp_reload_with_config_request = MCPReloadWithConfigRequest.from_dict(obj.get("McpReloadWithConfigRequest")) mcp_remove_git_hub_result = MCPRemoveGitHubResult.from_dict(obj.get("McpRemoveGitHubResult")) @@ -26187,7 +30238,7 @@ def from_dict(obj: Any) -> 'RPC': model_capabilities_override_supports = ModelCapabilitiesOverrideSupports.from_dict(obj.get("ModelCapabilitiesOverrideSupports")) model_capabilities_supports = ModelCapabilitiesSupports.from_dict(obj.get("ModelCapabilitiesSupports")) model_list = ModelList.from_dict(obj.get("ModelList")) - model_list_request = ModelListRequest.from_dict(obj.get("ModelListRequest")) + model_list_request = obj.get("ModelListRequest") model_picker_category = ModelPickerCategory(obj.get("ModelPickerCategory")) model_picker_price_category = ModelPickerPriceCategory(obj.get("ModelPickerPriceCategory")) model_policy = ModelPolicy.from_dict(obj.get("ModelPolicy")) @@ -26224,6 +30275,7 @@ def from_dict(obj: Any) -> 'RPC': permission_decision_approve_for_location_approval_custom_tool = PermissionDecisionApproveForLocationApprovalCustomTool.from_dict(obj.get("PermissionDecisionApproveForLocationApprovalCustomTool")) permission_decision_approve_for_location_approval_extension_management = PermissionDecisionApproveForLocationApprovalExtensionManagement.from_dict(obj.get("PermissionDecisionApproveForLocationApprovalExtensionManagement")) permission_decision_approve_for_location_approval_extension_permission_access = PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess.from_dict(obj.get("PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess")) + permission_decision_approve_for_location_approval_factory = PermissionDecisionApproveForLocationApprovalFactory.from_dict(obj.get("PermissionDecisionApproveForLocationApprovalFactory")) permission_decision_approve_for_location_approval_mcp = PermissionDecisionApproveForLocationApprovalMCP.from_dict(obj.get("PermissionDecisionApproveForLocationApprovalMcp")) permission_decision_approve_for_location_approval_mcp_sampling = PermissionDecisionApproveForLocationApprovalMCPSampling.from_dict(obj.get("PermissionDecisionApproveForLocationApprovalMcpSampling")) permission_decision_approve_for_location_approval_memory = PermissionDecisionApproveForLocationApprovalMemory.from_dict(obj.get("PermissionDecisionApproveForLocationApprovalMemory")) @@ -26235,6 +30287,7 @@ def from_dict(obj: Any) -> 'RPC': permission_decision_approve_for_session_approval_custom_tool = PermissionDecisionApproveForSessionApprovalCustomTool.from_dict(obj.get("PermissionDecisionApproveForSessionApprovalCustomTool")) permission_decision_approve_for_session_approval_extension_management = PermissionDecisionApproveForSessionApprovalExtensionManagement.from_dict(obj.get("PermissionDecisionApproveForSessionApprovalExtensionManagement")) permission_decision_approve_for_session_approval_extension_permission_access = PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess.from_dict(obj.get("PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess")) + permission_decision_approve_for_session_approval_factory = PermissionDecisionApproveForSessionApprovalFactory.from_dict(obj.get("PermissionDecisionApproveForSessionApprovalFactory")) permission_decision_approve_for_session_approval_mcp = PermissionDecisionApproveForSessionApprovalMCP.from_dict(obj.get("PermissionDecisionApproveForSessionApprovalMcp")) permission_decision_approve_for_session_approval_mcp_sampling = PermissionDecisionApproveForSessionApprovalMCPSampling.from_dict(obj.get("PermissionDecisionApproveForSessionApprovalMcpSampling")) permission_decision_approve_for_session_approval_memory = PermissionDecisionApproveForSessionApprovalMemory.from_dict(obj.get("PermissionDecisionApproveForSessionApprovalMemory")) @@ -26243,13 +30296,17 @@ def from_dict(obj: Any) -> 'RPC': permission_decision_approve_once = PermissionDecisionApproveOnce.from_dict(obj.get("PermissionDecisionApproveOnce")) permission_decision_approve_permanently = PermissionDecisionApprovePermanently.from_dict(obj.get("PermissionDecisionApprovePermanently")) permission_decision_cancelled = PermissionDecisionCancelled.from_dict(obj.get("PermissionDecisionCancelled")) + permission_decision_context = PermissionDecisionContext.from_dict(obj.get("PermissionDecisionContext")) permission_decision_denied_by_content_exclusion_policy = PermissionDecisionDeniedByContentExclusionPolicy.from_dict(obj.get("PermissionDecisionDeniedByContentExclusionPolicy")) permission_decision_denied_by_permission_request_hook = PermissionDecisionDeniedByPermissionRequestHook.from_dict(obj.get("PermissionDecisionDeniedByPermissionRequestHook")) permission_decision_denied_by_rules = PermissionDecisionDeniedByRules.from_dict(obj.get("PermissionDecisionDeniedByRules")) permission_decision_denied_interactively_by_user = PermissionDecisionDeniedInteractivelyByUser.from_dict(obj.get("PermissionDecisionDeniedInteractivelyByUser")) permission_decision_denied_no_approval_rule_and_could_not_request_from_user = PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser.from_dict(obj.get("PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser")) + permission_decision_outcome = PermissionDecisionOutcome(obj.get("PermissionDecisionOutcome")) permission_decision_reject = PermissionDecisionReject.from_dict(obj.get("PermissionDecisionReject")) permission_decision_request = PermissionDecisionRequest.from_dict(obj.get("PermissionDecisionRequest")) + permission_decision_source = PermissionDecisionSource(obj.get("PermissionDecisionSource")) + permission_decision_surface = PermissionDecisionSurface(obj.get("PermissionDecisionSurface")) permission_decision_user_not_available = PermissionDecisionUserNotAvailable.from_dict(obj.get("PermissionDecisionUserNotAvailable")) permission_location_add_tool_approval_params = PermissionLocationAddToolApprovalParams.from_dict(obj.get("PermissionLocationAddToolApprovalParams")) permission_location_apply_params = PermissionLocationApplyParams.from_dict(obj.get("PermissionLocationApplyParams")) @@ -26282,6 +30339,7 @@ def from_dict(obj: Any) -> 'RPC': permissions_locations_add_tool_approval_details_custom_tool = PermissionsLocationsAddToolApprovalDetailsCustomTool.from_dict(obj.get("PermissionsLocationsAddToolApprovalDetailsCustomTool")) permissions_locations_add_tool_approval_details_extension_management = PermissionsLocationsAddToolApprovalDetailsExtensionManagement.from_dict(obj.get("PermissionsLocationsAddToolApprovalDetailsExtensionManagement")) permissions_locations_add_tool_approval_details_extension_permission_access = PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess.from_dict(obj.get("PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess")) + permissions_locations_add_tool_approval_details_factory = PermissionsLocationsAddToolApprovalDetailsFactory.from_dict(obj.get("PermissionsLocationsAddToolApprovalDetailsFactory")) permissions_locations_add_tool_approval_details_mcp = PermissionsLocationsAddToolApprovalDetailsMCP.from_dict(obj.get("PermissionsLocationsAddToolApprovalDetailsMcp")) permissions_locations_add_tool_approval_details_mcp_sampling = PermissionsLocationsAddToolApprovalDetailsMCPSampling.from_dict(obj.get("PermissionsLocationsAddToolApprovalDetailsMcpSampling")) permissions_locations_add_tool_approval_details_memory = PermissionsLocationsAddToolApprovalDetailsMemory.from_dict(obj.get("PermissionsLocationsAddToolApprovalDetailsMemory")) @@ -26327,7 +30385,7 @@ def from_dict(obj: Any) -> 'RPC': plugins_marketplaces_browse_request = PluginsMarketplacesBrowseRequest.from_dict(obj.get("PluginsMarketplacesBrowseRequest")) plugins_marketplaces_refresh_request = PluginsMarketplacesRefreshRequest.from_dict(obj.get("PluginsMarketplacesRefreshRequest")) plugins_marketplaces_remove_request = PluginsMarketplacesRemoveRequest.from_dict(obj.get("PluginsMarketplacesRemoveRequest")) - plugins_reload_request = PluginsReloadRequest.from_dict(obj.get("PluginsReloadRequest")) + plugins_reload_request = obj.get("PluginsReloadRequest") plugins_uninstall_request = PluginsUninstallRequest.from_dict(obj.get("PluginsUninstallRequest")) plugins_update_request = PluginsUpdateRequest.from_dict(obj.get("PluginsUpdateRequest")) plugin_update_all_entry = PluginUpdateAllEntry.from_dict(obj.get("PluginUpdateAllEntry")) @@ -26344,7 +30402,7 @@ def from_dict(obj: Any) -> 'RPC': provider_endpoint_transport = ProviderTransport(obj.get("ProviderEndpointTransport")) provider_endpoint_type = ProviderType(obj.get("ProviderEndpointType")) provider_endpoint_wire_api = ProviderWireAPI(obj.get("ProviderEndpointWireApi")) - provider_get_endpoint_request = ProviderGetEndpointRequest.from_dict(obj.get("ProviderGetEndpointRequest")) + provider_get_endpoint_request = obj.get("ProviderGetEndpointRequest") provider_model_config = ProviderModelConfig.from_dict(obj.get("ProviderModelConfig")) provider_session_token = ProviderSessionToken.from_dict(obj.get("ProviderSessionToken")) provider_token_acquire_request = ProviderTokenAcquireRequest.from_dict(obj.get("ProviderTokenAcquireRequest")) @@ -26372,13 +30430,36 @@ def from_dict(obj: Any) -> 'RPC': push_attachment_selection_details_end = PushAttachmentSelectionDetailsEnd.from_dict(obj.get("PushAttachmentSelectionDetailsEnd")) push_attachment_selection_details_start = PushAttachmentSelectionDetailsStart.from_dict(obj.get("PushAttachmentSelectionDetailsStart")) push_git_hub_repo_ref = PushGitHubRepoRef.from_dict(obj.get("PushGitHubRepoRef")) + queue_begin_deferred_idle_drain_request = QueueBeginDeferredIdleDrainRequest.from_dict(obj.get("QueueBeginDeferredIdleDrainRequest")) + queue_begin_deferred_idle_drain_result = QueueBeginDeferredIdleDrainResult.from_dict(obj.get("QueueBeginDeferredIdleDrainResult")) + queue_consume_system_notifications_request = QueueConsumeSystemNotificationsRequest.from_dict(obj.get("QueueConsumeSystemNotificationsRequest")) queued_command_handled = QueuedCommandHandled.from_dict(obj.get("QueuedCommandHandled")) queued_command_not_handled = QueuedCommandNotHandled.from_dict(obj.get("QueuedCommandNotHandled")) queued_command_result = _load_QueuedCommandResult(obj.get("QueuedCommandResult")) + queue_defer_session_idle_request = QueueDeferSessionIdleRequest.from_dict(obj.get("QueueDeferSessionIdleRequest")) + queue_duplicate_at_request = QueueDuplicateAtRequest.from_dict(obj.get("QueueDuplicateAtRequest")) + queue_duplicate_at_result = QueueDuplicateAtResult.from_dict(obj.get("QueueDuplicateAtResult")) + queue_enqueue_resume_pending_result = QueueEnqueueResumePendingResult.from_dict(obj.get("QueueEnqueueResumePendingResult")) + queue_finish_deferred_idle_drain_request = QueueFinishDeferredIdleDrainRequest.from_dict(obj.get("QueueFinishDeferredIdleDrainRequest")) + queue_finish_deferred_idle_drain_result = QueueFinishDeferredIdleDrainResult.from_dict(obj.get("QueueFinishDeferredIdleDrainResult")) + queue_has_pending_result = QueueHasPendingResult.from_dict(obj.get("QueueHasPendingResult")) + queue_insert_at_request = QueueInsertAtRequest.from_dict(obj.get("QueueInsertAtRequest")) + queue_insert_at_result = QueueInsertAtResult.from_dict(obj.get("QueueInsertAtResult")) + queue_insert_message = QueueInsertMessage.from_dict(obj.get("QueueInsertMessage")) + queue_move_item_request = QueueMoveItemRequest.from_dict(obj.get("QueueMoveItemRequest")) + queue_move_item_result = QueueMoveItemResult.from_dict(obj.get("QueueMoveItemResult")) queue_pending_items = QueuePendingItems.from_dict(obj.get("QueuePendingItems")) queue_pending_items_kind = QueuePendingItemsKind(obj.get("QueuePendingItemsKind")) queue_pending_items_result = QueuePendingItemsResult.from_dict(obj.get("QueuePendingItemsResult")) + queue_remove_at_request = QueueRemoveAtRequest.from_dict(obj.get("QueueRemoveAtRequest")) + queue_remove_at_result = QueueRemoveAtResult.from_dict(obj.get("QueueRemoveAtResult")) queue_remove_most_recent_result = QueueRemoveMostRecentResult.from_dict(obj.get("QueueRemoveMostRecentResult")) + queue_send_now_request = QueueSendNowRequest.from_dict(obj.get("QueueSendNowRequest")) + queue_send_now_result = QueueSendNowResult.from_dict(obj.get("QueueSendNowResult")) + queue_set_drain_paused_request = QueueSetDrainPausedRequest.from_dict(obj.get("QueueSetDrainPausedRequest")) + queue_snapshot_result = QueueSnapshotResult.from_dict(obj.get("QueueSnapshotResult")) + queue_update_text_request = QueueUpdateTextRequest.from_dict(obj.get("QueueUpdateTextRequest")) + queue_update_text_result = QueueUpdateTextResult.from_dict(obj.get("QueueUpdateTextResult")) register_event_interest_params = RegisterEventInterestParams.from_dict(obj.get("RegisterEventInterestParams")) register_event_interest_result = RegisterEventInterestResult.from_dict(obj.get("RegisterEventInterestResult")) register_extension_tools_params = _RegisterExtensionToolsParams.from_dict(obj.get("RegisterExtensionToolsParams")) @@ -26406,14 +30487,23 @@ def from_dict(obj: Any) -> 'RPC': remote_session_repository = RemoteSessionRepository.from_dict(obj.get("RemoteSessionRepository")) run_options = RunOptions.from_dict(obj.get("RunOptions")) sandbox_config = SandboxConfig.from_dict(obj.get("SandboxConfig")) + sandbox_config_auth = SandboxConfigAuth.from_dict(obj.get("SandboxConfigAuth")) sandbox_config_user_policy = SandboxConfigUserPolicy.from_dict(obj.get("SandboxConfigUserPolicy")) sandbox_config_user_policy_experimental = SandboxConfigUserPolicyExperimental.from_dict(obj.get("SandboxConfigUserPolicyExperimental")) sandbox_config_user_policy_experimental_seatbelt = SandboxConfigUserPolicyExperimentalSeatbelt.from_dict(obj.get("SandboxConfigUserPolicyExperimentalSeatbelt")) sandbox_config_user_policy_filesystem = SandboxConfigUserPolicyFilesystem.from_dict(obj.get("SandboxConfigUserPolicyFilesystem")) sandbox_config_user_policy_network = SandboxConfigUserPolicyNetwork.from_dict(obj.get("SandboxConfigUserPolicyNetwork")) + sandbox_config_user_policy_network_proxy = SandboxConfigUserPolicyNetworkProxy.from_dict(obj.get("SandboxConfigUserPolicyNetworkProxy")) sandbox_config_user_policy_seatbelt = SandboxConfigUserPolicySeatbelt.from_dict(obj.get("SandboxConfigUserPolicySeatbelt")) + schedule_add_at_request = ScheduleAddAtRequest.from_dict(obj.get("ScheduleAddAtRequest")) + schedule_add_cron_request = ScheduleAddCronRequest.from_dict(obj.get("ScheduleAddCronRequest")) + schedule_add_request = ScheduleAddRequest.from_dict(obj.get("ScheduleAddRequest")) + schedule_add_result = ScheduleAddResult.from_dict(obj.get("ScheduleAddResult")) + schedule_add_self_paced_request = ScheduleAddSelfPacedRequest.from_dict(obj.get("ScheduleAddSelfPacedRequest")) schedule_entry = ScheduleEntry.from_dict(obj.get("ScheduleEntry")) + schedule_has_self_paced_result = ScheduleHasSelfPacedResult.from_dict(obj.get("ScheduleHasSelfPacedResult")) schedule_list = ScheduleList.from_dict(obj.get("ScheduleList")) + schedule_rearm_self_paced_request = ScheduleRearmSelfPacedRequest.from_dict(obj.get("ScheduleRearmSelfPacedRequest")) schedule_stop_request = ScheduleStopRequest.from_dict(obj.get("ScheduleStopRequest")) schedule_stop_result = ScheduleStopResult.from_dict(obj.get("ScheduleStopResult")) secrets_add_filter_values_request = SecretsAddFilterValuesRequest.from_dict(obj.get("SecretsAddFilterValuesRequest")) @@ -26426,14 +30516,18 @@ def from_dict(obj: Any) -> 'RPC': send_mode = SendMode(obj.get("SendMode")) send_request = SendRequest.from_dict(obj.get("SendRequest")) send_result = SendResult.from_dict(obj.get("SendResult")) + send_system_notification_request = SendSystemNotificationRequest.from_dict(obj.get("SendSystemNotificationRequest")) server_agent_list = ServerAgentList.from_dict(obj.get("ServerAgentList")) server_instruction_source_list = ServerInstructionSourceList.from_dict(obj.get("ServerInstructionSourceList")) server_skill = ServerSkill.from_dict(obj.get("ServerSkill")) server_skill_list = ServerSkillList.from_dict(obj.get("ServerSkillList")) session_activity = SessionActivity.from_dict(obj.get("SessionActivity")) + session_agent_list_request = SessionAgentListRequest.from_dict(obj.get("SessionAgentListRequest")) session_auth_status = SessionAuthStatus.from_dict(obj.get("SessionAuthStatus")) session_bulk_delete_result = SessionBulkDeleteResult.from_dict(obj.get("SessionBulkDeleteResult")) + session_cancel_all_background_agents_result = from_int(obj.get("SessionCancelAllBackgroundAgentsResult")) session_capability = SessionCapability(obj.get("SessionCapability")) + session_commands_list_request = SessionCommandsListRequest.from_dict(obj.get("SessionCommandsListRequest")) session_completion_item = SessionCompletionItem.from_dict(obj.get("SessionCompletionItem")) session_context = SessionContext.from_dict(obj.get("SessionContext")) session_context_host_type = HostType(obj.get("SessionContextHostType")) @@ -26463,23 +30557,42 @@ def from_dict(obj: Any) -> 'RPC': session_fs_sqlite_query_request = SessionFSSqliteQueryRequest.from_dict(obj.get("SessionFsSqliteQueryRequest")) session_fs_sqlite_query_result = SessionFSSqliteQueryResult.from_dict(obj.get("SessionFsSqliteQueryResult")) session_fs_sqlite_query_type = SessionFSSqliteQueryType(obj.get("SessionFsSqliteQueryType")) + session_fs_sqlite_transaction_error = SessionFSSqliteTransactionError.from_dict(obj.get("SessionFsSqliteTransactionError")) + session_fs_sqlite_transaction_error_class = SessionFSSqliteTransactionErrorClass(obj.get("SessionFsSqliteTransactionErrorClass")) + session_fs_sqlite_transaction_request = SessionFSSqliteTransactionRequest.from_dict(obj.get("SessionFsSqliteTransactionRequest")) + session_fs_sqlite_transaction_result = SessionFSSqliteTransactionResult.from_dict(obj.get("SessionFsSqliteTransactionResult")) + session_fs_sqlite_transaction_statement = SessionFSSqliteTransactionStatement.from_dict(obj.get("SessionFsSqliteTransactionStatement")) session_fs_stat_request = SessionFSStatRequest.from_dict(obj.get("SessionFsStatRequest")) session_fs_stat_result = SessionFSStatResult.from_dict(obj.get("SessionFsStatResult")) session_fs_write_file_request = SessionFSWriteFileRequest.from_dict(obj.get("SessionFsWriteFileRequest")) + session_history_compact_request = SessionHistoryCompactRequest.from_dict(obj.get("SessionHistoryCompactRequest")) session_installed_plugin = SessionInstalledPlugin.from_dict(obj.get("SessionInstalledPlugin")) session_installed_plugin_source = from_union([SessionInstalledPluginSource.from_dict, from_str], obj.get("SessionInstalledPluginSource")) session_installed_plugin_source_git_hub = SessionInstalledPluginSourceGitHub.from_dict(obj.get("SessionInstalledPluginSourceGitHub")) session_installed_plugin_source_local = SessionInstalledPluginSourceLocal.from_dict(obj.get("SessionInstalledPluginSourceLocal")) session_installed_plugin_source_url = SessionInstalledPluginSourceURL.from_dict(obj.get("SessionInstalledPluginSourceUrl")) + session_limit_prediction_baseline_data = SessionLimitPredictionBaselineData.from_dict(obj.get("SessionLimitPredictionBaselineData")) + session_limit_prediction_client_type = SessionLimitPredictionClientType(obj.get("SessionLimitPredictionClientType")) + session_limit_prediction_details = SessionLimitPredictionDetails.from_dict(obj.get("SessionLimitPredictionDetails")) + session_limit_prediction_predict_request = SessionLimitPredictionPredictRequest.from_dict(obj.get("SessionLimitPredictionPredictRequest")) + session_limit_prediction_request = obj.get("SessionLimitPredictionRequest") + session_limit_prediction_result = SessionLimitPredictionResult.from_dict(obj.get("SessionLimitPredictionResult")) + session_limit_prediction_source = SessionLimitPredictionSource(obj.get("SessionLimitPredictionSource")) + session_limit_prediction_tier = SessionLimitPredictionTier(obj.get("SessionLimitPredictionTier")) + session_limit_prediction_tier_option = SessionLimitPredictionTierOption.from_dict(obj.get("SessionLimitPredictionTierOption")) + session_limit_prediction_unavailable_reason = SessionLimitPredictionUnavailableReason(obj.get("SessionLimitPredictionUnavailableReason")) session_list = SessionList.from_dict(obj.get("SessionList")) session_list_entry = _load_SessionListEntry(obj.get("SessionListEntry")) session_list_filter = SessionListFilter.from_dict(obj.get("SessionListFilter")) session_load_deferred_repo_hooks_result = SessionLoadDeferredRepoHooksResult.from_dict(obj.get("SessionLoadDeferredRepoHooksResult")) session_log_level = SessionLogLevel(obj.get("SessionLogLevel")) + session_managed_permissions = SessionManagedPermissions.from_dict(obj.get("SessionManagedPermissions")) + session_managed_settings = SessionManagedSettings.from_dict(obj.get("SessionManagedSettings")) session_mcp_apps_call_tool_result = from_dict(lambda x: x, obj.get("SessionMcpAppsCallToolResult")) session_metadata_snapshot = SessionMetadataSnapshot.from_dict(obj.get("SessionMetadataSnapshot")) session_mode = SessionMode(obj.get("SessionMode")) session_model_list = SessionModelList.from_dict(obj.get("SessionModelList")) + session_model_list_request = SessionModelListRequest.from_dict(obj.get("SessionModelListRequest")) session_model_price_category = SessionModelPriceCategory.from_dict(obj.get("SessionModelPriceCategory")) session_open_options = SessionOpenOptions.from_dict(obj.get("SessionOpenOptions")) session_open_options_additional_content_exclusion_policy = SessionOpenOptionsAdditionalContentExclusionPolicy.from_dict(obj.get("SessionOpenOptionsAdditionalContentExclusionPolicy")) @@ -26490,12 +30603,15 @@ def from_dict(obj: Any) -> 'RPC': session_open_options_reasoning_summary = ReasoningSummary(obj.get("SessionOpenOptionsReasoningSummary")) session_open_params = _load_SessionOpenParams(obj.get("SessionOpenParams")) session_open_result = SessionOpenResult.from_dict(obj.get("SessionOpenResult")) + session_plugins_reload_request = SessionPluginsReloadRequest.from_dict(obj.get("SessionPluginsReloadRequest")) + session_provider_get_endpoint_request = SessionProviderGetEndpointRequest.from_dict(obj.get("SessionProviderGetEndpointRequest")) session_prune_result = SessionPruneResult.from_dict(obj.get("SessionPruneResult")) sessions_bulk_delete_request = SessionsBulkDeleteRequest.from_dict(obj.get("SessionsBulkDeleteRequest")) sessions_check_in_use_request = SessionsCheckInUseRequest.from_dict(obj.get("SessionsCheckInUseRequest")) sessions_check_in_use_result = SessionsCheckInUseResult.from_dict(obj.get("SessionsCheckInUseResult")) sessions_close_request = SessionsCloseRequest.from_dict(obj.get("SessionsCloseRequest")) sessions_close_result = SessionsCloseResult.from_dict(obj.get("SessionsCloseResult")) + sessions_delete_request = SessionsDeleteRequest.from_dict(obj.get("SessionsDeleteRequest")) sessions_enrich_metadata_request = SessionsEnrichMetadataRequest.from_dict(obj.get("SessionsEnrichMetadataRequest")) session_set_credentials_params = SessionSetCredentialsParams.from_dict(obj.get("SessionSetCredentialsParams")) session_set_credentials_result = SessionSetCredentialsResult.from_dict(obj.get("SessionSetCredentialsResult")) @@ -26521,9 +30637,13 @@ def from_dict(obj: Any) -> 'RPC': sessions_get_event_file_path_result = SessionsGetEventFilePathResult.from_dict(obj.get("SessionsGetEventFilePathResult")) sessions_get_last_for_context_request = SessionsGetLastForContextRequest.from_dict(obj.get("SessionsGetLastForContextRequest")) sessions_get_last_for_context_result = SessionsGetLastForContextResult.from_dict(obj.get("SessionsGetLastForContextResult")) + sessions_get_metadata_request = SessionsGetMetadataRequest.from_dict(obj.get("SessionsGetMetadataRequest")) + sessions_get_metadata_result = SessionsGetMetadataResult.from_dict(obj.get("SessionsGetMetadataResult")) sessions_get_persisted_remote_steerable_request = SessionsGetPersistedRemoteSteerableRequest.from_dict(obj.get("SessionsGetPersistedRemoteSteerableRequest")) sessions_get_persisted_remote_steerable_result = SessionsGetPersistedRemoteSteerableResult.from_dict(obj.get("SessionsGetPersistedRemoteSteerableResult")) session_sizes = SessionSizes.from_dict(obj.get("SessionSizes")) + sessions_list_non_empty_session_ids_request = SessionsListNonEmptySessionIDSRequest.from_dict(obj.get("SessionsListNonEmptySessionIdsRequest")) + sessions_list_non_empty_session_ids_result = SessionsListNonEmptySessionIDSResult.from_dict(obj.get("SessionsListNonEmptySessionIdsResult")) sessions_list_request = SessionsListRequest.from_dict(obj.get("SessionsListRequest")) sessions_load_deferred_repo_hooks_request = SessionsLoadDeferredRepoHooksRequest.from_dict(obj.get("SessionsLoadDeferredRepoHooksRequest")) sessions_open_attach = SessionsOpenAttach.from_dict(obj.get("SessionsOpenAttach")) @@ -26563,9 +30683,13 @@ def from_dict(obj: Any) -> 'RPC': shell_exec_request = ShellExecRequest.from_dict(obj.get("ShellExecRequest")) shell_exec_result = ShellExecResult.from_dict(obj.get("ShellExecResult")) shell_execute_user_requested_request = ShellExecuteUserRequestedRequest.from_dict(obj.get("ShellExecuteUserRequestedRequest")) + shell_init_profile = ShellInitProfile(obj.get("ShellInitProfile")) + shell_init_script = ShellInitScript.from_dict(obj.get("ShellInitScript")) + shell_init_script_shell = ShellInitScriptShell(obj.get("ShellInitScriptShell")) shell_kill_request = ShellKillRequest.from_dict(obj.get("ShellKillRequest")) shell_kill_result = ShellKillResult.from_dict(obj.get("ShellKillResult")) shell_kill_signal = ShellKillSignal(obj.get("ShellKillSignal")) + shell_options = ShellOptions.from_dict(obj.get("ShellOptions")) shutdown_request = ShutdownRequest.from_dict(obj.get("ShutdownRequest")) skill = Skill.from_dict(obj.get("Skill")) skill_discovery_path = SkillDiscoveryPath.from_dict(obj.get("SkillDiscoveryPath")) @@ -26688,26 +30812,36 @@ def from_dict(obj: Any) -> 'RPC': workspace_diff_file_change_type = WorkspaceDiffFileChangeType(obj.get("WorkspaceDiffFileChangeType")) workspace_diff_mode = WorkspaceDiffMode(obj.get("WorkspaceDiffMode")) workspace_diff_result = WorkspaceDiffResult.from_dict(obj.get("WorkspaceDiffResult")) + workspaces_add_summary_request = WorkspacesAddSummaryRequest.from_dict(obj.get("WorkspacesAddSummaryRequest")) + workspaces_add_summary_result = WorkspacesAddSummaryResult.from_dict(obj.get("WorkspacesAddSummaryResult")) + workspaces_autopilot_objective_exists_result = WorkspacesAutopilotObjectiveExistsResult.from_dict(obj.get("WorkspacesAutopilotObjectiveExistsResult")) workspaces_checkpoints = WorkspacesCheckpoints.from_dict(obj.get("WorkspacesCheckpoints")) workspaces_create_file_request = WorkspacesCreateFileRequest.from_dict(obj.get("WorkspacesCreateFileRequest")) + workspaces_delete_autopilot_objective_result = WorkspacesDeleteAutopilotObjectiveResult.from_dict(obj.get("WorkspacesDeleteAutopilotObjectiveResult")) workspaces_diff_request = WorkspacesDiffRequest.from_dict(obj.get("WorkspacesDiffRequest")) + workspaces_ensure_request = WorkspacesEnsureRequest.from_dict(obj.get("WorkspacesEnsureRequest")) workspaces_get_workspace_result = WorkspacesGetWorkspaceResult.from_dict(obj.get("WorkspacesGetWorkspaceResult")) workspaces_list_checkpoints_result = WorkspacesListCheckpointsResult.from_dict(obj.get("WorkspacesListCheckpointsResult")) workspaces_list_files_result = WorkspacesListFilesResult.from_dict(obj.get("WorkspacesListFilesResult")) + workspaces_read_autopilot_objective_result = WorkspacesReadAutopilotObjectiveResult.from_dict(obj.get("WorkspacesReadAutopilotObjectiveResult")) workspaces_read_checkpoint_request = WorkspacesReadCheckpointRequest.from_dict(obj.get("WorkspacesReadCheckpointRequest")) workspaces_read_checkpoint_result = WorkspacesReadCheckpointResult.from_dict(obj.get("WorkspacesReadCheckpointResult")) workspaces_read_file_request = WorkspacesReadFileRequest.from_dict(obj.get("WorkspacesReadFileRequest")) workspaces_read_file_result = WorkspacesReadFileResult.from_dict(obj.get("WorkspacesReadFileResult")) workspaces_save_large_paste_request = WorkspacesSaveLargePasteRequest.from_dict(obj.get("WorkspacesSaveLargePasteRequest")) workspaces_save_large_paste_result = WorkspacesSaveLargePasteResult.from_dict(obj.get("WorkspacesSaveLargePasteResult")) + workspaces_truncate_summaries_request = WorkspacesTruncateSummariesRequest.from_dict(obj.get("WorkspacesTruncateSummariesRequest")) workspace_summary_host_type = HostType(obj.get("WorkspaceSummaryHostType")) + workspaces_update_metadata_request = WorkspacesUpdateMetadataRequest.from_dict(obj.get("WorkspacesUpdateMetadataRequest")) workspaces_workspace_details_host_type = HostType(obj.get("WorkspacesWorkspaceDetailsHostType")) + workspaces_write_autopilot_objective_request = WorkspacesWriteAutopilotObjectiveRequest.from_dict(obj.get("WorkspacesWriteAutopilotObjectiveRequest")) + workspaces_write_autopilot_objective_result = WorkspacesWriteAutopilotObjectiveResult.from_dict(obj.get("WorkspacesWriteAutopilotObjectiveResult")) session_context_attribution = from_union([SessionContextAttribution.from_dict, from_none], obj.get("SessionContextAttribution")) session_context_info = from_union([SessionContextInfo.from_dict, from_none], obj.get("SessionContextInfo")) subagent_settings = from_union([SubagentSettings.from_dict, from_none], obj.get("SubagentSettings")) task_progress = from_union([TaskProgress.from_dict, from_none], obj.get("TaskProgress")) workspace_summary = from_union([WorkspaceSummary.from_dict, from_none], obj.get("WorkspaceSummary")) - return RPC(abort_request, abort_result, account_all_users, account_get_all_users_result, account_get_current_auth_result, account_get_quota_request, account_get_quota_result, account_login_request, account_login_result, account_logout_request, account_logout_result, account_quota_snapshot, adaptive_thinking_support, agent_discovery_path, agent_discovery_path_list, agent_discovery_path_scope, agent_get_current_result, agent_info, agent_info_source, agent_list, agent_registry_live_target_entry, agent_registry_live_target_entry_attention_kind, agent_registry_live_target_entry_kind, agent_registry_live_target_entry_last_terminal_event, agent_registry_live_target_entry_status, agent_registry_log_capture, agent_registry_log_capture_open_error_reason, agent_registry_spawn_error, agent_registry_spawn_permission_mode, agent_registry_spawn_registry_timeout, agent_registry_spawn_request, agent_registry_spawn_result, agent_registry_spawn_spawned, agent_registry_spawn_validation_error, agent_registry_spawn_validation_error_field, agent_registry_spawn_validation_error_reason, agent_reload_result, agents_discover_request, agent_select_request, agent_select_result, agents_get_discovery_paths_request, allow_all_permission_set_result, allow_all_permission_state, api_key_auth_info, auth_info, auth_info_type, cancel_user_requested_shell_command_result, canvas_action, canvas_action_invoke_request, canvas_action_invoke_result, canvas_close_request, canvas_host_context, canvas_host_context_capabilities, canvas_json_schema, canvas_list, canvas_list_open_result, canvas_open_request, canvas_provider_close_request, canvas_provider_invoke_action_request, canvas_provider_open_request, canvas_provider_open_result, canvas_session_context, capi_session_options, command_list, commands_handle_pending_command_request, commands_handle_pending_command_result, commands_invoke_request, commands_list_request, commands_respond_to_queued_command_request, commands_respond_to_queued_command_result, completions_get_trigger_characters_result, completions_request_request, completions_request_result, configure_session_extensions_params, connected_remote_session_metadata, connected_remote_session_metadata_kind, connected_remote_session_metadata_repository, connect_remote_session_params, connect_request, connect_result, content_filter_mode, context_heaviest_message, copilot_api_token_auth_info, copilot_user_response, copilot_user_response_endpoints, copilot_user_response_quota_snapshots, copilot_user_response_quota_snapshots_chat, copilot_user_response_quota_snapshots_completions, copilot_user_response_quota_snapshots_premium_interactions, current_model, current_tool_metadata, debug_collect_logs_collected_entry, debug_collect_logs_destination, debug_collect_logs_entry, debug_collect_logs_entry_kind, debug_collect_logs_include, debug_collect_logs_redaction, debug_collect_logs_request, debug_collect_logs_result, debug_collect_logs_result_kind, debug_collect_logs_skipped_entry, debug_collect_logs_source, discovered_canvas, discovered_mcp_server, discovered_mcp_server_type, enqueue_command_params, enqueue_command_result, env_auth_info, event_log_read_request, event_log_release_interest_result, event_log_tail_result, event_log_types, events_agent_scope, events_cursor_status, events_read_result, execute_command_params, execute_command_result, extension, extension_context_push_input, extension_list, extensions_disable_request, extensions_enable_request, extension_source, extension_status, external_tool_result, external_tool_text_result_for_llm, external_tool_text_result_for_llm_binary_results_for_llm, external_tool_text_result_for_llm_binary_results_for_llm_type, external_tool_text_result_for_llm_content, external_tool_text_result_for_llm_content_audio, external_tool_text_result_for_llm_content_image, external_tool_text_result_for_llm_content_resource, external_tool_text_result_for_llm_content_resource_details, external_tool_text_result_for_llm_content_resource_link, external_tool_text_result_for_llm_content_resource_link_icon, external_tool_text_result_for_llm_content_resource_link_icon_theme, external_tool_text_result_for_llm_content_shell_exit, external_tool_text_result_for_llm_content_terminal, external_tool_text_result_for_llm_content_text, factory_abort_request, factory_ack_result, factory_agent_options, factory_agent_request, factory_agent_result, factory_cancel_request, factory_execute_request, factory_execute_result, factory_get_run_request, factory_journal_get_request, factory_journal_get_result, factory_journal_put_request, factory_log_line, factory_log_line_kind, factory_log_request, factory_run_failure, factory_run_failure_kind, factory_run_limits, factory_run_request, factory_run_result, factory_run_status, filter_mapping, fleet_start_request, fleet_start_result, folder_trust_add_params, folder_trust_check_params, folder_trust_check_result, gh_cli_auth_info, git_hub_telemetry_client_info, git_hub_telemetry_event, git_hub_telemetry_notification, handle_pending_tool_call_request, handle_pending_tool_call_result, history_abort_manual_compaction_result, history_cancel_background_compaction_result, history_compact_context_window, history_compact_request, history_compact_result, history_summarize_for_handoff_result, history_truncate_request, history_truncate_result, hmac_auth_info, hook_invoke_request, hook_invoke_response, hook_type, installed_plugin, installed_plugin_info, installed_plugin_source, installed_plugin_source_git_hub, installed_plugin_source_local, installed_plugin_source_url, instruction_discovery_path, instruction_discovery_path_kind, instruction_discovery_path_list, instruction_discovery_path_location, instructions_discover_request, instructions_get_discovery_paths_request, instructions_get_sources_result, instruction_source, instruction_source_location, instruction_source_type, llm_inference_headers, llm_inference_http_request_chunk_request, llm_inference_http_request_chunk_result, llm_inference_http_request_start_request, llm_inference_http_request_start_result, llm_inference_http_request_start_transport, llm_inference_http_response_chunk_error, llm_inference_http_response_chunk_request, llm_inference_http_response_chunk_result, llm_inference_http_response_start_request, llm_inference_http_response_start_result, llm_inference_set_provider_result, local_session_metadata_value, log_request, log_result, lsp_initialize_request, marketplace_add_result, marketplace_browse_result, marketplace_info, marketplace_list_result, marketplace_plugin_info, marketplace_refresh_entry, marketplace_refresh_result, marketplace_remove_result, mcp_allowed_server, mcp_apps_call_tool_request, mcp_apps_diagnose_capability, mcp_apps_diagnose_request, mcp_apps_diagnose_result, mcp_apps_diagnose_server, mcp_apps_host_context, mcp_apps_host_context_details, mcp_apps_host_context_details_available_display_mode, mcp_apps_host_context_details_display_mode, mcp_apps_host_context_details_platform, mcp_apps_host_context_details_theme, mcp_apps_list_tools_request, mcp_apps_list_tools_result, mcp_apps_read_resource_request, mcp_apps_read_resource_result, mcp_apps_resource_content, mcp_apps_set_host_context_details, mcp_apps_set_host_context_details_available_display_mode, mcp_apps_set_host_context_details_display_mode, mcp_apps_set_host_context_details_platform, mcp_apps_set_host_context_details_theme, mcp_apps_set_host_context_request, mcp_cancel_sampling_execution_params, mcp_cancel_sampling_execution_result, mcp_config_add_request, mcp_config_disable_request, mcp_config_enable_request, mcp_config_list, mcp_config_remove_request, mcp_config_update_request, mcp_configure_git_hub_request, mcp_configure_git_hub_result, mcp_disable_request, mcp_discover_request, mcp_discover_result, mcp_enable_request, mcp_execute_sampling_params, mcp_execute_sampling_request, mcp_execute_sampling_result, mcp_filtered_server, mcp_headers_handle_pending_headers_refresh_request, mcp_headers_handle_pending_headers_refresh_request_request, mcp_headers_handle_pending_headers_refresh_request_result, mcp_host_state, mcp_is_server_running_request, mcp_is_server_running_result, mcp_list_tools_request, mcp_list_tools_result, mcp_oauth_handle_pending_request, mcp_oauth_handle_pending_result, mcp_oauth_login_grant_type, mcp_oauth_login_request, mcp_oauth_login_result, mcp_oauth_pending_request_response, mcp_register_external_client_request, mcp_reload_with_config_request, mcp_remove_git_hub_result, mcp_resource, mcp_resource_annotations, mcp_resource_content, mcp_resource_icon, mcp_resources_list_request, mcp_resources_list_result, mcp_resources_list_templates_request, mcp_resources_list_templates_result, mcp_resources_read_request, mcp_resources_read_result, mcp_resource_template, mcp_restart_server_request, mcp_sampling_execution_action, mcp_sampling_execution_result, mcp_server, mcp_server_auth_config, mcp_server_auth_config_redirect_port, mcp_server_config, mcp_server_config_defer_tools, mcp_server_config_http, mcp_server_config_http_oauth_grant_type, mcp_server_config_http_type, mcp_server_config_stdio, mcp_server_failure_info, mcp_server_list, mcp_server_needs_auth_info, mcp_set_env_value_mode_details, mcp_set_env_value_mode_params, mcp_set_env_value_mode_result, mcp_start_server_request, mcp_start_servers_result, mcp_stop_server_request, mcp_tools, mcp_tool_ui, mcp_tool_ui_visibility, mcp_unregister_external_client_request, memory_configuration, metadata_context_attribution_result, metadata_context_heaviest_messages_request, metadata_context_heaviest_messages_result, metadata_context_info_request, metadata_context_info_result, metadata_is_processing_result, metadata_recompute_context_tokens_request, metadata_recompute_context_tokens_result, metadata_record_context_change_request, metadata_record_context_change_result, metadata_set_working_directory_request, metadata_set_working_directory_result, metadata_snapshot_current_mode, metadata_snapshot_remote_metadata, metadata_snapshot_remote_metadata_repository, metadata_snapshot_remote_metadata_task_type, model, model_billing, model_billing_promo, model_billing_token_prices, model_billing_token_prices_long_context, model_capabilities, model_capabilities_limits, model_capabilities_limits_vision, model_capabilities_override, model_capabilities_override_limits, model_capabilities_override_limits_vision, model_capabilities_override_supports, model_capabilities_supports, model_list, model_list_request, model_picker_category, model_picker_price_category, model_policy, model_policy_state, model_set_reasoning_effort_request, model_set_reasoning_effort_result, models_list_request, model_switch_to_request, model_switch_to_result, mode_set_request, named_provider_config, name_get_result, name_set_auto_request, name_set_auto_result, name_set_request, open_canvas_instance, options_update_additional_content_exclusion_policy, options_update_additional_content_exclusion_policy_rule, options_update_additional_content_exclusion_policy_rule_source, options_update_additional_content_exclusion_policy_scope, options_update_context_tier, options_update_env_value_mode, options_update_reasoning_summary, options_update_tool_filter_precedence, pending_permission_request, pending_permission_request_list, permission_decision, permission_decision_approved, permission_decision_approved_for_location, permission_decision_approved_for_session, permission_decision_approve_for_location, permission_decision_approve_for_location_approval, permission_decision_approve_for_location_approval_commands, permission_decision_approve_for_location_approval_custom_tool, permission_decision_approve_for_location_approval_extension_management, permission_decision_approve_for_location_approval_extension_permission_access, permission_decision_approve_for_location_approval_mcp, permission_decision_approve_for_location_approval_mcp_sampling, permission_decision_approve_for_location_approval_memory, permission_decision_approve_for_location_approval_read, permission_decision_approve_for_location_approval_write, permission_decision_approve_for_session, permission_decision_approve_for_session_approval, permission_decision_approve_for_session_approval_commands, permission_decision_approve_for_session_approval_custom_tool, permission_decision_approve_for_session_approval_extension_management, permission_decision_approve_for_session_approval_extension_permission_access, permission_decision_approve_for_session_approval_mcp, permission_decision_approve_for_session_approval_mcp_sampling, permission_decision_approve_for_session_approval_memory, permission_decision_approve_for_session_approval_read, permission_decision_approve_for_session_approval_write, permission_decision_approve_once, permission_decision_approve_permanently, permission_decision_cancelled, permission_decision_denied_by_content_exclusion_policy, permission_decision_denied_by_permission_request_hook, permission_decision_denied_by_rules, permission_decision_denied_interactively_by_user, permission_decision_denied_no_approval_rule_and_could_not_request_from_user, permission_decision_reject, permission_decision_request, permission_decision_user_not_available, permission_location_add_tool_approval_params, permission_location_apply_params, permission_location_apply_result, permission_location_resolve_params, permission_location_resolve_result, permission_location_type, permission_paths_add_params, permission_paths_allowed_check_params, permission_paths_allowed_check_result, permission_paths_config, permission_paths_list, permission_paths_update_primary_params, permission_paths_workspace_check_params, permission_paths_workspace_check_result, permission_prompt_shown_notification, permission_request_result, permission_rules_set, permissions_allow_all_mode, permissions_configure_additional_content_exclusion_policy, permissions_configure_additional_content_exclusion_policy_rule, permissions_configure_additional_content_exclusion_policy_rule_source, permissions_configure_additional_content_exclusion_policy_scope, permissions_configure_params, permissions_configure_result, permissions_folder_trust_add_trusted_result, permissions_get_allow_all_request, permissions_locations_add_tool_approval_details, permissions_locations_add_tool_approval_details_commands, permissions_locations_add_tool_approval_details_custom_tool, permissions_locations_add_tool_approval_details_extension_management, permissions_locations_add_tool_approval_details_extension_permission_access, permissions_locations_add_tool_approval_details_mcp, permissions_locations_add_tool_approval_details_mcp_sampling, permissions_locations_add_tool_approval_details_memory, permissions_locations_add_tool_approval_details_read, permissions_locations_add_tool_approval_details_write, permissions_locations_add_tool_approval_result, permissions_modify_rules_params, permissions_modify_rules_result, permissions_modify_rules_scope, permissions_notify_prompt_shown_result, permissions_paths_add_result, permissions_paths_list_request, permissions_paths_update_primary_result, permissions_pending_requests_request, permissions_reset_session_approvals_request, permissions_reset_session_approvals_result, permissions_set_allow_all_request, permissions_set_allow_all_source, permissions_set_approve_all_request, permissions_set_approve_all_result, permissions_set_approve_all_source, permissions_set_required_request, permissions_set_required_result, permissions_urls_set_unrestricted_mode_result, permission_urls_config, permission_urls_set_unrestricted_mode_params, ping_request, ping_result, plan_read_result, plan_read_sql_todos_result, plan_read_sql_todos_with_dependencies_result, plan_sql_todo_dependency, plan_sql_todos_row, plan_update_request, plugin, plugin_install_result, plugin_list, plugin_list_result, plugins_disable_request, plugins_enable_request, plugins_install_request, plugins_marketplaces_add_request, plugins_marketplaces_browse_request, plugins_marketplaces_refresh_request, plugins_marketplaces_remove_request, plugins_reload_request, plugins_uninstall_request, plugins_update_request, plugin_update_all_entry, plugin_update_all_result, plugin_update_result, provider_add_request, provider_add_result, provider_config, provider_config_azure, provider_config_transport, provider_config_type, provider_config_wire_api, provider_endpoint, provider_endpoint_transport, provider_endpoint_type, provider_endpoint_wire_api, provider_get_endpoint_request, provider_model_config, provider_session_token, provider_token_acquire_request, provider_token_acquire_result, push_attachment, push_attachment_blob, push_attachment_directory, push_attachment_file, push_attachment_file_line_range, push_attachment_git_hub_actions_job, push_attachment_git_hub_commit, push_attachment_git_hub_file, push_attachment_git_hub_file_diff, push_attachment_git_hub_file_diff_side, push_attachment_git_hub_reference, push_attachment_git_hub_reference_type, push_attachment_git_hub_release, push_attachment_git_hub_repository, push_attachment_git_hub_snippet, push_attachment_git_hub_tree_comparison, push_attachment_git_hub_tree_comparison_side, push_attachment_git_hub_url, push_attachment_selection, push_attachment_selection_details, push_attachment_selection_details_end, push_attachment_selection_details_start, push_git_hub_repo_ref, queued_command_handled, queued_command_not_handled, queued_command_result, queue_pending_items, queue_pending_items_kind, queue_pending_items_result, queue_remove_most_recent_result, register_event_interest_params, register_event_interest_result, register_extension_tools_params, register_extension_tools_result, release_event_interest_params, remote_control_config, remote_control_config_existing_mc_session, remote_control_status, remote_control_status_active, remote_control_status_connecting, remote_control_status_error, remote_control_status_off, remote_control_status_result, remote_control_stop_result, remote_control_transfer_result, remote_enable_request, remote_enable_result, remote_notify_steerable_changed_request, remote_notify_steerable_changed_result, remote_session_connection_result, remote_session_metadata_repository, remote_session_metadata_task_type, remote_session_metadata_value, remote_session_mode, remote_session_repository, run_options, sandbox_config, sandbox_config_user_policy, sandbox_config_user_policy_experimental, sandbox_config_user_policy_experimental_seatbelt, sandbox_config_user_policy_filesystem, sandbox_config_user_policy_network, sandbox_config_user_policy_seatbelt, schedule_entry, schedule_list, schedule_stop_request, schedule_stop_result, secrets_add_filter_values_request, secrets_add_filter_values_result, send_agent_mode, send_attachments_to_message_params, send_message_item, send_messages_request, send_messages_result, send_mode, send_request, send_result, server_agent_list, server_instruction_source_list, server_skill, server_skill_list, session_activity, session_auth_status, session_bulk_delete_result, session_capability, session_completion_item, session_context, session_context_host_type, session_enrich_metadata_result, session_fs_append_file_request, session_fs_error, session_fs_error_code, session_fs_exists_request, session_fs_exists_result, session_fs_mkdir_request, session_fs_readdir_request, session_fs_readdir_result, session_fs_readdir_with_types_entry, session_fs_readdir_with_types_entry_type, session_fs_readdir_with_types_request, session_fs_readdir_with_types_result, session_fs_read_file_request, session_fs_read_file_result, session_fs_rename_request, session_fs_rm_request, session_fs_set_provider_capabilities, session_fs_set_provider_conventions, session_fs_set_provider_request, session_fs_set_provider_result, session_fs_sqlite_exists_request, session_fs_sqlite_exists_result, session_fs_sqlite_query_request, session_fs_sqlite_query_result, session_fs_sqlite_query_type, session_fs_stat_request, session_fs_stat_result, session_fs_write_file_request, session_installed_plugin, session_installed_plugin_source, session_installed_plugin_source_git_hub, session_installed_plugin_source_local, session_installed_plugin_source_url, session_list, session_list_entry, session_list_filter, session_load_deferred_repo_hooks_result, session_log_level, session_mcp_apps_call_tool_result, session_metadata_snapshot, session_mode, session_model_list, session_model_price_category, session_open_options, session_open_options_additional_content_exclusion_policy, session_open_options_additional_content_exclusion_policy_rule, session_open_options_additional_content_exclusion_policy_rule_source, session_open_options_additional_content_exclusion_policy_scope, session_open_options_env_value_mode, session_open_options_reasoning_summary, session_open_params, session_open_result, session_prune_result, sessions_bulk_delete_request, sessions_check_in_use_request, sessions_check_in_use_result, sessions_close_request, sessions_close_result, sessions_enrich_metadata_request, session_set_credentials_params, session_set_credentials_result, session_settings_built_in_tool_availability_snapshot, session_settings_evaluate_predicate_request, session_settings_evaluate_predicate_result, session_settings_job_snapshot, session_settings_model_snapshot, session_settings_online_evaluation_snapshot, session_settings_predicate_name, session_settings_repo_snapshot, session_settings_snapshot, session_settings_validation_snapshot, sessions_find_by_prefix_request, sessions_find_by_prefix_result, sessions_find_by_task_id_request, sessions_find_by_task_id_result, sessions_fork_request, sessions_fork_result, sessions_get_board_entry_count_request, sessions_get_board_entry_count_result, sessions_get_event_file_path_request, sessions_get_event_file_path_result, sessions_get_last_for_context_request, sessions_get_last_for_context_result, sessions_get_persisted_remote_steerable_request, sessions_get_persisted_remote_steerable_result, session_sizes, sessions_list_request, sessions_load_deferred_repo_hooks_request, sessions_open_attach, sessions_open_cloud, sessions_open_create, sessions_open_handoff, sessions_open_handoff_task_type, sessions_open_progress, sessions_open_progress_status, sessions_open_progress_step, sessions_open_remote, sessions_open_resume, sessions_open_resume_last, sessions_open_status, session_source, sessions_prune_old_request, sessions_register_extension_tools_on_session_options, sessions_release_lock_request, sessions_release_lock_result, sessions_reload_plugin_hooks_request, sessions_reload_plugin_hooks_result, sessions_save_request, sessions_save_result, sessions_set_additional_plugins_request, sessions_set_additional_plugins_result, sessions_set_remote_control_steering_request, sessions_start_remote_control_request, sessions_stop_remote_control_request, sessions_transfer_remote_control_request, session_telemetry_engagement, session_update_options_params, session_update_options_result, session_visibility_status, session_working_directory_context, session_working_directory_context_host_type, shell_cancel_user_requested_request, shell_exec_request, shell_exec_result, shell_execute_user_requested_request, shell_kill_request, shell_kill_result, shell_kill_signal, shutdown_request, skill, skill_discovery_path, skill_discovery_path_list, skill_discovery_scope, skill_list, skills_config_set_disabled_skills_request, skills_disable_request, skills_discover_request, skills_enable_request, skills_get_discovery_paths_request, skills_get_invoked_result, skills_invoked_skill, skills_load_diagnostics, slash_command_agent_prompt_result, slash_command_completed_result, slash_command_info, slash_command_input, slash_command_input_choice, slash_command_input_completion, slash_command_invocation_result, slash_command_kind, slash_command_select_subcommand_option, slash_command_select_subcommand_result, slash_command_text_result, subagent_settings_entry, subagent_settings_entry_context_tier, task_agent_info, task_agent_progress, task_execution_mode, task_info, task_list, task_progress_line, tasks_cancel_request, tasks_cancel_result, tasks_get_current_promotable_result, tasks_get_progress_request, tasks_get_progress_result, task_shell_info, task_shell_info_attachment_mode, task_shell_progress, tasks_promote_current_to_background_result, tasks_promote_to_background_request, tasks_promote_to_background_result, tasks_refresh_result, tasks_remove_request, tasks_remove_result, tasks_send_message_request, tasks_send_message_result, tasks_start_agent_request, tasks_start_agent_result, task_status, tasks_wait_for_pending_result, telemetry_set_feature_overrides_request, token_auth_info, tool, tool_list, tools_get_current_metadata_result, tools_initialize_and_validate_result, tools_list_request, tools_update_subagent_settings_result, ui_auto_mode_switch_response, ui_elicitation_array_any_of_field, ui_elicitation_array_any_of_field_items, ui_elicitation_array_any_of_field_items_any_of, ui_elicitation_array_enum_field, ui_elicitation_array_enum_field_items, ui_elicitation_field_value, ui_elicitation_request, ui_elicitation_response, ui_elicitation_response_action, ui_elicitation_response_content, ui_elicitation_result, ui_elicitation_schema, ui_elicitation_schema_property, ui_elicitation_schema_property_boolean, ui_elicitation_schema_property_number, ui_elicitation_schema_property_number_type, ui_elicitation_schema_property_string, ui_elicitation_schema_property_string_format, ui_elicitation_string_enum_field, ui_elicitation_string_one_of_field, ui_elicitation_string_one_of_field_one_of, ui_ephemeral_query_request, ui_ephemeral_query_result, ui_exit_plan_mode_action, ui_exit_plan_mode_response, ui_handle_pending_auto_mode_switch_request, ui_handle_pending_elicitation_request, ui_handle_pending_exit_plan_mode_request, ui_handle_pending_result, ui_handle_pending_sampling_request, ui_handle_pending_sampling_response, ui_handle_pending_session_limits_exhausted_request, ui_handle_pending_user_input_request, ui_register_direct_auto_mode_switch_handler_result, ui_session_limits_exhausted_response, ui_session_limits_exhausted_response_action, ui_unregister_direct_auto_mode_switch_handler_request, ui_unregister_direct_auto_mode_switch_handler_result, ui_user_input_response, update_subagent_settings_request, usage_get_metrics_result, usage_metrics_code_changes, usage_metrics_model_metric, usage_metrics_model_metric_requests, usage_metrics_model_metric_token_detail, usage_metrics_model_metric_usage, usage_metrics_token_detail, user_auth_info, user_requested_shell_command_result, user_setting_metadata, user_settings_get_result, user_settings_set_request, user_settings_set_result, visibility_get_result, visibility_set_request, visibility_set_result, workspace_diff_file_change, workspace_diff_file_change_type, workspace_diff_mode, workspace_diff_result, workspaces_checkpoints, workspaces_create_file_request, workspaces_diff_request, workspaces_get_workspace_result, workspaces_list_checkpoints_result, workspaces_list_files_result, workspaces_read_checkpoint_request, workspaces_read_checkpoint_result, workspaces_read_file_request, workspaces_read_file_result, workspaces_save_large_paste_request, workspaces_save_large_paste_result, workspace_summary_host_type, workspaces_workspace_details_host_type, session_context_attribution, session_context_info, subagent_settings, task_progress, workspace_summary) + return RPC(abort_request, abort_result, account_all_users, account_get_all_users_result, account_get_current_auth_result, account_get_quota_request, account_get_quota_result, account_login_request, account_login_result, account_logout_request, account_logout_result, account_quota_snapshot, adaptive_thinking_support, agent_discovery_path, agent_discovery_path_list, agent_discovery_path_scope, agent_get_current_result, agent_info, agent_info_source, agent_list, agent_list_request, agent_registry_live_target_entry, agent_registry_live_target_entry_attention_kind, agent_registry_live_target_entry_kind, agent_registry_live_target_entry_last_terminal_event, agent_registry_live_target_entry_status, agent_registry_log_capture, agent_registry_log_capture_open_error_reason, agent_registry_spawn_error, agent_registry_spawn_permission_mode, agent_registry_spawn_registry_timeout, agent_registry_spawn_request, agent_registry_spawn_result, agent_registry_spawn_spawned, agent_registry_spawn_validation_error, agent_registry_spawn_validation_error_field, agent_registry_spawn_validation_error_reason, agent_reload_result, agents_discover_request, agent_select_request, agent_select_result, agent_set_prompt_request, agents_get_discovery_paths_request, allow_all_permission_set_result, allow_all_permission_state, api_key_auth_info, auth_info, auth_info_type, built_in_model_catalog, built_in_model_catalog_entry, cancel_user_requested_shell_command_result, canvas_action, canvas_action_invoke_request, canvas_action_invoke_result, canvas_close_request, canvas_host_context, canvas_host_context_capabilities, canvas_json_schema, canvas_list, canvas_list_open_result, canvas_open_request, canvas_provider_close_request, canvas_provider_invoke_action_request, canvas_provider_open_request, canvas_provider_open_result, canvas_session_context, capi_session_options, command_list, commands_handle_pending_command_request, commands_handle_pending_command_result, commands_invoke_request, commands_list_request, commands_respond_to_queued_command_request, commands_respond_to_queued_command_result, completions_get_trigger_characters_result, completions_request_request, completions_request_result, configure_session_extensions_params, connected_remote_session_metadata, connected_remote_session_metadata_kind, connected_remote_session_metadata_repository, connect_remote_session_params, connect_request, connect_result, content_exclusion_check_paths_request, content_exclusion_check_paths_result, content_exclusion_path_check, content_filter_mode, context_heaviest_message, copilot_api_token_auth_info, copilot_user_response, copilot_user_response_endpoints, copilot_user_response_quota_snapshots, copilot_user_response_quota_snapshots_chat, copilot_user_response_quota_snapshots_completions, copilot_user_response_quota_snapshots_premium_interactions, current_model, current_tool_metadata, debug_collect_logs_collected_entry, debug_collect_logs_destination, debug_collect_logs_entry, debug_collect_logs_entry_kind, debug_collect_logs_include, debug_collect_logs_redaction, debug_collect_logs_request, debug_collect_logs_result, debug_collect_logs_result_kind, debug_collect_logs_skipped_entry, debug_collect_logs_source, disable_bypass_permissions_mode, discovered_canvas, discovered_extension, discovered_extension_mode, discovered_extension_plugin, discovered_extensions, discovered_extensions_disable_request, discovered_extensions_enable_request, discovered_extension_source, discovered_mcp_server, discovered_mcp_server_type, enqueue_command_params, enqueue_command_result, env_auth_info, event_log_read_request, event_log_release_interest_result, event_log_tail_result, event_log_types, events_agent_scope, events_cursor_status, events_read_direction, events_read_result, execute_command_params, execute_command_result, extension, extension_context_push_input, extension_launch_profile, extension_launch_provider_resolve_request, extension_launch_provider_resolve_result, extension_list, extensions_disable_request, extensions_enable_request, extension_source, extension_status, external_tool_result, external_tool_text_result_for_llm, external_tool_text_result_for_llm_binary_results_for_llm, external_tool_text_result_for_llm_binary_results_for_llm_type, external_tool_text_result_for_llm_content, external_tool_text_result_for_llm_content_audio, external_tool_text_result_for_llm_content_image, external_tool_text_result_for_llm_content_resource, external_tool_text_result_for_llm_content_resource_details, external_tool_text_result_for_llm_content_resource_link, external_tool_text_result_for_llm_content_resource_link_icon, external_tool_text_result_for_llm_content_resource_link_icon_theme, external_tool_text_result_for_llm_content_shell_exit, external_tool_text_result_for_llm_content_terminal, external_tool_text_result_for_llm_content_text, factory_abort_request, factory_ack_result, factory_agent_options, factory_agent_request, factory_agent_result, factory_agent_summary, factory_cancel_request, factory_current_phase, factory_declared_limits, factory_durable_operation, factory_execute_request, factory_execute_result, factory_get_run_progress_request, factory_get_run_request, factory_journal_get_request, factory_journal_get_result, factory_journal_put_request, factory_list_runs_request, factory_list_runs_result, factory_log_line, factory_log_line_kind, factory_log_request, factory_phase_observation, factory_phase_status, factory_progress_line, factory_progress_page, factory_resume_request, factory_resume_result, factory_run_consumed, factory_run_detail, factory_run_failure, factory_run_failure_kind, factory_run_limits, factory_run_request, factory_run_result, factory_run_status, factory_run_summary, factory_run_terminal, filter_mapping, fleet_start_request, fleet_start_result, folder_trust_add_params, folder_trust_check_params, folder_trust_check_result, gh_cli_auth_info, git_hub_telemetry_client_info, git_hub_telemetry_event, git_hub_telemetry_notification, handle_pending_tool_call_request, handle_pending_tool_call_result, history_abort_manual_compaction_result, history_cancel_background_compaction_result, history_clear_context_request, history_clear_context_result, history_compact_context_window, history_compact_request, history_compact_result, history_file_restore_skip_reason, history_list_rewind_points_result, history_preview_rewind_request, history_preview_rewind_result, history_rewind_change_type, history_rewind_file_preview, history_rewind_mode, history_rewind_outcome, history_rewind_point, history_rewind_request, history_rewind_result, history_rewind_unavailable_reason, history_skipped_file_restore, history_summarize_for_handoff_result, history_truncate_request, history_truncate_result, hmac_auth_info, hook_invoke_request, hook_invoke_response, hook_type, installed_plugin, installed_plugin_info, installed_plugin_source, installed_plugin_source_git_hub, installed_plugin_source_local, installed_plugin_source_url, instruction_discovery_path, instruction_discovery_path_kind, instruction_discovery_path_list, instruction_discovery_path_location, instructions_discover_request, instructions_get_discovery_paths_request, instructions_get_sources_result, instruction_source, instruction_source_location, instruction_source_type, interrupt_main_turn_request, interrupt_main_turn_result, llm_inference_headers, llm_inference_http_request_chunk_request, llm_inference_http_request_chunk_result, llm_inference_http_request_start_request, llm_inference_http_request_start_result, llm_inference_http_request_start_transport, llm_inference_http_response_chunk_error, llm_inference_http_response_chunk_request, llm_inference_http_response_chunk_result, llm_inference_http_response_start_request, llm_inference_http_response_start_result, llm_inference_set_provider_result, local_session_metadata_value, log_request, log_result, lsp_initialize_request, managed_settings_read_result, marketplace_add_result, marketplace_browse_result, marketplace_info, marketplace_list_result, marketplace_plugin_info, marketplace_refresh_entry, marketplace_refresh_result, marketplace_remove_result, mcp_allowed_server, mcp_apps_call_tool_request, mcp_apps_diagnose_capability, mcp_apps_diagnose_request, mcp_apps_diagnose_result, mcp_apps_diagnose_server, mcp_apps_host_context, mcp_apps_host_context_details, mcp_apps_host_context_details_available_display_mode, mcp_apps_host_context_details_display_mode, mcp_apps_host_context_details_platform, mcp_apps_host_context_details_theme, mcp_apps_list_tools_request, mcp_apps_list_tools_result, mcp_apps_read_resource_request, mcp_apps_read_resource_result, mcp_apps_resource_content, mcp_apps_set_host_context_details, mcp_apps_set_host_context_details_available_display_mode, mcp_apps_set_host_context_details_display_mode, mcp_apps_set_host_context_details_platform, mcp_apps_set_host_context_details_theme, mcp_apps_set_host_context_request, mcp_cancel_sampling_execution_params, mcp_cancel_sampling_execution_result, mcp_config_add_request, mcp_config_disable_request, mcp_config_enable_request, mcp_config_list, mcp_config_remove_request, mcp_config_update_request, mcp_configure_git_hub_request, mcp_configure_git_hub_result, mcp_disable_request, mcp_discover_request, mcp_discover_result, mcp_enable_request, mcp_execute_sampling_params, mcp_execute_sampling_request, mcp_execute_sampling_result, mcp_filtered_server, mcp_headers_handle_pending_headers_refresh_request, mcp_headers_handle_pending_headers_refresh_request_request, mcp_headers_handle_pending_headers_refresh_request_result, mcp_host_state, mcp_is_server_running_request, mcp_is_server_running_result, mcp_list_tools_request, mcp_list_tools_result, mcp_oauth_authentication_state_changed_request, mcp_oauth_handle_pending_request, mcp_oauth_handle_pending_result, mcp_oauth_login_grant_type, mcp_oauth_login_request, mcp_oauth_login_result, mcp_oauth_pending_request_response, mcp_oauth_respond_request, mcp_oauth_respond_result, mcp_register_external_client_request, mcp_reload_with_config_request, mcp_remove_git_hub_result, mcp_resource, mcp_resource_annotations, mcp_resource_content, mcp_resource_icon, mcp_resources_list_request, mcp_resources_list_result, mcp_resources_list_templates_request, mcp_resources_list_templates_result, mcp_resources_read_request, mcp_resources_read_result, mcp_resource_template, mcp_restart_server_request, mcp_sampling_execution_action, mcp_sampling_execution_result, mcp_server, mcp_server_auth_config, mcp_server_auth_config_redirect_port, mcp_server_config, mcp_server_config_defer_tools, mcp_server_config_http, mcp_server_config_http_oauth_grant_type, mcp_server_config_http_type, mcp_server_config_stdio, mcp_server_failure_info, mcp_server_list, mcp_server_needs_auth_info, mcp_set_env_value_mode_details, mcp_set_env_value_mode_params, mcp_set_env_value_mode_result, mcp_start_server_request, mcp_start_servers_result, mcp_stop_server_request, mcp_tools, mcp_tool_ui, mcp_tool_ui_visibility, mcp_unregister_external_client_request, memory_configuration, metadata_context_attribution_result, metadata_context_heaviest_messages_request, metadata_context_heaviest_messages_result, metadata_context_info_request, metadata_context_info_result, metadata_is_processing_result, metadata_recompute_context_tokens_request, metadata_recompute_context_tokens_result, metadata_record_context_change_request, metadata_record_context_change_result, metadata_set_working_directory_request, metadata_set_working_directory_result, metadata_snapshot_current_mode, metadata_snapshot_remote_metadata, metadata_snapshot_remote_metadata_repository, metadata_snapshot_remote_metadata_task_type, model, model_billing, model_billing_promo, model_billing_token_prices, model_billing_token_prices_long_context, model_capabilities, model_capabilities_limits, model_capabilities_limits_vision, model_capabilities_override, model_capabilities_override_limits, model_capabilities_override_limits_vision, model_capabilities_override_supports, model_capabilities_supports, model_list, model_list_request, model_picker_category, model_picker_price_category, model_policy, model_policy_state, model_set_reasoning_effort_request, model_set_reasoning_effort_result, models_list_request, model_switch_to_request, model_switch_to_result, mode_set_request, named_provider_config, name_get_result, name_set_auto_request, name_set_auto_result, name_set_request, open_canvas_instance, options_update_additional_content_exclusion_policy, options_update_additional_content_exclusion_policy_rule, options_update_additional_content_exclusion_policy_rule_source, options_update_additional_content_exclusion_policy_scope, options_update_context_tier, options_update_env_value_mode, options_update_reasoning_summary, options_update_tool_filter_precedence, pending_permission_request, pending_permission_request_list, permission_decision, permission_decision_approved, permission_decision_approved_for_location, permission_decision_approved_for_session, permission_decision_approve_for_location, permission_decision_approve_for_location_approval, permission_decision_approve_for_location_approval_commands, permission_decision_approve_for_location_approval_custom_tool, permission_decision_approve_for_location_approval_extension_management, permission_decision_approve_for_location_approval_extension_permission_access, permission_decision_approve_for_location_approval_factory, permission_decision_approve_for_location_approval_mcp, permission_decision_approve_for_location_approval_mcp_sampling, permission_decision_approve_for_location_approval_memory, permission_decision_approve_for_location_approval_read, permission_decision_approve_for_location_approval_write, permission_decision_approve_for_session, permission_decision_approve_for_session_approval, permission_decision_approve_for_session_approval_commands, permission_decision_approve_for_session_approval_custom_tool, permission_decision_approve_for_session_approval_extension_management, permission_decision_approve_for_session_approval_extension_permission_access, permission_decision_approve_for_session_approval_factory, permission_decision_approve_for_session_approval_mcp, permission_decision_approve_for_session_approval_mcp_sampling, permission_decision_approve_for_session_approval_memory, permission_decision_approve_for_session_approval_read, permission_decision_approve_for_session_approval_write, permission_decision_approve_once, permission_decision_approve_permanently, permission_decision_cancelled, permission_decision_context, permission_decision_denied_by_content_exclusion_policy, permission_decision_denied_by_permission_request_hook, permission_decision_denied_by_rules, permission_decision_denied_interactively_by_user, permission_decision_denied_no_approval_rule_and_could_not_request_from_user, permission_decision_outcome, permission_decision_reject, permission_decision_request, permission_decision_source, permission_decision_surface, permission_decision_user_not_available, permission_location_add_tool_approval_params, permission_location_apply_params, permission_location_apply_result, permission_location_resolve_params, permission_location_resolve_result, permission_location_type, permission_paths_add_params, permission_paths_allowed_check_params, permission_paths_allowed_check_result, permission_paths_config, permission_paths_list, permission_paths_update_primary_params, permission_paths_workspace_check_params, permission_paths_workspace_check_result, permission_prompt_shown_notification, permission_request_result, permission_rules_set, permissions_allow_all_mode, permissions_configure_additional_content_exclusion_policy, permissions_configure_additional_content_exclusion_policy_rule, permissions_configure_additional_content_exclusion_policy_rule_source, permissions_configure_additional_content_exclusion_policy_scope, permissions_configure_params, permissions_configure_result, permissions_folder_trust_add_trusted_result, permissions_get_allow_all_request, permissions_locations_add_tool_approval_details, permissions_locations_add_tool_approval_details_commands, permissions_locations_add_tool_approval_details_custom_tool, permissions_locations_add_tool_approval_details_extension_management, permissions_locations_add_tool_approval_details_extension_permission_access, permissions_locations_add_tool_approval_details_factory, permissions_locations_add_tool_approval_details_mcp, permissions_locations_add_tool_approval_details_mcp_sampling, permissions_locations_add_tool_approval_details_memory, permissions_locations_add_tool_approval_details_read, permissions_locations_add_tool_approval_details_write, permissions_locations_add_tool_approval_result, permissions_modify_rules_params, permissions_modify_rules_result, permissions_modify_rules_scope, permissions_notify_prompt_shown_result, permissions_paths_add_result, permissions_paths_list_request, permissions_paths_update_primary_result, permissions_pending_requests_request, permissions_reset_session_approvals_request, permissions_reset_session_approvals_result, permissions_set_allow_all_request, permissions_set_allow_all_source, permissions_set_approve_all_request, permissions_set_approve_all_result, permissions_set_approve_all_source, permissions_set_required_request, permissions_set_required_result, permissions_urls_set_unrestricted_mode_result, permission_urls_config, permission_urls_set_unrestricted_mode_params, ping_request, ping_result, plan_read_result, plan_read_sql_todos_result, plan_read_sql_todos_with_dependencies_result, plan_sql_todo_dependency, plan_sql_todos_row, plan_update_request, plugin, plugin_install_result, plugin_list, plugin_list_result, plugins_disable_request, plugins_enable_request, plugins_install_request, plugins_marketplaces_add_request, plugins_marketplaces_browse_request, plugins_marketplaces_refresh_request, plugins_marketplaces_remove_request, plugins_reload_request, plugins_uninstall_request, plugins_update_request, plugin_update_all_entry, plugin_update_all_result, plugin_update_result, provider_add_request, provider_add_result, provider_config, provider_config_azure, provider_config_transport, provider_config_type, provider_config_wire_api, provider_endpoint, provider_endpoint_transport, provider_endpoint_type, provider_endpoint_wire_api, provider_get_endpoint_request, provider_model_config, provider_session_token, provider_token_acquire_request, provider_token_acquire_result, push_attachment, push_attachment_blob, push_attachment_directory, push_attachment_file, push_attachment_file_line_range, push_attachment_git_hub_actions_job, push_attachment_git_hub_commit, push_attachment_git_hub_file, push_attachment_git_hub_file_diff, push_attachment_git_hub_file_diff_side, push_attachment_git_hub_reference, push_attachment_git_hub_reference_type, push_attachment_git_hub_release, push_attachment_git_hub_repository, push_attachment_git_hub_snippet, push_attachment_git_hub_tree_comparison, push_attachment_git_hub_tree_comparison_side, push_attachment_git_hub_url, push_attachment_selection, push_attachment_selection_details, push_attachment_selection_details_end, push_attachment_selection_details_start, push_git_hub_repo_ref, queue_begin_deferred_idle_drain_request, queue_begin_deferred_idle_drain_result, queue_consume_system_notifications_request, queued_command_handled, queued_command_not_handled, queued_command_result, queue_defer_session_idle_request, queue_duplicate_at_request, queue_duplicate_at_result, queue_enqueue_resume_pending_result, queue_finish_deferred_idle_drain_request, queue_finish_deferred_idle_drain_result, queue_has_pending_result, queue_insert_at_request, queue_insert_at_result, queue_insert_message, queue_move_item_request, queue_move_item_result, queue_pending_items, queue_pending_items_kind, queue_pending_items_result, queue_remove_at_request, queue_remove_at_result, queue_remove_most_recent_result, queue_send_now_request, queue_send_now_result, queue_set_drain_paused_request, queue_snapshot_result, queue_update_text_request, queue_update_text_result, register_event_interest_params, register_event_interest_result, register_extension_tools_params, register_extension_tools_result, release_event_interest_params, remote_control_config, remote_control_config_existing_mc_session, remote_control_status, remote_control_status_active, remote_control_status_connecting, remote_control_status_error, remote_control_status_off, remote_control_status_result, remote_control_stop_result, remote_control_transfer_result, remote_enable_request, remote_enable_result, remote_notify_steerable_changed_request, remote_notify_steerable_changed_result, remote_session_connection_result, remote_session_metadata_repository, remote_session_metadata_task_type, remote_session_metadata_value, remote_session_mode, remote_session_repository, run_options, sandbox_config, sandbox_config_auth, sandbox_config_user_policy, sandbox_config_user_policy_experimental, sandbox_config_user_policy_experimental_seatbelt, sandbox_config_user_policy_filesystem, sandbox_config_user_policy_network, sandbox_config_user_policy_network_proxy, sandbox_config_user_policy_seatbelt, schedule_add_at_request, schedule_add_cron_request, schedule_add_request, schedule_add_result, schedule_add_self_paced_request, schedule_entry, schedule_has_self_paced_result, schedule_list, schedule_rearm_self_paced_request, schedule_stop_request, schedule_stop_result, secrets_add_filter_values_request, secrets_add_filter_values_result, send_agent_mode, send_attachments_to_message_params, send_message_item, send_messages_request, send_messages_result, send_mode, send_request, send_result, send_system_notification_request, server_agent_list, server_instruction_source_list, server_skill, server_skill_list, session_activity, session_agent_list_request, session_auth_status, session_bulk_delete_result, session_cancel_all_background_agents_result, session_capability, session_commands_list_request, session_completion_item, session_context, session_context_host_type, session_enrich_metadata_result, session_fs_append_file_request, session_fs_error, session_fs_error_code, session_fs_exists_request, session_fs_exists_result, session_fs_mkdir_request, session_fs_readdir_request, session_fs_readdir_result, session_fs_readdir_with_types_entry, session_fs_readdir_with_types_entry_type, session_fs_readdir_with_types_request, session_fs_readdir_with_types_result, session_fs_read_file_request, session_fs_read_file_result, session_fs_rename_request, session_fs_rm_request, session_fs_set_provider_capabilities, session_fs_set_provider_conventions, session_fs_set_provider_request, session_fs_set_provider_result, session_fs_sqlite_exists_request, session_fs_sqlite_exists_result, session_fs_sqlite_query_request, session_fs_sqlite_query_result, session_fs_sqlite_query_type, session_fs_sqlite_transaction_error, session_fs_sqlite_transaction_error_class, session_fs_sqlite_transaction_request, session_fs_sqlite_transaction_result, session_fs_sqlite_transaction_statement, session_fs_stat_request, session_fs_stat_result, session_fs_write_file_request, session_history_compact_request, session_installed_plugin, session_installed_plugin_source, session_installed_plugin_source_git_hub, session_installed_plugin_source_local, session_installed_plugin_source_url, session_limit_prediction_baseline_data, session_limit_prediction_client_type, session_limit_prediction_details, session_limit_prediction_predict_request, session_limit_prediction_request, session_limit_prediction_result, session_limit_prediction_source, session_limit_prediction_tier, session_limit_prediction_tier_option, session_limit_prediction_unavailable_reason, session_list, session_list_entry, session_list_filter, session_load_deferred_repo_hooks_result, session_log_level, session_managed_permissions, session_managed_settings, session_mcp_apps_call_tool_result, session_metadata_snapshot, session_mode, session_model_list, session_model_list_request, session_model_price_category, session_open_options, session_open_options_additional_content_exclusion_policy, session_open_options_additional_content_exclusion_policy_rule, session_open_options_additional_content_exclusion_policy_rule_source, session_open_options_additional_content_exclusion_policy_scope, session_open_options_env_value_mode, session_open_options_reasoning_summary, session_open_params, session_open_result, session_plugins_reload_request, session_provider_get_endpoint_request, session_prune_result, sessions_bulk_delete_request, sessions_check_in_use_request, sessions_check_in_use_result, sessions_close_request, sessions_close_result, sessions_delete_request, sessions_enrich_metadata_request, session_set_credentials_params, session_set_credentials_result, session_settings_built_in_tool_availability_snapshot, session_settings_evaluate_predicate_request, session_settings_evaluate_predicate_result, session_settings_job_snapshot, session_settings_model_snapshot, session_settings_online_evaluation_snapshot, session_settings_predicate_name, session_settings_repo_snapshot, session_settings_snapshot, session_settings_validation_snapshot, sessions_find_by_prefix_request, sessions_find_by_prefix_result, sessions_find_by_task_id_request, sessions_find_by_task_id_result, sessions_fork_request, sessions_fork_result, sessions_get_board_entry_count_request, sessions_get_board_entry_count_result, sessions_get_event_file_path_request, sessions_get_event_file_path_result, sessions_get_last_for_context_request, sessions_get_last_for_context_result, sessions_get_metadata_request, sessions_get_metadata_result, sessions_get_persisted_remote_steerable_request, sessions_get_persisted_remote_steerable_result, session_sizes, sessions_list_non_empty_session_ids_request, sessions_list_non_empty_session_ids_result, sessions_list_request, sessions_load_deferred_repo_hooks_request, sessions_open_attach, sessions_open_cloud, sessions_open_create, sessions_open_handoff, sessions_open_handoff_task_type, sessions_open_progress, sessions_open_progress_status, sessions_open_progress_step, sessions_open_remote, sessions_open_resume, sessions_open_resume_last, sessions_open_status, session_source, sessions_prune_old_request, sessions_register_extension_tools_on_session_options, sessions_release_lock_request, sessions_release_lock_result, sessions_reload_plugin_hooks_request, sessions_reload_plugin_hooks_result, sessions_save_request, sessions_save_result, sessions_set_additional_plugins_request, sessions_set_additional_plugins_result, sessions_set_remote_control_steering_request, sessions_start_remote_control_request, sessions_stop_remote_control_request, sessions_transfer_remote_control_request, session_telemetry_engagement, session_update_options_params, session_update_options_result, session_visibility_status, session_working_directory_context, session_working_directory_context_host_type, shell_cancel_user_requested_request, shell_exec_request, shell_exec_result, shell_execute_user_requested_request, shell_init_profile, shell_init_script, shell_init_script_shell, shell_kill_request, shell_kill_result, shell_kill_signal, shell_options, shutdown_request, skill, skill_discovery_path, skill_discovery_path_list, skill_discovery_scope, skill_list, skills_config_set_disabled_skills_request, skills_disable_request, skills_discover_request, skills_enable_request, skills_get_discovery_paths_request, skills_get_invoked_result, skills_invoked_skill, skills_load_diagnostics, slash_command_agent_prompt_result, slash_command_completed_result, slash_command_info, slash_command_input, slash_command_input_choice, slash_command_input_completion, slash_command_invocation_result, slash_command_kind, slash_command_select_subcommand_option, slash_command_select_subcommand_result, slash_command_text_result, subagent_settings_entry, subagent_settings_entry_context_tier, task_agent_info, task_agent_progress, task_execution_mode, task_info, task_list, task_progress_line, tasks_cancel_request, tasks_cancel_result, tasks_get_current_promotable_result, tasks_get_progress_request, tasks_get_progress_result, task_shell_info, task_shell_info_attachment_mode, task_shell_progress, tasks_promote_current_to_background_result, tasks_promote_to_background_request, tasks_promote_to_background_result, tasks_refresh_result, tasks_remove_request, tasks_remove_result, tasks_send_message_request, tasks_send_message_result, tasks_start_agent_request, tasks_start_agent_result, task_status, tasks_wait_for_pending_result, telemetry_set_feature_overrides_request, token_auth_info, tool, tool_list, tools_get_current_metadata_result, tools_initialize_and_validate_result, tools_list_request, tools_update_subagent_settings_result, ui_auto_mode_switch_response, ui_elicitation_array_any_of_field, ui_elicitation_array_any_of_field_items, ui_elicitation_array_any_of_field_items_any_of, ui_elicitation_array_enum_field, ui_elicitation_array_enum_field_items, ui_elicitation_field_value, ui_elicitation_request, ui_elicitation_response, ui_elicitation_response_action, ui_elicitation_response_content, ui_elicitation_result, ui_elicitation_schema, ui_elicitation_schema_property, ui_elicitation_schema_property_boolean, ui_elicitation_schema_property_number, ui_elicitation_schema_property_number_type, ui_elicitation_schema_property_string, ui_elicitation_schema_property_string_format, ui_elicitation_string_enum_field, ui_elicitation_string_one_of_field, ui_elicitation_string_one_of_field_one_of, ui_ephemeral_query_request, ui_ephemeral_query_result, ui_exit_plan_mode_action, ui_exit_plan_mode_response, ui_handle_pending_auto_mode_switch_request, ui_handle_pending_elicitation_request, ui_handle_pending_exit_plan_mode_request, ui_handle_pending_result, ui_handle_pending_sampling_request, ui_handle_pending_sampling_response, ui_handle_pending_session_limits_exhausted_request, ui_handle_pending_user_input_request, ui_register_direct_auto_mode_switch_handler_result, ui_session_limits_exhausted_response, ui_session_limits_exhausted_response_action, ui_unregister_direct_auto_mode_switch_handler_request, ui_unregister_direct_auto_mode_switch_handler_result, ui_user_input_response, update_subagent_settings_request, usage_get_metrics_result, usage_metrics_code_changes, usage_metrics_model_metric, usage_metrics_model_metric_requests, usage_metrics_model_metric_token_detail, usage_metrics_model_metric_usage, usage_metrics_token_detail, user_auth_info, user_requested_shell_command_result, user_setting_metadata, user_settings_get_result, user_settings_set_request, user_settings_set_result, visibility_get_result, visibility_set_request, visibility_set_result, workspace_diff_file_change, workspace_diff_file_change_type, workspace_diff_mode, workspace_diff_result, workspaces_add_summary_request, workspaces_add_summary_result, workspaces_autopilot_objective_exists_result, workspaces_checkpoints, workspaces_create_file_request, workspaces_delete_autopilot_objective_result, workspaces_diff_request, workspaces_ensure_request, workspaces_get_workspace_result, workspaces_list_checkpoints_result, workspaces_list_files_result, workspaces_read_autopilot_objective_result, workspaces_read_checkpoint_request, workspaces_read_checkpoint_result, workspaces_read_file_request, workspaces_read_file_result, workspaces_save_large_paste_request, workspaces_save_large_paste_result, workspaces_truncate_summaries_request, workspace_summary_host_type, workspaces_update_metadata_request, workspaces_workspace_details_host_type, workspaces_write_autopilot_objective_request, workspaces_write_autopilot_objective_result, session_context_attribution, session_context_info, subagent_settings, task_progress, workspace_summary) def to_dict(self) -> dict: result: dict = {} @@ -26731,6 +30865,7 @@ def to_dict(self) -> dict: result["AgentInfo"] = to_class(AgentInfo, self.agent_info) result["AgentInfoSource"] = to_enum(AgentInfoSource, self.agent_info_source) result["AgentList"] = to_class(AgentList, self.agent_list) + result["AgentListRequest"] = self.agent_list_request result["AgentRegistryLiveTargetEntry"] = to_class(AgentRegistryLiveTargetEntry, self.agent_registry_live_target_entry) result["AgentRegistryLiveTargetEntryAttentionKind"] = to_enum(AgentRegistryLiveTargetEntryAttentionKind, self.agent_registry_live_target_entry_attention_kind) result["AgentRegistryLiveTargetEntryKind"] = to_enum(AgentRegistryLiveTargetEntryKind, self.agent_registry_live_target_entry_kind) @@ -26751,12 +30886,15 @@ def to_dict(self) -> dict: result["AgentsDiscoverRequest"] = to_class(AgentsDiscoverRequest, self.agents_discover_request) result["AgentSelectRequest"] = to_class(AgentSelectRequest, self.agent_select_request) result["AgentSelectResult"] = to_class(AgentSelectResult, self.agent_select_result) + result["AgentSetPromptRequest"] = to_class(AgentSetPromptRequest, self.agent_set_prompt_request) result["AgentsGetDiscoveryPathsRequest"] = to_class(AgentsGetDiscoveryPathsRequest, self.agents_get_discovery_paths_request) result["AllowAllPermissionSetResult"] = to_class(AllowAllPermissionSetResult, self.allow_all_permission_set_result) result["AllowAllPermissionState"] = to_class(AllowAllPermissionState, self.allow_all_permission_state) result["ApiKeyAuthInfo"] = to_class(APIKeyAuthInfo, self.api_key_auth_info) result["AuthInfo"] = (self.auth_info).to_dict() result["AuthInfoType"] = to_enum(AuthInfoType, self.auth_info_type) + result["BuiltInModelCatalog"] = to_class(BuiltInModelCatalog, self.built_in_model_catalog) + result["BuiltInModelCatalogEntry"] = to_class(BuiltInModelCatalogEntry, self.built_in_model_catalog_entry) result["CancelUserRequestedShellCommandResult"] = to_class(CancelUserRequestedShellCommandResult, self.cancel_user_requested_shell_command_result) result["CanvasAction"] = to_class(CanvasAction, self.canvas_action) result["CanvasActionInvokeRequest"] = to_class(CanvasActionInvokeRequest, self.canvas_action_invoke_request) @@ -26778,7 +30916,7 @@ def to_dict(self) -> dict: result["CommandsHandlePendingCommandRequest"] = to_class(CommandsHandlePendingCommandRequest, self.commands_handle_pending_command_request) result["CommandsHandlePendingCommandResult"] = to_class(CommandsHandlePendingCommandResult, self.commands_handle_pending_command_result) result["CommandsInvokeRequest"] = to_class(CommandsInvokeRequest, self.commands_invoke_request) - result["CommandsListRequest"] = to_class(CommandsListRequest, self.commands_list_request) + result["CommandsListRequest"] = self.commands_list_request result["CommandsRespondToQueuedCommandRequest"] = to_class(CommandsRespondToQueuedCommandRequest, self.commands_respond_to_queued_command_request) result["CommandsRespondToQueuedCommandResult"] = to_class(CommandsRespondToQueuedCommandResult, self.commands_respond_to_queued_command_result) result["CompletionsGetTriggerCharactersResult"] = to_class(CompletionsGetTriggerCharactersResult, self.completions_get_trigger_characters_result) @@ -26791,6 +30929,9 @@ def to_dict(self) -> dict: result["ConnectRemoteSessionParams"] = to_class(ConnectRemoteSessionParams, self.connect_remote_session_params) result["ConnectRequest"] = to_class(_ConnectRequest, self.connect_request) result["ConnectResult"] = to_class(_ConnectResult, self.connect_result) + result["ContentExclusionCheckPathsRequest"] = to_class(ContentExclusionCheckPathsRequest, self.content_exclusion_check_paths_request) + result["ContentExclusionCheckPathsResult"] = to_class(ContentExclusionCheckPathsResult, self.content_exclusion_check_paths_result) + result["ContentExclusionPathCheck"] = to_class(ContentExclusionPathCheck, self.content_exclusion_path_check) result["ContentFilterMode"] = to_enum(ContentFilterMode, self.content_filter_mode) result["ContextHeaviestMessage"] = to_class(ContextHeaviestMessage, self.context_heaviest_message) result["CopilotApiTokenAuthInfo"] = to_class(CopilotAPITokenAuthInfo, self.copilot_api_token_auth_info) @@ -26813,7 +30954,15 @@ def to_dict(self) -> dict: result["DebugCollectLogsResultKind"] = to_enum(DebugCollectLogsResultKind, self.debug_collect_logs_result_kind) result["DebugCollectLogsSkippedEntry"] = to_class(DebugCollectLogsSkippedEntry, self.debug_collect_logs_skipped_entry) result["DebugCollectLogsSource"] = to_enum(DebugCollectLogsSource, self.debug_collect_logs_source) + result["DisableBypassPermissionsMode"] = to_enum(DisableBypassPermissionsMode, self.disable_bypass_permissions_mode) result["DiscoveredCanvas"] = to_class(DiscoveredCanvas, self.discovered_canvas) + result["DiscoveredExtension"] = to_class(DiscoveredExtension, self.discovered_extension) + result["DiscoveredExtensionMode"] = to_enum(DiscoveredExtensionMode, self.discovered_extension_mode) + result["DiscoveredExtensionPlugin"] = to_class(DiscoveredExtensionPlugin, self.discovered_extension_plugin) + result["DiscoveredExtensions"] = to_class(DiscoveredExtensions, self.discovered_extensions) + result["DiscoveredExtensionsDisableRequest"] = to_class(DiscoveredExtensionsDisableRequest, self.discovered_extensions_disable_request) + result["DiscoveredExtensionsEnableRequest"] = to_class(DiscoveredExtensionsEnableRequest, self.discovered_extensions_enable_request) + result["DiscoveredExtensionSource"] = to_enum(DiscoveredExtensionSource, self.discovered_extension_source) result["DiscoveredMcpServer"] = to_class(DiscoveredMCPServer, self.discovered_mcp_server) result["DiscoveredMcpServerType"] = to_enum(DiscoveredMCPServerType, self.discovered_mcp_server_type) result["EnqueueCommandParams"] = to_class(EnqueueCommandParams, self.enqueue_command_params) @@ -26825,11 +30974,15 @@ def to_dict(self) -> dict: result["EventLogTypes"] = from_union([lambda x: from_list(from_str, x), lambda x: to_enum(EventLogTypes, x)], self.event_log_types) result["EventsAgentScope"] = to_enum(EventsAgentScope, self.events_agent_scope) result["EventsCursorStatus"] = to_enum(EventsCursorStatus, self.events_cursor_status) + result["EventsReadDirection"] = to_enum(EventsReadDirection, self.events_read_direction) result["EventsReadResult"] = to_class(EventsReadResult, self.events_read_result) result["ExecuteCommandParams"] = to_class(ExecuteCommandParams, self.execute_command_params) result["ExecuteCommandResult"] = to_class(ExecuteCommandResult, self.execute_command_result) result["Extension"] = to_class(Extension, self.extension) result["ExtensionContextPushInput"] = to_class(ExtensionContextPushInput, self.extension_context_push_input) + result["ExtensionLaunchProfile"] = to_class(ExtensionLaunchProfile, self.extension_launch_profile) + result["ExtensionLaunchProviderResolveRequest"] = to_class(ExtensionLaunchProviderResolveRequest, self.extension_launch_provider_resolve_request) + result["ExtensionLaunchProviderResolveResult"] = to_class(ExtensionLaunchProviderResolveResult, self.extension_launch_provider_resolve_result) result["ExtensionList"] = to_class(ExtensionList, self.extension_list) result["ExtensionsDisableRequest"] = to_class(ExtensionsDisableRequest, self.extensions_disable_request) result["ExtensionsEnableRequest"] = to_class(ExtensionsEnableRequest, self.extensions_enable_request) @@ -26855,22 +31008,39 @@ def to_dict(self) -> dict: result["FactoryAgentOptions"] = to_class(FactoryAgentOptions, self.factory_agent_options) result["FactoryAgentRequest"] = to_class(FactoryAgentRequest, self.factory_agent_request) result["FactoryAgentResult"] = to_class(FactoryAgentResult, self.factory_agent_result) + result["FactoryAgentSummary"] = to_class(FactoryAgentSummary, self.factory_agent_summary) result["FactoryCancelRequest"] = to_class(FactoryCancelRequest, self.factory_cancel_request) + result["FactoryCurrentPhase"] = to_class(FactoryCurrentPhase, self.factory_current_phase) + result["FactoryDeclaredLimits"] = to_class(FactoryDeclaredLimits, self.factory_declared_limits) + result["FactoryDurableOperation"] = to_enum(FactoryDurableOperation, self.factory_durable_operation) result["FactoryExecuteRequest"] = to_class(FactoryExecuteRequest, self.factory_execute_request) result["FactoryExecuteResult"] = to_class(FactoryExecuteResult, self.factory_execute_result) + result["FactoryGetRunProgressRequest"] = to_class(FactoryGetRunProgressRequest, self.factory_get_run_progress_request) result["FactoryGetRunRequest"] = to_class(FactoryGetRunRequest, self.factory_get_run_request) result["FactoryJournalGetRequest"] = to_class(FactoryJournalGetRequest, self.factory_journal_get_request) result["FactoryJournalGetResult"] = to_class(FactoryJournalGetResult, self.factory_journal_get_result) result["FactoryJournalPutRequest"] = to_class(FactoryJournalPutRequest, self.factory_journal_put_request) + result["FactoryListRunsRequest"] = to_class(FactoryListRunsRequest, self.factory_list_runs_request) + result["FactoryListRunsResult"] = to_class(FactoryListRunsResult, self.factory_list_runs_result) result["FactoryLogLine"] = to_class(FactoryLogLine, self.factory_log_line) result["FactoryLogLineKind"] = to_enum(FactoryLogLineKind, self.factory_log_line_kind) result["FactoryLogRequest"] = to_class(FactoryLogRequest, self.factory_log_request) + result["FactoryPhaseObservation"] = to_class(FactoryPhaseObservation, self.factory_phase_observation) + result["FactoryPhaseStatus"] = to_enum(FactoryPhaseStatus, self.factory_phase_status) + result["FactoryProgressLine"] = to_class(FactoryProgressLine, self.factory_progress_line) + result["FactoryProgressPage"] = to_class(FactoryProgressPage, self.factory_progress_page) + result["FactoryResumeRequest"] = to_class(FactoryResumeRequest, self.factory_resume_request) + result["FactoryResumeResult"] = to_class(FactoryResumeResult, self.factory_resume_result) + result["FactoryRunConsumed"] = to_class(FactoryRunConsumed, self.factory_run_consumed) + result["FactoryRunDetail"] = to_class(FactoryRunDetail, self.factory_run_detail) result["FactoryRunFailure"] = to_class(FactoryRunFailure, self.factory_run_failure) result["FactoryRunFailureKind"] = to_enum(FactoryRunFailureKind, self.factory_run_failure_kind) result["FactoryRunLimits"] = to_class(FactoryRunLimits, self.factory_run_limits) result["FactoryRunRequest"] = to_class(FactoryRunRequest, self.factory_run_request) result["FactoryRunResult"] = to_class(FactoryRunResult, self.factory_run_result) result["FactoryRunStatus"] = to_enum(FactoryRunStatus, self.factory_run_status) + result["FactoryRunSummary"] = to_class(FactoryRunSummary, self.factory_run_summary) + result["FactoryRunTerminal"] = to_class(FactoryRunTerminal, self.factory_run_terminal) result["FilterMapping"] = from_union([lambda x: from_dict(lambda x: to_enum(ContentFilterMode, x), x), lambda x: to_enum(ContentFilterMode, x)], self.filter_mapping) result["FleetStartRequest"] = to_class(FleetStartRequest, self.fleet_start_request) result["FleetStartResult"] = to_class(FleetStartResult, self.fleet_start_result) @@ -26885,9 +31055,24 @@ def to_dict(self) -> dict: result["HandlePendingToolCallResult"] = to_class(HandlePendingToolCallResult, self.handle_pending_tool_call_result) result["HistoryAbortManualCompactionResult"] = to_class(HistoryAbortManualCompactionResult, self.history_abort_manual_compaction_result) result["HistoryCancelBackgroundCompactionResult"] = to_class(HistoryCancelBackgroundCompactionResult, self.history_cancel_background_compaction_result) + result["HistoryClearContextRequest"] = to_class(HistoryClearContextRequest, self.history_clear_context_request) + result["HistoryClearContextResult"] = to_class(HistoryClearContextResult, self.history_clear_context_result) result["HistoryCompactContextWindow"] = to_class(HistoryCompactContextWindow, self.history_compact_context_window) - result["HistoryCompactRequest"] = to_class(HistoryCompactRequest, self.history_compact_request) + result["HistoryCompactRequest"] = self.history_compact_request result["HistoryCompactResult"] = to_class(HistoryCompactResult, self.history_compact_result) + result["HistoryFileRestoreSkipReason"] = to_enum(HistoryFileRestoreSkipReason, self.history_file_restore_skip_reason) + result["HistoryListRewindPointsResult"] = to_class(HistoryListRewindPointsResult, self.history_list_rewind_points_result) + result["HistoryPreviewRewindRequest"] = to_class(HistoryPreviewRewindRequest, self.history_preview_rewind_request) + result["HistoryPreviewRewindResult"] = to_class(HistoryPreviewRewindResult, self.history_preview_rewind_result) + result["HistoryRewindChangeType"] = to_enum(HistoryRewindChangeType, self.history_rewind_change_type) + result["HistoryRewindFilePreview"] = to_class(HistoryRewindFilePreview, self.history_rewind_file_preview) + result["HistoryRewindMode"] = to_enum(HistoryRewindMode, self.history_rewind_mode) + result["HistoryRewindOutcome"] = to_enum(HistoryRewindOutcome, self.history_rewind_outcome) + result["HistoryRewindPoint"] = to_class(HistoryRewindPoint, self.history_rewind_point) + result["HistoryRewindRequest"] = to_class(HistoryRewindRequest, self.history_rewind_request) + result["HistoryRewindResult"] = to_class(HistoryRewindResult, self.history_rewind_result) + result["HistoryRewindUnavailableReason"] = to_enum(HistoryRewindUnavailableReason, self.history_rewind_unavailable_reason) + result["HistorySkippedFileRestore"] = to_class(HistorySkippedFileRestore, self.history_skipped_file_restore) result["HistorySummarizeForHandoffResult"] = to_class(HistorySummarizeForHandoffResult, self.history_summarize_for_handoff_result) result["HistoryTruncateRequest"] = to_class(HistoryTruncateRequest, self.history_truncate_request) result["HistoryTruncateResult"] = to_class(HistoryTruncateResult, self.history_truncate_result) @@ -26911,6 +31096,8 @@ def to_dict(self) -> dict: result["InstructionSource"] = to_class(InstructionSource, self.instruction_source) result["InstructionSourceLocation"] = to_enum(InstructionLocation, self.instruction_source_location) result["InstructionSourceType"] = to_enum(InstructionSourceType, self.instruction_source_type) + result["InterruptMainTurnRequest"] = to_class(InterruptMainTurnRequest, self.interrupt_main_turn_request) + result["InterruptMainTurnResult"] = to_class(InterruptMainTurnResult, self.interrupt_main_turn_result) result["LlmInferenceHeaders"] = from_dict(lambda x: from_list(from_str, x), self.llm_inference_headers) result["LlmInferenceHttpRequestChunkRequest"] = to_class(LlmInferenceHTTPRequestChunkRequest, self.llm_inference_http_request_chunk_request) result["LlmInferenceHttpRequestChunkResult"] = to_class(LlmInferenceHTTPRequestChunkResult, self.llm_inference_http_request_chunk_result) @@ -26927,6 +31114,7 @@ def to_dict(self) -> dict: result["LogRequest"] = to_class(LogRequest, self.log_request) result["LogResult"] = to_class(LogResult, self.log_result) result["LspInitializeRequest"] = to_class(LspInitializeRequest, self.lsp_initialize_request) + result["ManagedSettingsReadResult"] = to_class(ManagedSettingsReadResult, self.managed_settings_read_result) result["MarketplaceAddResult"] = to_class(MarketplaceAddResult, self.marketplace_add_result) result["MarketplaceBrowseResult"] = to_class(MarketplaceBrowseResult, self.marketplace_browse_result) result["MarketplaceInfo"] = to_class(MarketplaceInfo, self.marketplace_info) @@ -26984,12 +31172,15 @@ def to_dict(self) -> dict: result["McpIsServerRunningResult"] = to_class(MCPIsServerRunningResult, self.mcp_is_server_running_result) result["McpListToolsRequest"] = to_class(MCPListToolsRequest, self.mcp_list_tools_request) result["McpListToolsResult"] = to_class(MCPListToolsResult, self.mcp_list_tools_result) + result["McpOauthAuthenticationStateChangedRequest"] = to_class(MCPOauthAuthenticationStateChangedRequest, self.mcp_oauth_authentication_state_changed_request) result["McpOauthHandlePendingRequest"] = to_class(MCPOauthHandlePendingRequest, self.mcp_oauth_handle_pending_request) result["McpOauthHandlePendingResult"] = to_class(MCPOauthHandlePendingResult, self.mcp_oauth_handle_pending_result) result["McpOauthLoginGrantType"] = to_enum(MCPGrantType, self.mcp_oauth_login_grant_type) result["McpOauthLoginRequest"] = to_class(MCPOauthLoginRequest, self.mcp_oauth_login_request) result["McpOauthLoginResult"] = to_class(MCPOauthLoginResult, self.mcp_oauth_login_result) result["McpOauthPendingRequestResponse"] = to_class(MCPOauthPendingRequestResponse, self.mcp_oauth_pending_request_response) + result["McpOauthRespondRequest"] = to_class(MCPOauthRespondRequest, self.mcp_oauth_respond_request) + result["McpOauthRespondResult"] = to_class(MCPOauthRespondResult, self.mcp_oauth_respond_result) result["McpRegisterExternalClientRequest"] = to_class(MCPRegisterExternalClientRequest, self.mcp_register_external_client_request) result["McpReloadWithConfigRequest"] = to_class(MCPReloadWithConfigRequest, self.mcp_reload_with_config_request) result["McpRemoveGitHubResult"] = to_class(MCPRemoveGitHubResult, self.mcp_remove_git_hub_result) @@ -27060,7 +31251,7 @@ def to_dict(self) -> dict: result["ModelCapabilitiesOverrideSupports"] = to_class(ModelCapabilitiesOverrideSupports, self.model_capabilities_override_supports) result["ModelCapabilitiesSupports"] = to_class(ModelCapabilitiesSupports, self.model_capabilities_supports) result["ModelList"] = to_class(ModelList, self.model_list) - result["ModelListRequest"] = to_class(ModelListRequest, self.model_list_request) + result["ModelListRequest"] = self.model_list_request result["ModelPickerCategory"] = to_enum(ModelPickerCategory, self.model_picker_category) result["ModelPickerPriceCategory"] = to_enum(ModelPickerPriceCategory, self.model_picker_price_category) result["ModelPolicy"] = to_class(ModelPolicy, self.model_policy) @@ -27097,6 +31288,7 @@ def to_dict(self) -> dict: result["PermissionDecisionApproveForLocationApprovalCustomTool"] = to_class(PermissionDecisionApproveForLocationApprovalCustomTool, self.permission_decision_approve_for_location_approval_custom_tool) result["PermissionDecisionApproveForLocationApprovalExtensionManagement"] = to_class(PermissionDecisionApproveForLocationApprovalExtensionManagement, self.permission_decision_approve_for_location_approval_extension_management) result["PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess"] = to_class(PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess, self.permission_decision_approve_for_location_approval_extension_permission_access) + result["PermissionDecisionApproveForLocationApprovalFactory"] = to_class(PermissionDecisionApproveForLocationApprovalFactory, self.permission_decision_approve_for_location_approval_factory) result["PermissionDecisionApproveForLocationApprovalMcp"] = to_class(PermissionDecisionApproveForLocationApprovalMCP, self.permission_decision_approve_for_location_approval_mcp) result["PermissionDecisionApproveForLocationApprovalMcpSampling"] = to_class(PermissionDecisionApproveForLocationApprovalMCPSampling, self.permission_decision_approve_for_location_approval_mcp_sampling) result["PermissionDecisionApproveForLocationApprovalMemory"] = to_class(PermissionDecisionApproveForLocationApprovalMemory, self.permission_decision_approve_for_location_approval_memory) @@ -27108,6 +31300,7 @@ def to_dict(self) -> dict: result["PermissionDecisionApproveForSessionApprovalCustomTool"] = to_class(PermissionDecisionApproveForSessionApprovalCustomTool, self.permission_decision_approve_for_session_approval_custom_tool) result["PermissionDecisionApproveForSessionApprovalExtensionManagement"] = to_class(PermissionDecisionApproveForSessionApprovalExtensionManagement, self.permission_decision_approve_for_session_approval_extension_management) result["PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess"] = to_class(PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess, self.permission_decision_approve_for_session_approval_extension_permission_access) + result["PermissionDecisionApproveForSessionApprovalFactory"] = to_class(PermissionDecisionApproveForSessionApprovalFactory, self.permission_decision_approve_for_session_approval_factory) result["PermissionDecisionApproveForSessionApprovalMcp"] = to_class(PermissionDecisionApproveForSessionApprovalMCP, self.permission_decision_approve_for_session_approval_mcp) result["PermissionDecisionApproveForSessionApprovalMcpSampling"] = to_class(PermissionDecisionApproveForSessionApprovalMCPSampling, self.permission_decision_approve_for_session_approval_mcp_sampling) result["PermissionDecisionApproveForSessionApprovalMemory"] = to_class(PermissionDecisionApproveForSessionApprovalMemory, self.permission_decision_approve_for_session_approval_memory) @@ -27116,13 +31309,17 @@ def to_dict(self) -> dict: result["PermissionDecisionApproveOnce"] = to_class(PermissionDecisionApproveOnce, self.permission_decision_approve_once) result["PermissionDecisionApprovePermanently"] = to_class(PermissionDecisionApprovePermanently, self.permission_decision_approve_permanently) result["PermissionDecisionCancelled"] = to_class(PermissionDecisionCancelled, self.permission_decision_cancelled) + result["PermissionDecisionContext"] = to_class(PermissionDecisionContext, self.permission_decision_context) result["PermissionDecisionDeniedByContentExclusionPolicy"] = to_class(PermissionDecisionDeniedByContentExclusionPolicy, self.permission_decision_denied_by_content_exclusion_policy) result["PermissionDecisionDeniedByPermissionRequestHook"] = to_class(PermissionDecisionDeniedByPermissionRequestHook, self.permission_decision_denied_by_permission_request_hook) result["PermissionDecisionDeniedByRules"] = to_class(PermissionDecisionDeniedByRules, self.permission_decision_denied_by_rules) result["PermissionDecisionDeniedInteractivelyByUser"] = to_class(PermissionDecisionDeniedInteractivelyByUser, self.permission_decision_denied_interactively_by_user) result["PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser"] = to_class(PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser, self.permission_decision_denied_no_approval_rule_and_could_not_request_from_user) + result["PermissionDecisionOutcome"] = to_enum(PermissionDecisionOutcome, self.permission_decision_outcome) result["PermissionDecisionReject"] = to_class(PermissionDecisionReject, self.permission_decision_reject) result["PermissionDecisionRequest"] = to_class(PermissionDecisionRequest, self.permission_decision_request) + result["PermissionDecisionSource"] = to_enum(PermissionDecisionSource, self.permission_decision_source) + result["PermissionDecisionSurface"] = to_enum(PermissionDecisionSurface, self.permission_decision_surface) result["PermissionDecisionUserNotAvailable"] = to_class(PermissionDecisionUserNotAvailable, self.permission_decision_user_not_available) result["PermissionLocationAddToolApprovalParams"] = to_class(PermissionLocationAddToolApprovalParams, self.permission_location_add_tool_approval_params) result["PermissionLocationApplyParams"] = to_class(PermissionLocationApplyParams, self.permission_location_apply_params) @@ -27155,6 +31352,7 @@ def to_dict(self) -> dict: result["PermissionsLocationsAddToolApprovalDetailsCustomTool"] = to_class(PermissionsLocationsAddToolApprovalDetailsCustomTool, self.permissions_locations_add_tool_approval_details_custom_tool) result["PermissionsLocationsAddToolApprovalDetailsExtensionManagement"] = to_class(PermissionsLocationsAddToolApprovalDetailsExtensionManagement, self.permissions_locations_add_tool_approval_details_extension_management) result["PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess"] = to_class(PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess, self.permissions_locations_add_tool_approval_details_extension_permission_access) + result["PermissionsLocationsAddToolApprovalDetailsFactory"] = to_class(PermissionsLocationsAddToolApprovalDetailsFactory, self.permissions_locations_add_tool_approval_details_factory) result["PermissionsLocationsAddToolApprovalDetailsMcp"] = to_class(PermissionsLocationsAddToolApprovalDetailsMCP, self.permissions_locations_add_tool_approval_details_mcp) result["PermissionsLocationsAddToolApprovalDetailsMcpSampling"] = to_class(PermissionsLocationsAddToolApprovalDetailsMCPSampling, self.permissions_locations_add_tool_approval_details_mcp_sampling) result["PermissionsLocationsAddToolApprovalDetailsMemory"] = to_class(PermissionsLocationsAddToolApprovalDetailsMemory, self.permissions_locations_add_tool_approval_details_memory) @@ -27200,7 +31398,7 @@ def to_dict(self) -> dict: result["PluginsMarketplacesBrowseRequest"] = to_class(PluginsMarketplacesBrowseRequest, self.plugins_marketplaces_browse_request) result["PluginsMarketplacesRefreshRequest"] = to_class(PluginsMarketplacesRefreshRequest, self.plugins_marketplaces_refresh_request) result["PluginsMarketplacesRemoveRequest"] = to_class(PluginsMarketplacesRemoveRequest, self.plugins_marketplaces_remove_request) - result["PluginsReloadRequest"] = to_class(PluginsReloadRequest, self.plugins_reload_request) + result["PluginsReloadRequest"] = self.plugins_reload_request result["PluginsUninstallRequest"] = to_class(PluginsUninstallRequest, self.plugins_uninstall_request) result["PluginsUpdateRequest"] = to_class(PluginsUpdateRequest, self.plugins_update_request) result["PluginUpdateAllEntry"] = to_class(PluginUpdateAllEntry, self.plugin_update_all_entry) @@ -27217,7 +31415,7 @@ def to_dict(self) -> dict: result["ProviderEndpointTransport"] = to_enum(ProviderTransport, self.provider_endpoint_transport) result["ProviderEndpointType"] = to_enum(ProviderType, self.provider_endpoint_type) result["ProviderEndpointWireApi"] = to_enum(ProviderWireAPI, self.provider_endpoint_wire_api) - result["ProviderGetEndpointRequest"] = to_class(ProviderGetEndpointRequest, self.provider_get_endpoint_request) + result["ProviderGetEndpointRequest"] = self.provider_get_endpoint_request result["ProviderModelConfig"] = to_class(ProviderModelConfig, self.provider_model_config) result["ProviderSessionToken"] = to_class(ProviderSessionToken, self.provider_session_token) result["ProviderTokenAcquireRequest"] = to_class(ProviderTokenAcquireRequest, self.provider_token_acquire_request) @@ -27245,13 +31443,36 @@ def to_dict(self) -> dict: result["PushAttachmentSelectionDetailsEnd"] = to_class(PushAttachmentSelectionDetailsEnd, self.push_attachment_selection_details_end) result["PushAttachmentSelectionDetailsStart"] = to_class(PushAttachmentSelectionDetailsStart, self.push_attachment_selection_details_start) result["PushGitHubRepoRef"] = to_class(PushGitHubRepoRef, self.push_git_hub_repo_ref) + result["QueueBeginDeferredIdleDrainRequest"] = to_class(QueueBeginDeferredIdleDrainRequest, self.queue_begin_deferred_idle_drain_request) + result["QueueBeginDeferredIdleDrainResult"] = to_class(QueueBeginDeferredIdleDrainResult, self.queue_begin_deferred_idle_drain_result) + result["QueueConsumeSystemNotificationsRequest"] = to_class(QueueConsumeSystemNotificationsRequest, self.queue_consume_system_notifications_request) result["QueuedCommandHandled"] = to_class(QueuedCommandHandled, self.queued_command_handled) result["QueuedCommandNotHandled"] = to_class(QueuedCommandNotHandled, self.queued_command_not_handled) result["QueuedCommandResult"] = (self.queued_command_result).to_dict() + result["QueueDeferSessionIdleRequest"] = to_class(QueueDeferSessionIdleRequest, self.queue_defer_session_idle_request) + result["QueueDuplicateAtRequest"] = to_class(QueueDuplicateAtRequest, self.queue_duplicate_at_request) + result["QueueDuplicateAtResult"] = to_class(QueueDuplicateAtResult, self.queue_duplicate_at_result) + result["QueueEnqueueResumePendingResult"] = to_class(QueueEnqueueResumePendingResult, self.queue_enqueue_resume_pending_result) + result["QueueFinishDeferredIdleDrainRequest"] = to_class(QueueFinishDeferredIdleDrainRequest, self.queue_finish_deferred_idle_drain_request) + result["QueueFinishDeferredIdleDrainResult"] = to_class(QueueFinishDeferredIdleDrainResult, self.queue_finish_deferred_idle_drain_result) + result["QueueHasPendingResult"] = to_class(QueueHasPendingResult, self.queue_has_pending_result) + result["QueueInsertAtRequest"] = to_class(QueueInsertAtRequest, self.queue_insert_at_request) + result["QueueInsertAtResult"] = to_class(QueueInsertAtResult, self.queue_insert_at_result) + result["QueueInsertMessage"] = to_class(QueueInsertMessage, self.queue_insert_message) + result["QueueMoveItemRequest"] = to_class(QueueMoveItemRequest, self.queue_move_item_request) + result["QueueMoveItemResult"] = to_class(QueueMoveItemResult, self.queue_move_item_result) result["QueuePendingItems"] = to_class(QueuePendingItems, self.queue_pending_items) result["QueuePendingItemsKind"] = to_enum(QueuePendingItemsKind, self.queue_pending_items_kind) result["QueuePendingItemsResult"] = to_class(QueuePendingItemsResult, self.queue_pending_items_result) + result["QueueRemoveAtRequest"] = to_class(QueueRemoveAtRequest, self.queue_remove_at_request) + result["QueueRemoveAtResult"] = to_class(QueueRemoveAtResult, self.queue_remove_at_result) result["QueueRemoveMostRecentResult"] = to_class(QueueRemoveMostRecentResult, self.queue_remove_most_recent_result) + result["QueueSendNowRequest"] = to_class(QueueSendNowRequest, self.queue_send_now_request) + result["QueueSendNowResult"] = to_class(QueueSendNowResult, self.queue_send_now_result) + result["QueueSetDrainPausedRequest"] = to_class(QueueSetDrainPausedRequest, self.queue_set_drain_paused_request) + result["QueueSnapshotResult"] = to_class(QueueSnapshotResult, self.queue_snapshot_result) + result["QueueUpdateTextRequest"] = to_class(QueueUpdateTextRequest, self.queue_update_text_request) + result["QueueUpdateTextResult"] = to_class(QueueUpdateTextResult, self.queue_update_text_result) result["RegisterEventInterestParams"] = to_class(RegisterEventInterestParams, self.register_event_interest_params) result["RegisterEventInterestResult"] = to_class(RegisterEventInterestResult, self.register_event_interest_result) result["RegisterExtensionToolsParams"] = to_class(_RegisterExtensionToolsParams, self.register_extension_tools_params) @@ -27279,14 +31500,23 @@ def to_dict(self) -> dict: result["RemoteSessionRepository"] = to_class(RemoteSessionRepository, self.remote_session_repository) result["RunOptions"] = to_class(RunOptions, self.run_options) result["SandboxConfig"] = to_class(SandboxConfig, self.sandbox_config) + result["SandboxConfigAuth"] = to_class(SandboxConfigAuth, self.sandbox_config_auth) result["SandboxConfigUserPolicy"] = to_class(SandboxConfigUserPolicy, self.sandbox_config_user_policy) result["SandboxConfigUserPolicyExperimental"] = to_class(SandboxConfigUserPolicyExperimental, self.sandbox_config_user_policy_experimental) result["SandboxConfigUserPolicyExperimentalSeatbelt"] = to_class(SandboxConfigUserPolicyExperimentalSeatbelt, self.sandbox_config_user_policy_experimental_seatbelt) result["SandboxConfigUserPolicyFilesystem"] = to_class(SandboxConfigUserPolicyFilesystem, self.sandbox_config_user_policy_filesystem) result["SandboxConfigUserPolicyNetwork"] = to_class(SandboxConfigUserPolicyNetwork, self.sandbox_config_user_policy_network) + result["SandboxConfigUserPolicyNetworkProxy"] = to_class(SandboxConfigUserPolicyNetworkProxy, self.sandbox_config_user_policy_network_proxy) result["SandboxConfigUserPolicySeatbelt"] = to_class(SandboxConfigUserPolicySeatbelt, self.sandbox_config_user_policy_seatbelt) + result["ScheduleAddAtRequest"] = to_class(ScheduleAddAtRequest, self.schedule_add_at_request) + result["ScheduleAddCronRequest"] = to_class(ScheduleAddCronRequest, self.schedule_add_cron_request) + result["ScheduleAddRequest"] = to_class(ScheduleAddRequest, self.schedule_add_request) + result["ScheduleAddResult"] = to_class(ScheduleAddResult, self.schedule_add_result) + result["ScheduleAddSelfPacedRequest"] = to_class(ScheduleAddSelfPacedRequest, self.schedule_add_self_paced_request) result["ScheduleEntry"] = to_class(ScheduleEntry, self.schedule_entry) + result["ScheduleHasSelfPacedResult"] = to_class(ScheduleHasSelfPacedResult, self.schedule_has_self_paced_result) result["ScheduleList"] = to_class(ScheduleList, self.schedule_list) + result["ScheduleRearmSelfPacedRequest"] = to_class(ScheduleRearmSelfPacedRequest, self.schedule_rearm_self_paced_request) result["ScheduleStopRequest"] = to_class(ScheduleStopRequest, self.schedule_stop_request) result["ScheduleStopResult"] = to_class(ScheduleStopResult, self.schedule_stop_result) result["SecretsAddFilterValuesRequest"] = to_class(SecretsAddFilterValuesRequest, self.secrets_add_filter_values_request) @@ -27299,14 +31529,18 @@ def to_dict(self) -> dict: result["SendMode"] = to_enum(SendMode, self.send_mode) result["SendRequest"] = to_class(SendRequest, self.send_request) result["SendResult"] = to_class(SendResult, self.send_result) + result["SendSystemNotificationRequest"] = to_class(SendSystemNotificationRequest, self.send_system_notification_request) result["ServerAgentList"] = to_class(ServerAgentList, self.server_agent_list) result["ServerInstructionSourceList"] = to_class(ServerInstructionSourceList, self.server_instruction_source_list) result["ServerSkill"] = to_class(ServerSkill, self.server_skill) result["ServerSkillList"] = to_class(ServerSkillList, self.server_skill_list) result["SessionActivity"] = to_class(SessionActivity, self.session_activity) + result["SessionAgentListRequest"] = to_class(SessionAgentListRequest, self.session_agent_list_request) result["SessionAuthStatus"] = to_class(SessionAuthStatus, self.session_auth_status) result["SessionBulkDeleteResult"] = to_class(SessionBulkDeleteResult, self.session_bulk_delete_result) + result["SessionCancelAllBackgroundAgentsResult"] = from_int(self.session_cancel_all_background_agents_result) result["SessionCapability"] = to_enum(SessionCapability, self.session_capability) + result["SessionCommandsListRequest"] = to_class(SessionCommandsListRequest, self.session_commands_list_request) result["SessionCompletionItem"] = to_class(SessionCompletionItem, self.session_completion_item) result["SessionContext"] = to_class(SessionContext, self.session_context) result["SessionContextHostType"] = to_enum(HostType, self.session_context_host_type) @@ -27336,23 +31570,42 @@ def to_dict(self) -> dict: result["SessionFsSqliteQueryRequest"] = to_class(SessionFSSqliteQueryRequest, self.session_fs_sqlite_query_request) result["SessionFsSqliteQueryResult"] = to_class(SessionFSSqliteQueryResult, self.session_fs_sqlite_query_result) result["SessionFsSqliteQueryType"] = to_enum(SessionFSSqliteQueryType, self.session_fs_sqlite_query_type) + result["SessionFsSqliteTransactionError"] = to_class(SessionFSSqliteTransactionError, self.session_fs_sqlite_transaction_error) + result["SessionFsSqliteTransactionErrorClass"] = to_enum(SessionFSSqliteTransactionErrorClass, self.session_fs_sqlite_transaction_error_class) + result["SessionFsSqliteTransactionRequest"] = to_class(SessionFSSqliteTransactionRequest, self.session_fs_sqlite_transaction_request) + result["SessionFsSqliteTransactionResult"] = to_class(SessionFSSqliteTransactionResult, self.session_fs_sqlite_transaction_result) + result["SessionFsSqliteTransactionStatement"] = to_class(SessionFSSqliteTransactionStatement, self.session_fs_sqlite_transaction_statement) result["SessionFsStatRequest"] = to_class(SessionFSStatRequest, self.session_fs_stat_request) result["SessionFsStatResult"] = to_class(SessionFSStatResult, self.session_fs_stat_result) result["SessionFsWriteFileRequest"] = to_class(SessionFSWriteFileRequest, self.session_fs_write_file_request) + result["SessionHistoryCompactRequest"] = to_class(SessionHistoryCompactRequest, self.session_history_compact_request) result["SessionInstalledPlugin"] = to_class(SessionInstalledPlugin, self.session_installed_plugin) result["SessionInstalledPluginSource"] = from_union([lambda x: to_class(SessionInstalledPluginSource, x), from_str], self.session_installed_plugin_source) result["SessionInstalledPluginSourceGitHub"] = to_class(SessionInstalledPluginSourceGitHub, self.session_installed_plugin_source_git_hub) result["SessionInstalledPluginSourceLocal"] = to_class(SessionInstalledPluginSourceLocal, self.session_installed_plugin_source_local) result["SessionInstalledPluginSourceUrl"] = to_class(SessionInstalledPluginSourceURL, self.session_installed_plugin_source_url) + result["SessionLimitPredictionBaselineData"] = to_class(SessionLimitPredictionBaselineData, self.session_limit_prediction_baseline_data) + result["SessionLimitPredictionClientType"] = to_enum(SessionLimitPredictionClientType, self.session_limit_prediction_client_type) + result["SessionLimitPredictionDetails"] = to_class(SessionLimitPredictionDetails, self.session_limit_prediction_details) + result["SessionLimitPredictionPredictRequest"] = to_class(SessionLimitPredictionPredictRequest, self.session_limit_prediction_predict_request) + result["SessionLimitPredictionRequest"] = self.session_limit_prediction_request + result["SessionLimitPredictionResult"] = to_class(SessionLimitPredictionResult, self.session_limit_prediction_result) + result["SessionLimitPredictionSource"] = to_enum(SessionLimitPredictionSource, self.session_limit_prediction_source) + result["SessionLimitPredictionTier"] = to_enum(SessionLimitPredictionTier, self.session_limit_prediction_tier) + result["SessionLimitPredictionTierOption"] = to_class(SessionLimitPredictionTierOption, self.session_limit_prediction_tier_option) + result["SessionLimitPredictionUnavailableReason"] = to_enum(SessionLimitPredictionUnavailableReason, self.session_limit_prediction_unavailable_reason) result["SessionList"] = to_class(SessionList, self.session_list) result["SessionListEntry"] = (self.session_list_entry).to_dict() result["SessionListFilter"] = to_class(SessionListFilter, self.session_list_filter) result["SessionLoadDeferredRepoHooksResult"] = to_class(SessionLoadDeferredRepoHooksResult, self.session_load_deferred_repo_hooks_result) result["SessionLogLevel"] = to_enum(SessionLogLevel, self.session_log_level) + result["SessionManagedPermissions"] = to_class(SessionManagedPermissions, self.session_managed_permissions) + result["SessionManagedSettings"] = to_class(SessionManagedSettings, self.session_managed_settings) result["SessionMcpAppsCallToolResult"] = from_dict(lambda x: x, self.session_mcp_apps_call_tool_result) result["SessionMetadataSnapshot"] = to_class(SessionMetadataSnapshot, self.session_metadata_snapshot) result["SessionMode"] = to_enum(SessionMode, self.session_mode) result["SessionModelList"] = to_class(SessionModelList, self.session_model_list) + result["SessionModelListRequest"] = to_class(SessionModelListRequest, self.session_model_list_request) result["SessionModelPriceCategory"] = to_class(SessionModelPriceCategory, self.session_model_price_category) result["SessionOpenOptions"] = to_class(SessionOpenOptions, self.session_open_options) result["SessionOpenOptionsAdditionalContentExclusionPolicy"] = to_class(SessionOpenOptionsAdditionalContentExclusionPolicy, self.session_open_options_additional_content_exclusion_policy) @@ -27363,12 +31616,15 @@ def to_dict(self) -> dict: result["SessionOpenOptionsReasoningSummary"] = to_enum(ReasoningSummary, self.session_open_options_reasoning_summary) result["SessionOpenParams"] = (self.session_open_params).to_dict() result["SessionOpenResult"] = to_class(SessionOpenResult, self.session_open_result) + result["SessionPluginsReloadRequest"] = to_class(SessionPluginsReloadRequest, self.session_plugins_reload_request) + result["SessionProviderGetEndpointRequest"] = to_class(SessionProviderGetEndpointRequest, self.session_provider_get_endpoint_request) result["SessionPruneResult"] = to_class(SessionPruneResult, self.session_prune_result) result["SessionsBulkDeleteRequest"] = to_class(SessionsBulkDeleteRequest, self.sessions_bulk_delete_request) result["SessionsCheckInUseRequest"] = to_class(SessionsCheckInUseRequest, self.sessions_check_in_use_request) result["SessionsCheckInUseResult"] = to_class(SessionsCheckInUseResult, self.sessions_check_in_use_result) result["SessionsCloseRequest"] = to_class(SessionsCloseRequest, self.sessions_close_request) result["SessionsCloseResult"] = to_class(SessionsCloseResult, self.sessions_close_result) + result["SessionsDeleteRequest"] = to_class(SessionsDeleteRequest, self.sessions_delete_request) result["SessionsEnrichMetadataRequest"] = to_class(SessionsEnrichMetadataRequest, self.sessions_enrich_metadata_request) result["SessionSetCredentialsParams"] = to_class(SessionSetCredentialsParams, self.session_set_credentials_params) result["SessionSetCredentialsResult"] = to_class(SessionSetCredentialsResult, self.session_set_credentials_result) @@ -27394,9 +31650,13 @@ def to_dict(self) -> dict: result["SessionsGetEventFilePathResult"] = to_class(SessionsGetEventFilePathResult, self.sessions_get_event_file_path_result) result["SessionsGetLastForContextRequest"] = to_class(SessionsGetLastForContextRequest, self.sessions_get_last_for_context_request) result["SessionsGetLastForContextResult"] = to_class(SessionsGetLastForContextResult, self.sessions_get_last_for_context_result) + result["SessionsGetMetadataRequest"] = to_class(SessionsGetMetadataRequest, self.sessions_get_metadata_request) + result["SessionsGetMetadataResult"] = to_class(SessionsGetMetadataResult, self.sessions_get_metadata_result) result["SessionsGetPersistedRemoteSteerableRequest"] = to_class(SessionsGetPersistedRemoteSteerableRequest, self.sessions_get_persisted_remote_steerable_request) result["SessionsGetPersistedRemoteSteerableResult"] = to_class(SessionsGetPersistedRemoteSteerableResult, self.sessions_get_persisted_remote_steerable_result) result["SessionSizes"] = to_class(SessionSizes, self.session_sizes) + result["SessionsListNonEmptySessionIdsRequest"] = to_class(SessionsListNonEmptySessionIDSRequest, self.sessions_list_non_empty_session_ids_request) + result["SessionsListNonEmptySessionIdsResult"] = to_class(SessionsListNonEmptySessionIDSResult, self.sessions_list_non_empty_session_ids_result) result["SessionsListRequest"] = to_class(SessionsListRequest, self.sessions_list_request) result["SessionsLoadDeferredRepoHooksRequest"] = to_class(SessionsLoadDeferredRepoHooksRequest, self.sessions_load_deferred_repo_hooks_request) result["SessionsOpenAttach"] = to_class(SessionsOpenAttach, self.sessions_open_attach) @@ -27436,9 +31696,13 @@ def to_dict(self) -> dict: result["ShellExecRequest"] = to_class(ShellExecRequest, self.shell_exec_request) result["ShellExecResult"] = to_class(ShellExecResult, self.shell_exec_result) result["ShellExecuteUserRequestedRequest"] = to_class(ShellExecuteUserRequestedRequest, self.shell_execute_user_requested_request) + result["ShellInitProfile"] = to_enum(ShellInitProfile, self.shell_init_profile) + result["ShellInitScript"] = to_class(ShellInitScript, self.shell_init_script) + result["ShellInitScriptShell"] = to_enum(ShellInitScriptShell, self.shell_init_script_shell) result["ShellKillRequest"] = to_class(ShellKillRequest, self.shell_kill_request) result["ShellKillResult"] = to_class(ShellKillResult, self.shell_kill_result) result["ShellKillSignal"] = to_enum(ShellKillSignal, self.shell_kill_signal) + result["ShellOptions"] = to_class(ShellOptions, self.shell_options) result["ShutdownRequest"] = to_class(ShutdownRequest, self.shutdown_request) result["Skill"] = to_class(Skill, self.skill) result["SkillDiscoveryPath"] = to_class(SkillDiscoveryPath, self.skill_discovery_path) @@ -27561,20 +31825,30 @@ def to_dict(self) -> dict: result["WorkspaceDiffFileChangeType"] = to_enum(WorkspaceDiffFileChangeType, self.workspace_diff_file_change_type) result["WorkspaceDiffMode"] = to_enum(WorkspaceDiffMode, self.workspace_diff_mode) result["WorkspaceDiffResult"] = to_class(WorkspaceDiffResult, self.workspace_diff_result) + result["WorkspacesAddSummaryRequest"] = to_class(WorkspacesAddSummaryRequest, self.workspaces_add_summary_request) + result["WorkspacesAddSummaryResult"] = to_class(WorkspacesAddSummaryResult, self.workspaces_add_summary_result) + result["WorkspacesAutopilotObjectiveExistsResult"] = to_class(WorkspacesAutopilotObjectiveExistsResult, self.workspaces_autopilot_objective_exists_result) result["WorkspacesCheckpoints"] = to_class(WorkspacesCheckpoints, self.workspaces_checkpoints) result["WorkspacesCreateFileRequest"] = to_class(WorkspacesCreateFileRequest, self.workspaces_create_file_request) + result["WorkspacesDeleteAutopilotObjectiveResult"] = to_class(WorkspacesDeleteAutopilotObjectiveResult, self.workspaces_delete_autopilot_objective_result) result["WorkspacesDiffRequest"] = to_class(WorkspacesDiffRequest, self.workspaces_diff_request) + result["WorkspacesEnsureRequest"] = to_class(WorkspacesEnsureRequest, self.workspaces_ensure_request) result["WorkspacesGetWorkspaceResult"] = to_class(WorkspacesGetWorkspaceResult, self.workspaces_get_workspace_result) result["WorkspacesListCheckpointsResult"] = to_class(WorkspacesListCheckpointsResult, self.workspaces_list_checkpoints_result) result["WorkspacesListFilesResult"] = to_class(WorkspacesListFilesResult, self.workspaces_list_files_result) + result["WorkspacesReadAutopilotObjectiveResult"] = to_class(WorkspacesReadAutopilotObjectiveResult, self.workspaces_read_autopilot_objective_result) result["WorkspacesReadCheckpointRequest"] = to_class(WorkspacesReadCheckpointRequest, self.workspaces_read_checkpoint_request) result["WorkspacesReadCheckpointResult"] = to_class(WorkspacesReadCheckpointResult, self.workspaces_read_checkpoint_result) result["WorkspacesReadFileRequest"] = to_class(WorkspacesReadFileRequest, self.workspaces_read_file_request) result["WorkspacesReadFileResult"] = to_class(WorkspacesReadFileResult, self.workspaces_read_file_result) result["WorkspacesSaveLargePasteRequest"] = to_class(WorkspacesSaveLargePasteRequest, self.workspaces_save_large_paste_request) result["WorkspacesSaveLargePasteResult"] = to_class(WorkspacesSaveLargePasteResult, self.workspaces_save_large_paste_result) + result["WorkspacesTruncateSummariesRequest"] = to_class(WorkspacesTruncateSummariesRequest, self.workspaces_truncate_summaries_request) result["WorkspaceSummaryHostType"] = to_enum(HostType, self.workspace_summary_host_type) + result["WorkspacesUpdateMetadataRequest"] = to_class(WorkspacesUpdateMetadataRequest, self.workspaces_update_metadata_request) result["WorkspacesWorkspaceDetailsHostType"] = to_enum(HostType, self.workspaces_workspace_details_host_type) + result["WorkspacesWriteAutopilotObjectiveRequest"] = to_class(WorkspacesWriteAutopilotObjectiveRequest, self.workspaces_write_autopilot_objective_request) + result["WorkspacesWriteAutopilotObjectiveResult"] = to_class(WorkspacesWriteAutopilotObjectiveResult, self.workspaces_write_autopilot_objective_result) result["SessionContextAttribution"] = from_union([lambda x: to_class(SessionContextAttribution, x), from_none], self.session_context_attribution) result["SessionContextInfo"] = from_union([lambda x: to_class(SessionContextInfo, x), from_none], self.session_context_info) result["SubagentSettings"] = from_union([lambda x: to_class(SubagentSettings, x), from_none], self.subagent_settings) @@ -27658,7 +31932,7 @@ def _load_PermissionDecision(obj: Any) -> "PermissionDecision": case _: raise ValueError(f"Unknown PermissionDecision kind: {kind!r}") # Approval to persist for this location -PermissionDecisionApproveForLocationApproval = PermissionDecisionApproveForLocationApprovalCommands | PermissionDecisionApproveForLocationApprovalRead | PermissionDecisionApproveForLocationApprovalWrite | PermissionDecisionApproveForLocationApprovalMCP | PermissionDecisionApproveForLocationApprovalMCPSampling | PermissionDecisionApproveForLocationApprovalMemory | PermissionDecisionApproveForLocationApprovalCustomTool | PermissionDecisionApproveForLocationApprovalExtensionManagement | PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess +PermissionDecisionApproveForLocationApproval = PermissionDecisionApproveForLocationApprovalCommands | PermissionDecisionApproveForLocationApprovalRead | PermissionDecisionApproveForLocationApprovalWrite | PermissionDecisionApproveForLocationApprovalMCP | PermissionDecisionApproveForLocationApprovalMCPSampling | PermissionDecisionApproveForLocationApprovalMemory | PermissionDecisionApproveForLocationApprovalCustomTool | PermissionDecisionApproveForLocationApprovalExtensionManagement | PermissionDecisionApproveForLocationApprovalFactory | PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess def _load_PermissionDecisionApproveForLocationApproval(obj: Any) -> "PermissionDecisionApproveForLocationApproval": assert isinstance(obj, dict) @@ -27672,11 +31946,12 @@ def _load_PermissionDecisionApproveForLocationApproval(obj: Any) -> "PermissionD case "memory": return PermissionDecisionApproveForLocationApprovalMemory.from_dict(obj) case "custom-tool": return PermissionDecisionApproveForLocationApprovalCustomTool.from_dict(obj) case "extension-management": return PermissionDecisionApproveForLocationApprovalExtensionManagement.from_dict(obj) + case "factory": return PermissionDecisionApproveForLocationApprovalFactory.from_dict(obj) case "extension-permission-access": return PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess.from_dict(obj) case _: raise ValueError(f"Unknown PermissionDecisionApproveForLocationApproval kind: {kind!r}") # Session-scoped approval to remember (tool prompts only; omitted for path/url prompts) -PermissionDecisionApproveForSessionApproval = PermissionDecisionApproveForSessionApprovalCommands | PermissionDecisionApproveForSessionApprovalRead | PermissionDecisionApproveForSessionApprovalWrite | PermissionDecisionApproveForSessionApprovalMCP | PermissionDecisionApproveForSessionApprovalMCPSampling | PermissionDecisionApproveForSessionApprovalMemory | PermissionDecisionApproveForSessionApprovalCustomTool | PermissionDecisionApproveForSessionApprovalExtensionManagement | PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess +PermissionDecisionApproveForSessionApproval = PermissionDecisionApproveForSessionApprovalCommands | PermissionDecisionApproveForSessionApprovalRead | PermissionDecisionApproveForSessionApprovalWrite | PermissionDecisionApproveForSessionApprovalMCP | PermissionDecisionApproveForSessionApprovalMCPSampling | PermissionDecisionApproveForSessionApprovalMemory | PermissionDecisionApproveForSessionApprovalCustomTool | PermissionDecisionApproveForSessionApprovalExtensionManagement | PermissionDecisionApproveForSessionApprovalFactory | PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess def _load_PermissionDecisionApproveForSessionApproval(obj: Any) -> "PermissionDecisionApproveForSessionApproval": assert isinstance(obj, dict) @@ -27690,11 +31965,12 @@ def _load_PermissionDecisionApproveForSessionApproval(obj: Any) -> "PermissionDe case "memory": return PermissionDecisionApproveForSessionApprovalMemory.from_dict(obj) case "custom-tool": return PermissionDecisionApproveForSessionApprovalCustomTool.from_dict(obj) case "extension-management": return PermissionDecisionApproveForSessionApprovalExtensionManagement.from_dict(obj) + case "factory": return PermissionDecisionApproveForSessionApprovalFactory.from_dict(obj) case "extension-permission-access": return PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess.from_dict(obj) case _: raise ValueError(f"Unknown PermissionDecisionApproveForSessionApproval kind: {kind!r}") # Tool approval to persist and apply -PermissionsLocationsAddToolApprovalDetails = PermissionsLocationsAddToolApprovalDetailsCommands | PermissionsLocationsAddToolApprovalDetailsRead | PermissionsLocationsAddToolApprovalDetailsWrite | PermissionsLocationsAddToolApprovalDetailsMCP | PermissionsLocationsAddToolApprovalDetailsMCPSampling | PermissionsLocationsAddToolApprovalDetailsMemory | PermissionsLocationsAddToolApprovalDetailsCustomTool | PermissionsLocationsAddToolApprovalDetailsExtensionManagement | PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess +PermissionsLocationsAddToolApprovalDetails = PermissionsLocationsAddToolApprovalDetailsCommands | PermissionsLocationsAddToolApprovalDetailsRead | PermissionsLocationsAddToolApprovalDetailsWrite | PermissionsLocationsAddToolApprovalDetailsMCP | PermissionsLocationsAddToolApprovalDetailsMCPSampling | PermissionsLocationsAddToolApprovalDetailsMemory | PermissionsLocationsAddToolApprovalDetailsCustomTool | PermissionsLocationsAddToolApprovalDetailsExtensionManagement | PermissionsLocationsAddToolApprovalDetailsFactory | PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess def _load_PermissionsLocationsAddToolApprovalDetails(obj: Any) -> "PermissionsLocationsAddToolApprovalDetails": assert isinstance(obj, dict) @@ -27708,6 +31984,7 @@ def _load_PermissionsLocationsAddToolApprovalDetails(obj: Any) -> "PermissionsLo case "memory": return PermissionsLocationsAddToolApprovalDetailsMemory.from_dict(obj) case "custom-tool": return PermissionsLocationsAddToolApprovalDetailsCustomTool.from_dict(obj) case "extension-management": return PermissionsLocationsAddToolApprovalDetailsExtensionManagement.from_dict(obj) + case "factory": return PermissionsLocationsAddToolApprovalDetailsFactory.from_dict(obj) case "extension-permission-access": return PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess.from_dict(obj) case _: raise ValueError(f"Unknown PermissionsLocationsAddToolApprovalDetails kind: {kind!r}") @@ -27742,8 +32019,8 @@ def _load_QueuedCommandResult(obj: Any) -> "QueuedCommandResult": assert isinstance(obj, dict) kind = obj.get("handled") match kind: - case "true": return QueuedCommandHandled.from_dict(obj) - case "false": return QueuedCommandNotHandled.from_dict(obj) + case True: return QueuedCommandHandled.from_dict(obj) + case False: return QueuedCommandNotHandled.from_dict(obj) case _: raise ValueError(f"Unknown QueuedCommandResult handled: {kind!r}") # State of the runtime-managed remote-control singleton. @@ -27766,8 +32043,8 @@ def _load_SessionListEntry(obj: Any) -> "SessionListEntry": assert isinstance(obj, dict) kind = obj.get("isRemote") match kind: - case "false": return LocalSessionMetadataValue.from_dict(obj) - case "true": return RemoteSessionMetadataValue.from_dict(obj) + case False: return LocalSessionMetadataValue.from_dict(obj) + case True: return RemoteSessionMetadataValue.from_dict(obj) case _: raise ValueError(f"Unknown SessionListEntry isRemote: {kind!r}") # Open a session by creating, resuming, attaching, connecting to a remote, or handing off. @@ -27812,11 +32089,14 @@ def _load_TaskInfo(obj: Any) -> "TaskInfo": AccountGetAllUsersResult = list +AgentListRequest = Any CanvasActionInvokeResult = Any CanvasJsonSchema = Any +CommandsListRequest = Any ExternalToolResult = ExternalToolTextResultForLlm ExternalToolTextResultForLlmContentResourceLinkIconTheme = Theme FilterMapping = dict +HistoryCompactRequest = Any InstructionDiscoveryPathKind = DebugCollectLogsEntryKind InstructionDiscoveryPathLocation = InstructionLocation InstructionSourceLocation = InstructionLocation @@ -27834,21 +32114,26 @@ def _load_TaskInfo(obj: Any) -> "TaskInfo": McpServerAuthConfig = bool McpServerConfigHttpOauthGrantType = MCPGrantType MetadataSnapshotRemoteMetadataTaskType = TaskType +ModelListRequest = Any OptionsUpdateAdditionalContentExclusionPolicyScope = AdditionalContentExclusionPolicyScope OptionsUpdateEnvValueMode = MCPSetEnvValueModeDetails OptionsUpdateReasoningSummary = ReasoningSummary PermissionsConfigureAdditionalContentExclusionPolicyScope = AdditionalContentExclusionPolicyScope PermissionsSetAllowAllSource = PermissionsSetAAllSource PermissionsSetApproveAllSource = PermissionsSetAAllSource +PluginsReloadRequest = Any ProviderConfigTransport = ProviderTransport ProviderConfigType = ProviderType ProviderConfigWireApi = ProviderWireAPI ProviderEndpointTransport = ProviderTransport ProviderEndpointType = ProviderType ProviderEndpointWireApi = ProviderWireAPI +ProviderGetEndpointRequest = Any RemoteSessionMetadataTaskType = TaskType +SessionCancelAllBackgroundAgentsResult = int SessionContextHostType = HostType SessionFsReaddirWithTypesEntryType = DebugCollectLogsEntryKind +SessionLimitPredictionRequest = Any SessionMcpAppsCallToolResult = dict SessionOpenOptionsAdditionalContentExclusionPolicyScope = AdditionalContentExclusionPolicyScope SessionOpenOptionsEnvValueMode = MCPSetEnvValueModeDetails @@ -27898,6 +32183,10 @@ async def list(self, params: ModelsListRequest, *, timeout: float | None = None) params_dict = {k: v for k, v in params.to_dict().items() if v is not None} return ModelList.from_dict(_patch_model_capabilities(await self._client.request("models.list", params_dict, **_timeout_kwargs(timeout)))) + async def get_built_in_catalog(self, *, timeout: float | None = None) -> BuiltInModelCatalog: + "Returns the running runtime's complete catalog of well-known built-in model IDs without authentication or network access.\n\nReturns:\n The running runtime's complete catalog of well-known built-in model IDs, including supported models and additional IDs with built-in metadata." + return BuiltInModelCatalog.from_dict(await self._client.request("models.getBuiltInCatalog", {}, **_timeout_kwargs(timeout))) + # Experimental: this API group is experimental and may change or be removed. class ServerToolsApi: @@ -28001,6 +32290,26 @@ async def discover(self, params: MCPDiscoverRequest, *, timeout: float | None = return MCPDiscoverResult.from_dict(await self._client.request("mcp.discover", params_dict, **_timeout_kwargs(timeout))) +# Experimental: this API group is experimental and may change or be removed. +class ServerExtensionsApi: + def __init__(self, client: "JsonRpcClient"): + self._client = client + + async def discover(self, *, timeout: float | None = None) -> DiscoveredExtensions: + "Discovers user and enabled installed-plugin extensions from persisted Copilot home state, including enablement preferences. Launch-scoped additional plugins are not included.\n\nReturns:\n Extensions discovered from persisted Copilot home state and their effective loading mode. Launch-scoped additional plugins are not included." + return DiscoveredExtensions.from_dict(await self._client.request("extensions.discover", {}, **_timeout_kwargs(timeout))) + + async def enable(self, params: DiscoveredExtensionsEnableRequest, *, timeout: float | None = None) -> None: + "Persistently enables extension IDs for future sessions. Active sessions are unchanged; use session.extensions.enable to update them.\n\nArgs:\n params: Source-qualified extension identifiers to persistently enable for future sessions." + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + await self._client.request("extensions.enable", params_dict, **_timeout_kwargs(timeout)) + + async def disable(self, params: DiscoveredExtensionsDisableRequest, *, timeout: float | None = None) -> None: + "Persistently disables extension IDs for future sessions. Active sessions are unchanged; use session.extensions.disable to update them.\n\nArgs:\n params: Source-qualified extension identifiers to persistently disable for future sessions." + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + await self._client.request("extensions.disable", params_dict, **_timeout_kwargs(timeout)) + + # Experimental: this API group is experimental and may change or be removed. class ServerPluginsMarketplacesApi: def __init__(self, client: "JsonRpcClient"): @@ -28167,6 +32476,16 @@ def __init__(self, client: "JsonRpcClient"): self.settings = ServerUserSettingsApi(client) +# Experimental: this API group is experimental and may change or be removed. +class ServerManagedSettingsApi: + def __init__(self, client: "JsonRpcClient"): + self._client = client + + async def read(self, *, timeout: float | None = None) -> ManagedSettingsReadResult: + "Discovers device-managed settings from production MDM and managed-file sources, validates them against the runtime-owned managed-settings schema, and returns the canonical JSON without requiring a session.\n\nReturns:\n Validated device-managed settings discovered before a session exists." + return ManagedSettingsReadResult.from_dict(await self._client.request("managedSettings.read", {}, **_timeout_kwargs(timeout))) + + # Experimental: this API group is experimental and may change or be removed. class ServerRuntimeApi: def __init__(self, client: "JsonRpcClient"): @@ -28347,12 +32666,14 @@ def __init__(self, client: "JsonRpcClient"): self.account = ServerAccountApi(client) self.secrets = ServerSecretsApi(client) self.mcp = ServerMcpApi(client) + self.extensions = ServerExtensionsApi(client) self.plugins = ServerPluginsApi(client) self.skills = ServerSkillsApi(client) self.agents = ServerAgentsApi(client) self.instructions = ServerInstructionsApi(client) self.commands = ServerCommandsApi(client) self.user = ServerUserApi(client) + self.managed_settings = ServerManagedSettingsApi(client) self.runtime = ServerRuntimeApi(client) self.session_fs = ServerSessionFsApi(client) self.llm_inference = ServerLlmInferenceApi(client) @@ -28364,12 +32685,26 @@ async def ping(self, params: PingRequest, *, timeout: float | None = None) -> Pi params_dict = {k: v for k, v in params.to_dict().items() if v is not None} return PingResult.from_dict(await self._client.request("ping", params_dict, **_timeout_kwargs(timeout))) + async def register_extension_launch_provider(self, *, timeout: float | None = None) -> None: + "Registers the calling SDK client as the per-entrypoint extension launch provider. Call before creating any sessions. When omitted, the runtime temporarily falls back to its built-in Node launcher for backward compatibility.\n\n.. warning:: This API is experimental and may change or be removed in future versions." + await self._client.request("registerExtensionLaunchProvider", {}, **_timeout_kwargs(timeout)) + # Experimental: this API group is experimental and may change or be removed. class _InternalServerSessionsApi: def __init__(self, client: "JsonRpcClient"): self._client = client + async def _get_metadata(self, params: SessionsGetMetadataRequest, *, timeout: float | None = None) -> SessionsGetMetadataResult: + "Reads lightweight persisted metadata for one local session without opening it.\n\nArgs:\n params: Session ID whose persisted metadata should be read.\n\nReturns:\n Persisted local session metadata when the session exists.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + return SessionsGetMetadataResult.from_dict(await self._client.request("sessions.getMetadata", params_dict, **_timeout_kwargs(timeout))) + + async def _list_non_empty_session_ids(self, params: SessionsListNonEmptySessionIDSRequest, *, timeout: float | None = None) -> SessionsListNonEmptySessionIDSResult: + "Lists recent local session IDs that contain user-visible history, omitting housekeeping-only sessions.\n\nArgs:\n params: Limit for non-empty local session IDs.\n\nReturns:\n Recent local session IDs that contain user-visible history.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + return SessionsListNonEmptySessionIDSResult.from_dict(await self._client.request("sessions.listNonEmptySessionIds", params_dict, **_timeout_kwargs(timeout))) + async def _get_event_file_path(self, params: SessionsGetEventFilePathRequest, *, timeout: float | None = None) -> SessionsGetEventFilePathResult: "Computes the absolute path to a session's persisted events.jsonl file. Internal: filesystem paths are only meaningful in-process (CLI and runtime share a filesystem). Currently used by the CLI's contribution-graph feature to read historical events directly. Remote SDK consumers must not depend on this; a proper event-query API would replace it if the contribution graph ever needed to work over the wire.\n\nArgs:\n params: Session ID whose event-log file path to compute.\n\nReturns:\n Absolute path to the session's events.jsonl file on disk.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." params_dict = {k: v for k, v in params.to_dict().items() if v is not None} @@ -28380,6 +32715,11 @@ async def _get_persisted_remote_steerable(self, params: SessionsGetPersistedRemo params_dict = {k: v for k, v in params.to_dict().items() if v is not None} return SessionsGetPersistedRemoteSteerableResult.from_dict(await self._client.request("sessions.getPersistedRemoteSteerable", params_dict, **_timeout_kwargs(timeout))) + async def _delete(self, params: SessionsDeleteRequest, *, timeout: float | None = None) -> None: + "Deletes one local session from disk after running the same lifecycle hooks as the session manager.\n\nArgs:\n params: Session ID to delete from disk.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + await self._client.request("sessions.delete", params_dict, **_timeout_kwargs(timeout)) + async def _get_board_entry_count(self, params: SessionsGetBoardEntryCountRequest, *, timeout: float | None = None) -> SessionsGetBoardEntryCountResult: "Gets the dynamic-context board entry count associated with a session, when available. Internal: this exists solely so CLI telemetry events (`rem_spawn_gate`, `rem_consolidation_complete`) can pair START / END board counts around the detached rem-agent spawn. \"Dynamic context board\" is a runtime-internal concept that is not part of the public SDK contract; the long-term plan is to relocate the telemetry emission into the runtime so this method can be deleted entirely.\n\nArgs:\n params: Session ID whose board entry count should be returned.\n\nReturns:\n Dynamic-context board entry count, when available.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." params_dict = {k: v for k, v in params.to_dict().items() if v is not None} @@ -28511,12 +32851,36 @@ async def run(self, params: FactoryRunRequest, *, timeout: float | None = None) params_dict["sessionId"] = self._session_id return FactoryRunResult.from_dict(await self._client.request("session.factory.run", params_dict, **_timeout_kwargs(timeout))) + async def resume(self, params: FactoryResumeRequest, *, timeout: float | None = None) -> FactoryResumeResult: + "Resumes a factory run using its persisted name, arguments, journal, and accounting.\n\nArgs:\n params: Parameters for resuming a factory run from its persisted identity.\n\nReturns:\n Resolved persisted factory identity and resumed run envelope." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return FactoryResumeResult.from_dict(await self._client.request("session.factory.resume", params_dict, **_timeout_kwargs(timeout))) + async def get_run(self, params: FactoryGetRunRequest, *, timeout: float | None = None) -> FactoryRunResult: "Gets the current or settled envelope for a factory run.\n\nArgs:\n params: Parameters for retrieving a factory run.\n\nReturns:\n Complete current or terminal factory run envelope." params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id return FactoryRunResult.from_dict(await self._client.request("session.factory.getRun", params_dict, **_timeout_kwargs(timeout))) + async def list_runs(self, params: FactoryListRunsRequest, *, timeout: float | None = None) -> FactoryListRunsResult: + "Lists durable factory runs for this session in creation order.\n\nArgs:\n params: Parameters for paging factory runs.\n\nReturns:\n A page of factory runs in durable creation order." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return FactoryListRunsResult.from_dict(await self._client.request("session.factory.listRuns", params_dict, **_timeout_kwargs(timeout))) + + async def get_run_detail(self, params: FactoryGetRunRequest, *, timeout: float | None = None) -> FactoryRunDetail: + "Gets durable and live observability detail for one factory run.\n\nArgs:\n params: Parameters for retrieving a factory run.\n\nReturns:\n Full factory run observability detail." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return FactoryRunDetail.from_dict(await self._client.request("session.factory.getRunDetail", params_dict, **_timeout_kwargs(timeout))) + + async def get_run_progress(self, params: FactoryGetRunProgressRequest, *, timeout: float | None = None) -> FactoryProgressPage: + "Pages durable progress for one factory run.\n\nArgs:\n params: Parameters for paging factory progress.\n\nReturns:\n A bidirectional page of factory progress." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return FactoryProgressPage.from_dict(await self._client.request("session.factory.getRunProgress", params_dict, **_timeout_kwargs(timeout))) + async def cancel(self, params: FactoryCancelRequest, *, timeout: float | None = None) -> FactoryRunResult: "Requests cancellation of a factory run and returns its run envelope.\n\nArgs:\n params: Parameters for cancelling a factory run.\n\nReturns:\n Complete current or terminal factory run envelope." params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} @@ -28558,7 +32922,7 @@ async def set_reasoning_effort(self, params: ModelSetReasoningEffortRequest, *, params_dict["sessionId"] = self._session_id return ModelSetReasoningEffortResult.from_dict(await self._client.request("session.model.setReasoningEffort", params_dict, **_timeout_kwargs(timeout))) - async def list(self, params: ModelListRequest | None = None, *, timeout: float | None = None) -> SessionModelList: + async def list(self, params: SessionModelListRequest | None = None, *, timeout: float | None = None) -> SessionModelList: "Lists models available to this session using its own auth and integration context. Connected hosts (CLI TUI, GitHub App) should call this through the session client so remote sessions return the remote CLI's available models rather than the caller's.\n\nArgs:\n params: Optional listing options.\n\nReturns:\n The list of models available to this session." params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} if params is not None else {} params_dict["sessionId"] = self._session_id @@ -28644,6 +33008,18 @@ async def get_workspace(self, *, timeout: float | None = None) -> WorkspacesGetW "Gets current workspace metadata for the session.\n\nReturns:\n Current workspace metadata for the session, including its absolute filesystem path when available." return WorkspacesGetWorkspaceResult.from_dict(await self._client.request("session.workspaces.getWorkspace", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + async def update_metadata(self, params: WorkspacesUpdateMetadataRequest, *, timeout: float | None = None) -> WorkspacesGetWorkspaceResult: + "Updates workspace metadata for a local session and returns the refreshed workspace.\n\nArgs:\n params: Workspace metadata fields to update.\n\nReturns:\n Current workspace metadata for the session, including its absolute filesystem path when available." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return WorkspacesGetWorkspaceResult.from_dict(await self._client.request("session.workspaces.updateMetadata", params_dict, **_timeout_kwargs(timeout))) + + async def ensure(self, params: WorkspacesEnsureRequest, *, timeout: float | None = None) -> WorkspacesGetWorkspaceResult: + "Ensures a local session workspace exists and returns it.\n\nArgs:\n params: Optional session context used when creating a local workspace.\n\nReturns:\n Current workspace metadata for the session, including its absolute filesystem path when available." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return WorkspacesGetWorkspaceResult.from_dict(await self._client.request("session.workspaces.ensure", params_dict, **_timeout_kwargs(timeout))) + async def list_files(self, *, timeout: float | None = None) -> WorkspacesListFilesResult: "Lists files stored in the session workspace files directory.\n\nReturns:\n Relative paths of files stored in the session workspace files directory." return WorkspacesListFilesResult.from_dict(await self._client.request("session.workspaces.listFiles", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) @@ -28670,6 +33046,36 @@ async def read_checkpoint(self, params: WorkspacesReadCheckpointRequest, *, time params_dict["sessionId"] = self._session_id return WorkspacesReadCheckpointResult.from_dict(await self._client.request("session.workspaces.readCheckpoint", params_dict, **_timeout_kwargs(timeout))) + async def add_summary(self, params: WorkspacesAddSummaryRequest, *, timeout: float | None = None) -> WorkspacesAddSummaryResult: + "Adds a compaction summary checkpoint to the local session workspace.\n\nArgs:\n params: Compaction summary checkpoint to persist.\n\nReturns:\n Persisted summary metadata and refreshed workspace metadata." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return WorkspacesAddSummaryResult.from_dict(await self._client.request("session.workspaces.addSummary", params_dict, **_timeout_kwargs(timeout))) + + async def truncate_summaries(self, params: WorkspacesTruncateSummariesRequest, *, timeout: float | None = None) -> WorkspacesGetWorkspaceResult: + "Truncates local workspace compaction summaries after a rollback.\n\nArgs:\n params: Rollback point for local workspace summaries.\n\nReturns:\n Current workspace metadata for the session, including its absolute filesystem path when available." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return WorkspacesGetWorkspaceResult.from_dict(await self._client.request("session.workspaces.truncateSummaries", params_dict, **_timeout_kwargs(timeout))) + + async def read_autopilot_objective(self, *, timeout: float | None = None) -> WorkspacesReadAutopilotObjectiveResult: + "Reads the autopilot objective state file from the local session workspace.\n\nReturns:\n Autopilot objective file content, or null when missing." + return WorkspacesReadAutopilotObjectiveResult.from_dict(await self._client.request("session.workspaces.readAutopilotObjective", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + + async def write_autopilot_objective(self, params: WorkspacesWriteAutopilotObjectiveRequest, *, timeout: float | None = None) -> WorkspacesWriteAutopilotObjectiveResult: + "Writes the autopilot objective state file in the local session workspace.\n\nArgs:\n params: Autopilot objective file content to persist.\n\nReturns:\n Result of writing the autopilot objective file." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return WorkspacesWriteAutopilotObjectiveResult.from_dict(await self._client.request("session.workspaces.writeAutopilotObjective", params_dict, **_timeout_kwargs(timeout))) + + async def delete_autopilot_objective(self, *, timeout: float | None = None) -> WorkspacesDeleteAutopilotObjectiveResult: + "Deletes the autopilot objective state file from the local session workspace.\n\nReturns:\n Result of deleting the autopilot objective file." + return WorkspacesDeleteAutopilotObjectiveResult.from_dict(await self._client.request("session.workspaces.deleteAutopilotObjective", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + + async def autopilot_objective_exists(self, *, timeout: float | None = None) -> WorkspacesAutopilotObjectiveExistsResult: + "Checks whether the local session workspace has an autopilot objective state file.\n\nReturns:\n Whether the autopilot objective file exists." + return WorkspacesAutopilotObjectiveExistsResult.from_dict(await self._client.request("session.workspaces.autopilotObjectiveExists", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + async def save_large_paste(self, params: WorkspacesSaveLargePasteRequest, *, timeout: float | None = None) -> WorkspacesSaveLargePasteResult: "Saves pasted content as a UTF-8 file in the session workspace.\n\nArgs:\n params: Pasted content to save as a UTF-8 file in the session workspace.\n\nReturns:\n Descriptor for the saved paste file, or null when the workspace is unavailable." params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} @@ -28677,7 +33083,7 @@ async def save_large_paste(self, params: WorkspacesSaveLargePasteRequest, *, tim return WorkspacesSaveLargePasteResult.from_dict(await self._client.request("session.workspaces.saveLargePaste", params_dict, **_timeout_kwargs(timeout))) async def diff(self, params: WorkspacesDiffRequest, *, timeout: float | None = None) -> WorkspaceDiffResult: - "Computes a diff for the session workspace.\n\nArgs:\n params: Parameters for computing a workspace diff.\n\nReturns:\n Workspace diff result for the requested mode." + "Computes a diff for the session workspace. Never rejects for a busy session: a `session`-mode diff that cannot read the session's file-change captures falls back to an unstaged git diff with `isFallback: true` and reports why in `unavailableReason`.\n\nArgs:\n params: Parameters for computing a workspace diff.\n\nReturns:\n Workspace diff result for the requested mode." params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id return WorkspaceDiffResult.from_dict(await self._client.request("session.workspaces.diff", params_dict, **_timeout_kwargs(timeout))) @@ -28730,9 +33136,17 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client self._session_id = session_id - async def list(self, *, timeout: float | None = None) -> AgentList: - "Lists custom agents available to the session.\n\nReturns:\n Custom agents available to the session." - return AgentList.from_dict(await self._client.request("session.agent.list", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + async def list(self, params: SessionAgentListRequest | None = None, *, timeout: float | None = None) -> AgentList: + "Lists agents available to the session. Defaults to custom agents only; pass includeBuiltInAgents to include the effective built-in agents.\n\nArgs:\n params: Controls whether built-in agents and authored prompt text are included.\n\nReturns:\n Agents available to the session." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} if params is not None else {} + params_dict["sessionId"] = self._session_id + return AgentList.from_dict(await self._client.request("session.agent.list", params_dict, **_timeout_kwargs(timeout))) + + async def set_prompt(self, params: AgentSetPromptRequest, *, timeout: float | None = None) -> None: + "Sets an in-memory authored prompt override for an available agent. For built-in agents, this replaces only the static base prompt while preserving runtime-owned dynamic prompt composition and behavior. The special `general-purpose` agent is not overrideable. Overrides are not persisted; resumed and forked sessions start without them, so the host must re-apply them.\n\nArgs:\n params: An in-memory authored prompt override for an available agent." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + await self._client.request("session.agent.setPrompt", params_dict, **_timeout_kwargs(timeout)) async def get_current(self, *, timeout: float | None = None) -> AgentGetCurrentResult: "Gets the currently selected custom agent for the session.\n\nReturns:\n The currently selected custom agent, or null when using the default agent." @@ -28863,12 +33277,24 @@ async def handle_pending_request(self, params: MCPOauthHandlePendingRequest, *, params_dict["sessionId"] = self._session_id return MCPOauthHandlePendingResult.from_dict(await self._client.request("session.mcp.oauth.handlePendingRequest", params_dict, **_timeout_kwargs(timeout))) + async def authentication_state_changed(self, params: MCPOauthAuthenticationStateChangedRequest, *, timeout: float | None = None) -> None: + "Notifies the session that MCP OAuth authentication succeeded and updated credentials were persisted, so cached tool definitions can be refreshed.\n\nArgs:\n params: Identifies the MCP server whose persisted OAuth credentials were updated." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + await self._client.request("session.mcp.oauth.authenticationStateChanged", params_dict, **_timeout_kwargs(timeout)) + async def login(self, params: MCPOauthLoginRequest, *, timeout: float | None = None) -> MCPOauthLoginResult: "Starts OAuth authentication for a remote MCP server.\n\nArgs:\n params: Remote MCP server name and optional overrides controlling reauthentication, OAuth client display name, callback success-page copy, and static OAuth client selection.\n\nReturns:\n OAuth authorization URL the caller should open, or empty when cached tokens already authenticated the server." params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id return MCPOauthLoginResult.from_dict(await self._client.request("session.mcp.oauth.login", params_dict, **_timeout_kwargs(timeout))) + async def respond(self, params: MCPOauthRespondRequest, *, timeout: float | None = None) -> MCPOauthRespondResult: + "Responds to a pending MCP OAuth authorization request by its request id.\n\nArgs:\n params: Pending MCP OAuth request id to respond to.\n\nReturns:\n Indicates whether the pending MCP OAuth response was accepted." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return MCPOauthRespondResult.from_dict(await self._client.request("session.mcp.oauth.respond", params_dict, **_timeout_kwargs(timeout))) + # Experimental: this API group is experimental and may change or be removed. class McpHeadersApi: @@ -29008,7 +33434,7 @@ async def remove_git_hub(self, *, timeout: float | None = None) -> MCPRemoveGitH return MCPRemoveGitHubResult.from_dict(await self._client.request("session.mcp.removeGitHub", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) async def start_server(self, params: MCPStartServerRequest, *, timeout: float | None = None) -> None: - "Starts an individual MCP server on the live session from a caller-supplied config. Session-scoped and ephemeral: the server is added to this session's running set only and is reaped when the session ends. Does NOT modify persistent user configuration (`mcp.config.*`), so it does not affect future sessions. The server surfaces through `session.mcp.list` and the `session.mcp_servers_loaded` / `session.mcp_server_status_changed` events like any other server.\n\nArgs:\n params: Server name and configuration for an individual MCP server start." + "Starts an individual MCP server on the live session. Omit `config` for a config-free start-by-name of an already-configured server (reuses the server's already-registered configuration); supply `config` to start from a caller-supplied configuration. Session-scoped and ephemeral: the server is added to this session's running set only and is reaped when the session ends. Does NOT modify persistent user configuration (`mcp.config.*`), so it does not affect future sessions. The server surfaces through `session.mcp.list` and the `session.mcp_servers_loaded` / `session.mcp_server_status_changed` events like any other server.\n\nArgs:\n params: Server name and optional configuration for an individual MCP server start. Omit `config` for a config-free start-by-name of an already-configured server." params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id await self._client.request("session.mcp.startServer", params_dict, **_timeout_kwargs(timeout)) @@ -29042,7 +33468,7 @@ async def list(self, *, timeout: float | None = None) -> PluginList: "Lists plugins installed for the session.\n\nReturns:\n Plugins installed for the session, with their enabled state and version metadata." return PluginList.from_dict(await self._client.request("session.plugins.list", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) - async def reload(self, params: PluginsReloadRequest | None = None, *, timeout: float | None = None) -> None: + async def reload(self, params: SessionPluginsReloadRequest | None = None, *, timeout: float | None = None) -> None: "Reloads the session's plugin set, refreshing MCP servers, custom agents, hooks, and skills cache so SDK-driven changes via `server.plugins.*` take effect immediately.\n\nArgs:\n params: Optional flags controlling which side effects the reload performs." params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} if params is not None else {} params_dict["sessionId"] = self._session_id @@ -29055,7 +33481,7 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client self._session_id = session_id - async def get_endpoint(self, params: ProviderGetEndpointRequest | None = None, *, timeout: float | None = None) -> ProviderEndpoint: + async def get_endpoint(self, params: SessionProviderGetEndpointRequest | None = None, *, timeout: float | None = None) -> ProviderEndpoint: "Returns the provider endpoint and credentials the session is currently configured to talk to, so the caller can make inference calls directly against the same backend the session uses.\n\nArgs:\n params: Optional model identifier to scope the endpoint snapshot to.\n\nReturns:\n A snapshot of the provider endpoint the session is currently configured to talk to." params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} if params is not None else {} params_dict["sessionId"] = self._session_id @@ -29160,7 +33586,7 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client self._session_id = session_id - async def list(self, params: CommandsListRequest | None = None, *, timeout: float | None = None) -> CommandList: + async def list(self, params: SessionCommandsListRequest | None = None, *, timeout: float | None = None) -> CommandList: "Lists slash commands available in the session.\n\nArgs:\n params: Optional filters controlling which command sources to include in the listing.\n\nReturns:\n Slash commands available in the session, after applying any include/exclude filters." params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} if params is not None else {} params_dict["sessionId"] = self._session_id @@ -29425,9 +33851,11 @@ async def set_required(self, params: PermissionsSetRequiredRequest, *, timeout: params_dict["sessionId"] = self._session_id return PermissionsSetRequiredResult.from_dict(await self._client.request("session.permissions.setRequired", params_dict, **_timeout_kwargs(timeout))) - async def reset_session_approvals(self, *, timeout: float | None = None) -> PermissionsResetSessionApprovalsResult: - "Clears session-scoped tool permission approvals.\n\nReturns:\n Indicates whether the operation succeeded." - return PermissionsResetSessionApprovalsResult.from_dict(await self._client.request("session.permissions.resetSessionApprovals", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + async def reset_session_approvals(self, params: PermissionsResetSessionApprovalsRequest, *, timeout: float | None = None) -> PermissionsResetSessionApprovalsResult: + "Clears session-scoped tool permission approvals.\n\nArgs:\n params: Clears session-scoped tool permission approvals, and optionally the location-scoped ones.\n\nReturns:\n Indicates whether the operation succeeded." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return PermissionsResetSessionApprovalsResult.from_dict(await self._client.request("session.permissions.resetSessionApprovals", params_dict, **_timeout_kwargs(timeout))) async def notify_prompt_shown(self, params: PermissionPromptShownNotification, *, timeout: float | None = None) -> PermissionsNotifyPromptShownResult: "Notifies the runtime that a permission prompt UI has been shown to the user.\n\nArgs:\n params: Notification payload describing the permission prompt that the client just rendered.\n\nReturns:\n Indicates whether the operation succeeded." @@ -29489,6 +33917,19 @@ async def recompute_context_tokens(self, params: MetadataRecomputeContextTokensR return MetadataRecomputeContextTokensResult.from_dict(await self._client.request("session.metadata.recomputeContextTokens", params_dict, **_timeout_kwargs(timeout))) +# Experimental: this API group is experimental and may change or be removed. +class ContentExclusionApi: + def __init__(self, client: "JsonRpcClient", session_id: str): + self._client = client + self._session_id = session_id + + async def check_paths(self, params: ContentExclusionCheckPathsRequest, *, timeout: float | None = None) -> ContentExclusionCheckPathsResult: + "Checks local file system absolute paths within the session working directory against its content-exclusion policy. Results preserve input order. Unsupported paths/filesystems and unavailable policy evaluation return available false, and callers must treat every requested path as excluded.\n\nArgs:\n params: Local file system absolute paths within the session working directory to check against its content-exclusion policy.\n\nReturns:\n Batch content-exclusion result. Callers must fail closed when policy evaluation is unavailable." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return ContentExclusionCheckPathsResult.from_dict(await self._client.request("session.contentExclusion.checkPaths", params_dict, **_timeout_kwargs(timeout))) + + # Experimental: this API group is experimental and may change or be removed. class ShellApi: def __init__(self, client: "JsonRpcClient", session_id: str): @@ -29496,13 +33937,13 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._session_id = session_id async def exec(self, params: ShellExecRequest, *, timeout: float | None = None) -> ShellExecResult: - "Starts a shell command and streams output through session notifications.\n\nArgs:\n params: Shell command to run, with optional working directory and timeout in milliseconds.\n\nReturns:\n Identifier of the spawned process, used to correlate streamed output and exit notifications." + "Starts a shell command and streams output through session notifications. The command runs as the leader of its own process group (POSIX) or in a dedicated job object (Windows), so a forced termination — via \"shell.kill\", the request timeout, or session disposal — signals that whole group/job rather than only the direct child. Two gaps are worth planning for: a command that exits on its own does not trigger that teardown, and on POSIX a descendant that moves itself into a new session or process group (for example via \"setsid\") leaves the signalled group, so either can leave a background process running.\n\nArgs:\n params: Shell command to run, with optional working directory and timeout in milliseconds.\n\nReturns:\n Identifier of the spawned process, used to correlate streamed output and exit notifications." params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id return ShellExecResult.from_dict(await self._client.request("session.shell.exec", params_dict, **_timeout_kwargs(timeout))) async def kill(self, params: ShellKillRequest, *, timeout: float | None = None) -> ShellKillResult: - "Sends a signal to a shell process previously started via \"shell.exec\".\n\nArgs:\n params: Identifier of a process previously returned by \"shell.exec\" and the signal to send.\n\nReturns:\n Indicates whether the signal was delivered; false if the process was unknown or already exited." + "Sends a signal to a shell process previously started via \"shell.exec\". The signal targets the command's whole process group (POSIX) or job object (Windows), so descendants still in that group are signalled too, not just the direct child. On POSIX a descendant that moved itself into a new session or process group (for example via \"setsid\") is no longer in the signalled group and survives.\n\nArgs:\n params: Identifier of a process previously returned by \"shell.exec\" and the signal to send.\n\nReturns:\n Indicates whether the signal was delivered; false if the process was unknown or already exited." params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id return ShellKillResult.from_dict(await self._client.request("session.shell.kill", params_dict, **_timeout_kwargs(timeout))) @@ -29526,7 +33967,7 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client self._session_id = session_id - async def compact(self, params: HistoryCompactRequest | None = None, *, timeout: float | None = None) -> HistoryCompactResult: + async def compact(self, params: SessionHistoryCompactRequest | None = None, *, timeout: float | None = None) -> HistoryCompactResult: "Compacts the session history to reduce context usage.\n\nArgs:\n params: Optional compaction parameters.\n\nReturns:\n Compaction outcome with the number of tokens and messages removed, summary text, and the resulting context window breakdown." params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} if params is not None else {} params_dict["sessionId"] = self._session_id @@ -29538,6 +33979,22 @@ async def truncate(self, params: HistoryTruncateRequest, *, timeout: float | Non params_dict["sessionId"] = self._session_id return HistoryTruncateResult.from_dict(await self._client.request("session.history.truncate", params_dict, **_timeout_kwargs(timeout))) + async def list_rewind_points(self, *, timeout: float | None = None) -> HistoryListRewindPointsResult: + "Lists the user turns that the session can rewind to. Never rejects for a busy session: rewind reads need the session's file-change captures to be settled, so a session that still holds active work answers with `unavailableReason: \"session-busy\"` and no points, which the caller can retry.\n\nReturns:\n Rewind points and file-change-tracking availability for the session." + return HistoryListRewindPointsResult.from_dict(await self._client.request("session.history.listRewindPoints", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + + async def preview_rewind(self, params: HistoryPreviewRewindRequest, *, timeout: float | None = None) -> HistoryPreviewRewindResult: + "Previews the files that a conversation-and-files rewind would restore.\n\nArgs:\n params: Event boundary to preview for conversation-and-files rewind.\n\nReturns:\n Files and aggregate changes for a prospective rewind." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return HistoryPreviewRewindResult.from_dict(await self._client.request("session.history.previewRewind", params_dict, **_timeout_kwargs(timeout))) + + async def rewind(self, params: HistoryRewindRequest, *, timeout: float | None = None) -> HistoryRewindResult: + "Rewinds the session conversation, optionally restoring files changed by the discarded turns. Not crash-atomic: file restore and conversation truncation are separate stores, applied in that order, so a process crash between them can leave the workspace rewound while the conversation still contains the discarded turns. There is no recovery journal; re-running the same rewind is the recovery path for a crash before truncation lands, since file restore is idempotent (already-restored files are reported as skipped) and truncation is re-derived from the still-retained boundary event. After truncation lands that boundary no longer exists, so the same request is rejected; the only stage that can still be outstanding is snapshot pruning, whose failure leaves orphan snapshots the capture store tolerates. The reverse inconsistency cannot occur, because truncation is never applied before file restore succeeds.\n\nArgs:\n params: Boundary and mode for rewinding session history.\n\nReturns:\n Structured outcome of a rewind request." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return HistoryRewindResult.from_dict(await self._client.request("session.history.rewind", params_dict, **_timeout_kwargs(timeout))) + async def cancel_background_compaction(self, *, timeout: float | None = None) -> HistoryCancelBackgroundCompactionResult: "Cancels any in-progress background compaction on a local session.\n\nReturns:\n Indicates whether an in-progress background compaction was cancelled." return HistoryCancelBackgroundCompactionResult.from_dict(await self._client.request("session.history.cancelBackgroundCompaction", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) @@ -29550,6 +34007,12 @@ async def summarize_for_handoff(self, *, timeout: float | None = None) -> Histor "Produces a markdown summary of the session's conversation context for hand-off scenarios.\n\nReturns:\n Markdown summary of the conversation context (empty when not available)." return HistorySummarizeForHandoffResult.from_dict(await self._client.request("session.history.summarizeForHandoff", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + async def clear_context(self, params: HistoryClearContextRequest, *, timeout: float | None = None) -> HistoryClearContextResult: + "Clears the session's conversation history, keeping only system and developer messages, and seeds the fresh context window with a first user message. Must be called from inside a tool handler: the clear has to drop the results of the tool calls its wipe orphans, and it rejects when no tool call is in flight.\n\nArgs:\n params: Parameters for clearing the conversation and seeding the window that replaces it.\n\nReturns:\n What a successful clear removed. A clear that could not be applied rejects instead of reporting a count." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return HistoryClearContextResult.from_dict(await self._client.request("session.history.clearContext", params_dict, **_timeout_kwargs(timeout))) + # Experimental: this API group is experimental and may change or be removed. class QueueApi: @@ -29561,6 +34024,48 @@ async def pending_items(self, *, timeout: float | None = None) -> QueuePendingIt "Returns the local session's pending user-facing queued items and steering messages.\n\nReturns:\n Snapshot of the session's pending queued items and immediate-steering messages." return QueuePendingItemsResult.from_dict(await self._client.request("session.queue.pendingItems", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + async def move_item(self, params: QueueMoveItemRequest, *, timeout: float | None = None) -> QueueMoveItemResult: + "Moves an addressable queued item to a public visible position.\n\nArgs:\n params: Parameters for moving a queued item by stable id.\n\nReturns:\n Result of moving a queued item." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return QueueMoveItemResult.from_dict(await self._client.request("session.queue.moveItem", params_dict, **_timeout_kwargs(timeout))) + + async def insert_at(self, params: QueueInsertAtRequest, *, timeout: float | None = None) -> QueueInsertAtResult: + "Inserts a new queued message at a public visible position.\n\nArgs:\n params: Parameters for inserting a queued message at a public visible position.\n\nReturns:\n Result of inserting a queued message." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return QueueInsertAtResult.from_dict(await self._client.request("session.queue.insertAt", params_dict, **_timeout_kwargs(timeout))) + + async def remove_at(self, params: QueueRemoveAtRequest, *, timeout: float | None = None) -> QueueRemoveAtResult: + "Removes an addressable queued item by its stable id.\n\nArgs:\n params: Parameters for removing a queued item by stable id.\n\nReturns:\n Result of removing a queued item." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return QueueRemoveAtResult.from_dict(await self._client.request("session.queue.removeAt", params_dict, **_timeout_kwargs(timeout))) + + async def update_text(self, params: QueueUpdateTextRequest, *, timeout: float | None = None) -> QueueUpdateTextResult: + "Updates the text of an addressable single-message queue item.\n\nArgs:\n params: Parameters for editing a single queued message.\n\nReturns:\n Result of editing a queued message." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return QueueUpdateTextResult.from_dict(await self._client.request("session.queue.updateText", params_dict, **_timeout_kwargs(timeout))) + + async def duplicate_at(self, params: QueueDuplicateAtRequest, *, timeout: float | None = None) -> QueueDuplicateAtResult: + "Duplicates an addressable queued item immediately after its source.\n\nArgs:\n params: Parameters for duplicating a queued item.\n\nReturns:\n Result of duplicating a queued item." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return QueueDuplicateAtResult.from_dict(await self._client.request("session.queue.duplicateAt", params_dict, **_timeout_kwargs(timeout))) + + async def set_drain_paused(self, params: QueueSetDrainPausedRequest, *, timeout: float | None = None) -> None: + "Acquires or releases the queued-lane drain pause.\n\nArgs:\n params: Parameters for acquiring or releasing the queued-lane drain pause. Acquisition is exclusive and non-idempotent: `paused: true` against an already-paused session fails with `queue_already_paused`. The pause is never released automatically — it is not tied to the caller's lifetime, so a client that exits without sending `paused: false` leaves the lane frozen. Release is unowned: `paused: false` clears the pause for any caller, including one that never acquired it." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + await self._client.request("session.queue.setDrainPaused", params_dict, **_timeout_kwargs(timeout)) + + async def send_now(self, params: QueueSendNowRequest, *, timeout: float | None = None) -> QueueSendNowResult: + "Moves an addressable queued message into the live turn's steering lane.\n\nArgs:\n params: Parameters for steering a queued message into a live turn.\n\nReturns:\n Result of trying to steer a queued message into a live turn." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return QueueSendNowResult.from_dict(await self._client.request("session.queue.sendNow", params_dict, **_timeout_kwargs(timeout))) + async def remove_most_recent(self, *, timeout: float | None = None) -> QueueRemoveMostRecentResult: "Removes the most recently queued user-facing item (LIFO).\n\nReturns:\n Indicates whether a user-facing pending item was removed." return QueueRemoveMostRecentResult.from_dict(await self._client.request("session.queue.removeMostRecent", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) @@ -29577,7 +34082,7 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._session_id = session_id async def read(self, params: EventLogReadRequest, *, timeout: float | None = None) -> EventsReadResult: - "Reads a batch of session events from a cursor, optionally waiting for new events.\n\nArgs:\n params: Cursor, batch size, and optional long-poll/filter parameters for reading session events.\n\nReturns:\n Batch of session events returned by a read, with cursor and continuation metadata." + "Reads a batch of session events from a cursor, optionally waiting for new events. Supports tail-first reads via `direction: backward`.\n\nArgs:\n params: Cursor, batch size, and optional long-poll/filter parameters for reading session events.\n\nReturns:\n Batch of session events returned by a read, with cursor and continuation metadata." params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id return EventsReadResult.from_dict(await self._client.request("session.eventLog.read", params_dict, **_timeout_kwargs(timeout))) @@ -29610,6 +34115,19 @@ async def get_metrics(self, *, timeout: float | None = None) -> UsageGetMetricsR return UsageGetMetricsResult.from_dict(await self._client.request("session.usage.getMetrics", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) +# Experimental: this API group is experimental and may change or be removed. +class LimitPredictionApi: + def __init__(self, client: "JsonRpcClient", session_id: str): + self._client = client + self._session_id = session_id + + async def predict(self, params: SessionLimitPredictionPredictRequest | None = None, *, timeout: float | None = None) -> SessionLimitPredictionResult: + "Predicts an AI-credit session limit for the session's resolved model. Returns an unavailable result instead of falling back when the current model is unresolved auto.\n\nArgs:\n params: Parameters for predicting an AI-credit session limit. Omitting `modelId` uses the session's currently selected model.\n\nReturns:\n Prediction result. Available results include prediction details; unavailable results include an explicit reason." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} if params is not None else {} + params_dict["sessionId"] = self._session_id + return SessionLimitPredictionResult.from_dict(await self._client.request("session.limitPrediction.predict", params_dict, **_timeout_kwargs(timeout))) + + # Experimental: this API group is experimental and may change or be removed. class RemoteApi: def __init__(self, client: "JsonRpcClient", session_id: str): @@ -29699,11 +34217,13 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self.ui = UiApi(client, session_id) self.permissions = PermissionsApi(client, session_id) self.metadata = MetadataApi(client, session_id) + self.content_exclusion = ContentExclusionApi(client, session_id) self.shell = ShellApi(client, session_id) self.history = HistoryApi(client, session_id) self.queue = QueueApi(client, session_id) self.event_log = EventLogApi(client, session_id) self.usage = UsageApi(client, session_id) + self.limit_prediction = LimitPredictionApi(client, session_id) self.remote = RemoteApi(client, session_id) self.visibility = VisibilityApi(client, session_id) self.schedule = ScheduleApi(client, session_id) @@ -29730,6 +34250,16 @@ async def abort(self, params: AbortRequest, *, timeout: float | None = None) -> params_dict["sessionId"] = self._session_id return AbortResult.from_dict(await self._client.request("session.abort", params_dict, **_timeout_kwargs(timeout))) + async def interrupt_main_turn(self, params: InterruptMainTurnRequest, *, timeout: float | None = None) -> InterruptMainTurnResult: + "Interrupts the current main agent turn while leaving running background work (subagents, sidekicks, and promoted attached shells) alive. No-op when the main loop is not processing.\n\nArgs:\n params: Parameters for interrupting the main agent turn.\n\nReturns:\n Result of interrupting the main agent turn.\n\n.. warning:: This API is experimental and may change or be removed in future versions." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return InterruptMainTurnResult.from_dict(await self._client.request("session.interruptMainTurn", params_dict, **_timeout_kwargs(timeout))) + + async def cancel_all_background_agents(self, *, timeout: float | None = None) -> int: + "Cancels every running background agent (task-registry subagents plus sidekick agents) without interrupting the main agent loop. Promoted attached shells are left running.\n\nReturns:\n The number of running background agents (task-registry agents) that were cancelled.\n\n.. warning:: This API is experimental and may change or be removed in future versions." + return int(await self._client.request("session.cancelAllBackgroundAgents", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + async def shutdown(self, params: ShutdownRequest, *, timeout: float | None = None) -> None: "Shuts down the session and persists its final state. Awaits any deferred sessionEnd hooks before resolving so user-supplied hook scripts complete before the runtime tears down.\n\nArgs:\n params: Parameters for shutting down the session\n\n.. warning:: This API is experimental and may change or be removed in future versions." params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} @@ -29791,6 +34321,98 @@ async def _evaluate_predicate(self, params: SessionSettingsEvaluatePredicateRequ return SessionSettingsEvaluatePredicateResult.from_dict(await self._client.request("session.settings.evaluatePredicate", params_dict, **_timeout_kwargs(timeout))) +# Experimental: this API group is experimental and may change or be removed. +class _InternalQueueApi: + def __init__(self, client: "JsonRpcClient", session_id: str): + self._client = client + self._session_id = session_id + + async def _snapshot(self, *, timeout: float | None = None) -> QueueSnapshotResult: + "Returns the internal native queue snapshot for in-process session orchestration.\n\nReturns:\n Internal snapshot of native queue state for local session orchestration.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + return QueueSnapshotResult.from_dict(await self._client.request("session.queue.snapshot", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + + async def _has_pending(self, *, timeout: float | None = None) -> QueueHasPendingResult: + "Reports whether the local session has native queued work pending.\n\nReturns:\n Whether the native queue has pending work.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + return QueueHasPendingResult.from_dict(await self._client.request("session.queue.hasPending", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + + async def _begin_deferred_idle_drain(self, params: QueueBeginDeferredIdleDrainRequest, *, timeout: float | None = None) -> QueueBeginDeferredIdleDrainResult: + "Begins a native deferred-idle drain when background work has quiesced.\n\nArgs:\n params: Inputs for starting a deferred-idle drain.\n\nReturns:\n Whether a deferred-idle drain should run.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return QueueBeginDeferredIdleDrainResult.from_dict(await self._client.request("session.queue.beginDeferredIdleDrain", params_dict, **_timeout_kwargs(timeout))) + + async def _finish_deferred_idle_drain(self, params: QueueFinishDeferredIdleDrainRequest, *, timeout: float | None = None) -> QueueFinishDeferredIdleDrainResult: + "Finishes a native deferred-idle drain and reports whether to drain queue work or emit idle.\n\nArgs:\n params: Inputs for completing a deferred-idle drain.\n\nReturns:\n Action selected by the native deferred-idle drain.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return QueueFinishDeferredIdleDrainResult.from_dict(await self._client.request("session.queue.finishDeferredIdleDrain", params_dict, **_timeout_kwargs(timeout))) + + async def _defer_session_idle(self, params: QueueDeferSessionIdleRequest, *, timeout: float | None = None) -> None: + "Marks session.idle as deferred by native background work state.\n\nArgs:\n params: Inputs for marking session.idle deferred in native state.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + await self._client.request("session.queue.deferSessionIdle", params_dict, **_timeout_kwargs(timeout)) + + async def _consume_system_notifications(self, params: QueueConsumeSystemNotificationsRequest, *, timeout: float | None = None) -> QueueRemoveMostRecentResult: + "Consumes queued native system notifications matching an internal filter.\n\nArgs:\n params: Internal filter for consuming queued system notifications.\n\nReturns:\n Indicates whether a user-facing pending item was removed.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return QueueRemoveMostRecentResult.from_dict(await self._client.request("session.queue.consumeSystemNotifications", params_dict, **_timeout_kwargs(timeout))) + + async def _enqueue_resume_pending(self, *, timeout: float | None = None) -> QueueEnqueueResumePendingResult: + "Enqueues the internal resume-pending wake item when orphan handling needs a follow-up turn.\n\nReturns:\n Result of enqueueing the resume-pending wake item.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + return QueueEnqueueResumePendingResult.from_dict(await self._client.request("session.queue.enqueueResumePending", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + + async def _process(self, *, timeout: float | None = None) -> None: + "Drains the native local-session work queue for in-process session orchestration.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + await self._client.request("session.queue.process", {"sessionId": self._session_id}, **_timeout_kwargs(timeout)) + + +# Experimental: this API group is experimental and may change or be removed. +class _InternalScheduleApi: + def __init__(self, client: "JsonRpcClient", session_id: str): + self._client = client + self._session_id = session_id + + async def _hydrate(self, *, timeout: float | None = None) -> None: + "Hydrates the native schedule registry from persisted session events.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + await self._client.request("session.schedule.hydrate", {"sessionId": self._session_id}, **_timeout_kwargs(timeout)) + + async def _has_self_paced(self, *, timeout: float | None = None) -> ScheduleHasSelfPacedResult: + "Reports whether the session has an active self-paced scheduled prompt.\n\nReturns:\n Whether the session currently has an active self-paced schedule.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + return ScheduleHasSelfPacedResult.from_dict(await self._client.request("session.schedule.hasSelfPaced", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + + async def _add(self, params: ScheduleAddRequest, *, timeout: float | None = None) -> ScheduleAddResult: + "Registers a relative-interval scheduled prompt.\n\nArgs:\n params: Register a relative-interval scheduled prompt.\n\nReturns:\n Result of registering or re-arming a scheduled prompt.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return ScheduleAddResult.from_dict(await self._client.request("session.schedule.add", params_dict, **_timeout_kwargs(timeout))) + + async def _add_cron(self, params: ScheduleAddCronRequest, *, timeout: float | None = None) -> ScheduleAddResult: + "Registers a recurring cron scheduled prompt.\n\nArgs:\n params: Register a cron scheduled prompt.\n\nReturns:\n Result of registering or re-arming a scheduled prompt.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return ScheduleAddResult.from_dict(await self._client.request("session.schedule.addCron", params_dict, **_timeout_kwargs(timeout))) + + async def _add_at(self, params: ScheduleAddAtRequest, *, timeout: float | None = None) -> ScheduleAddResult: + "Registers an absolute-time scheduled prompt.\n\nArgs:\n params: Register an absolute-time scheduled prompt.\n\nReturns:\n Result of registering or re-arming a scheduled prompt.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return ScheduleAddResult.from_dict(await self._client.request("session.schedule.addAt", params_dict, **_timeout_kwargs(timeout))) + + async def _add_self_paced(self, params: ScheduleAddSelfPacedRequest, *, timeout: float | None = None) -> ScheduleAddResult: + "Registers a self-paced scheduled prompt.\n\nArgs:\n params: Register a self-paced scheduled prompt.\n\nReturns:\n Result of registering or re-arming a scheduled prompt.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return ScheduleAddResult.from_dict(await self._client.request("session.schedule.addSelfPaced", params_dict, **_timeout_kwargs(timeout))) + + async def _rearm_self_paced(self, params: ScheduleRearmSelfPacedRequest, *, timeout: float | None = None) -> ScheduleAddResult: + "Re-arms an active self-paced scheduled prompt.\n\nArgs:\n params: Re-arm a self-paced scheduled prompt.\n\nReturns:\n Result of registering or re-arming a scheduled prompt.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + return ScheduleAddResult.from_dict(await self._client.request("session.schedule.rearmSelfPaced", params_dict, **_timeout_kwargs(timeout))) + + class _InternalSessionRpc: """Internal SDK session-scoped RPC methods. Not part of the public API.""" def __init__(self, client: "JsonRpcClient", session_id: str): @@ -29798,6 +34420,14 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._session_id = session_id self.mcp = _InternalMcpApi(client, session_id) self.settings = _InternalSettingsApi(client, session_id) + self.queue = _InternalQueueApi(client, session_id) + self.schedule = _InternalScheduleApi(client, session_id) + + async def _send_system_notification(self, params: SendSystemNotificationRequest, *, timeout: float | None = None) -> None: + "Queues or sends an internal system notification to the session according to its passive policy.\n\nArgs:\n params: Internal request for sending a system notification.\n\n.. warning:: This API is experimental and may change or be removed in future versions.\n\n:meta private:\n\nInternal SDK API; not part of the public surface." + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + await self._client.request("session.sendSystemNotification", params_dict, **_timeout_kwargs(timeout)) # Experimental: this API group is experimental and may change or be removed. @@ -29848,7 +34478,10 @@ async def rename(self, params: SessionFSRenameRequest) -> SessionFSError | None: "Renames or moves a path in the client-provided session filesystem.\n\nArgs:\n params: Source and destination paths for renaming or moving an entry in the client-provided session filesystem.\n\nReturns:\n Describes a filesystem error." pass async def sqlite_query(self, params: SessionFSSqliteQueryRequest) -> SessionFSSqliteQueryResult: - "Executes a SQLite query against the per-session database.\n\nArgs:\n params: SQL query, query type, and optional bind parameters for executing a SQLite query against the per-session database.\n\nReturns:\n Query results including rows, columns, and rows affected, or a filesystem error if execution failed." + "Executes a SQLite query against the per-session database. Providers apply busy handling for every call.\n\nArgs:\n params: SQL query, query type, and optional bind parameters for executing a SQLite query against the per-session database. The provider applies its SQLite busy timeout for every call.\n\nReturns:\n Query results including rows, columns, and rows affected, or a filesystem error if execution failed." + pass + async def sqlite_transaction(self, params: SessionFSSqliteTransactionRequest) -> SessionFSSqliteTransactionResult: + "Executes SQLite statements atomically on the provider-owned connection.\n\nArgs:\n params: Statements to execute atomically. Providers apply busy handling for every call.\n\nReturns:\n Per-statement results, or a classified transaction error." pass async def sqlite_exists(self, params: SessionFSSqliteExistsRequest) -> SessionFSSqliteExistsResult: "Checks whether the per-session SQLite database already exists, without creating it.\n\nArgs:\n params: Identifies the target session.\n\nReturns:\n Indicates whether the per-session SQLite database already exists." @@ -29976,6 +34609,13 @@ async def handle_session_fs_sqlite_query(params: dict) -> dict | None: result = await handler.sqlite_query(request) return result.to_dict() client.set_request_handler("sessionFs.sqliteQuery", handle_session_fs_sqlite_query) + async def handle_session_fs_sqlite_transaction(params: dict) -> dict | None: + request = SessionFSSqliteTransactionRequest.from_dict(params) + handler = get_handlers(request.session_id).session_fs + if handler is None: raise RuntimeError(f"No session_fs handler registered for session: {request.session_id}") + result = await handler.sqlite_transaction(request) + return result.to_dict() + client.set_request_handler("sessionFs.sqliteTransaction", handle_session_fs_sqlite_transaction) async def handle_session_fs_sqlite_exists(params: dict) -> dict | None: request = SessionFSSqliteExistsRequest.from_dict(params) handler = get_handlers(request.session_id).session_fs @@ -30011,6 +34651,12 @@ async def invoke(self, params: _HookInvokeRequest) -> _HookInvokeResponse: "Dispatches one SDK callback hook from the runtime to the connection that registered it. Internal transport plumbing: clients opt in through session initialization and the Rust hook processor owns ordering, policy, timeout, and callback routing.\n\nArgs:\n params: Runtime-owned wire payload for a server-to-client hook callback invocation.\n\nReturns:\n Optional output returned by an SDK callback hook." pass +# Experimental: this API group is experimental and may change or be removed. +class ExtensionLaunchProviderHandler(Protocol): + async def resolve(self, params: ExtensionLaunchProviderResolveRequest) -> ExtensionLaunchProviderResolveResult: + "Asks the registered SDK client to resolve an opaque process launch profile for one discovered extension entrypoint immediately before launch or reload. The provider must respond within 15 seconds.\n\nArgs:\n params: A discovered extension entrypoint that the registered integrator may classify and resolve to an opaque launch profile.\n\nReturns:\n The launch profile for a supported entrypoint. Omit launch when the provider does not support the entrypoint." + pass + # Experimental: this API group is experimental and may change or be removed. class LlmInferenceHandler(Protocol): async def http_request_start(self, params: LlmInferenceHTTPRequestStartRequest) -> LlmInferenceHTTPRequestStartResult: @@ -30029,6 +34675,7 @@ async def event(self, params: GitHubTelemetryNotification) -> None: @dataclass class ClientGlobalApiHandlers: hooks: HooksHandler | None = None + extension_launch_provider: ExtensionLaunchProviderHandler | None = None llm_inference: LlmInferenceHandler | None = None git_hub_telemetry: GitHubTelemetryHandler | None = None @@ -30049,6 +34696,13 @@ async def handle_hooks_invoke(params: dict) -> dict | None: result = await handler.invoke(request) return result.to_dict() client.set_request_handler("hooks.invoke", handle_hooks_invoke) + async def handle_extension_launch_provider_resolve(params: dict) -> dict | None: + request = ExtensionLaunchProviderResolveRequest.from_dict(params) + handler = handlers.extension_launch_provider + if handler is None: raise RuntimeError("No extension_launch_provider client-global handler registered") + result = await handler.resolve(request) + return result.to_dict() + client.set_request_handler("extensionLaunchProvider.resolve", handle_extension_launch_provider_resolve) async def handle_llm_inference_http_request_start(params: dict) -> dict | None: request = LlmInferenceHTTPRequestStartRequest.from_dict(params) handler = handlers.llm_inference @@ -30096,6 +34750,7 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "AgentInfo", "AgentInfoSource", "AgentList", + "AgentListRequest", "AgentRegistryLiveTargetEntry", "AgentRegistryLiveTargetEntryAttentionKind", "AgentRegistryLiveTargetEntryKind", @@ -30120,6 +34775,7 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "AgentReloadResult", "AgentSelectRequest", "AgentSelectResult", + "AgentSetPromptRequest", "AgentsDiscoverRequest", "AgentsGetDiscoveryPathsRequest", "AllowAllPermissionSetResult", @@ -30127,6 +34783,8 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "ApprovalKind", "AuthInfo", "AuthInfoType", + "BuiltInModelCatalog", + "BuiltInModelCatalogEntry", "CancelUserRequestedShellCommandResult", "CanvasAction", "CanvasActionApi", @@ -30147,6 +34805,7 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "CanvasProviderOpenResult", "CanvasSessionContext", "CapiSessionOptions", + "Categories", "ClientGlobalApiHandlers", "ClientSessionApiHandlers", "CommandList", @@ -30166,6 +34825,10 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "ConnectedRemoteSessionMetadata", "ConnectedRemoteSessionMetadataKind", "ConnectedRemoteSessionMetadataRepository", + "ContentExclusionApi", + "ContentExclusionCheckPathsRequest", + "ContentExclusionCheckPathsResult", + "ContentExclusionPathCheck", "ContentFilterMode", "ContextHeaviestMessage", "CopilotAPITokenAuthInfo", @@ -30190,7 +34853,15 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "DebugCollectLogsResultKind", "DebugCollectLogsSkippedEntry", "DebugCollectLogsSource", + "DisableBypassPermissionsMode", "DiscoveredCanvas", + "DiscoveredExtension", + "DiscoveredExtensionMode", + "DiscoveredExtensionPlugin", + "DiscoveredExtensionSource", + "DiscoveredExtensions", + "DiscoveredExtensionsDisableRequest", + "DiscoveredExtensionsEnableRequest", "DiscoveredMCPServer", "DiscoveredMCPServerType", "EnqueueCommandParams", @@ -30205,12 +34876,17 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "EventLogTypes", "EventsAgentScope", "EventsCursorStatus", + "EventsReadDirection", "EventsReadResult", "ExecuteCommandParams", "ExecuteCommandResult", "Extension", "ExtensionContextPushInput", "ExtensionContextPushInputType", + "ExtensionLaunchProfile", + "ExtensionLaunchProviderHandler", + "ExtensionLaunchProviderResolveRequest", + "ExtensionLaunchProviderResolveResult", "ExtensionList", "ExtensionSource", "ExtensionStatus", @@ -30244,19 +34920,34 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "FactoryAgentOptions", "FactoryAgentRequest", "FactoryAgentResult", + "FactoryAgentSummary", "FactoryApi", "FactoryCancelRequest", + "FactoryCurrentPhase", + "FactoryDeclaredLimits", + "FactoryDurableOperation", "FactoryExecuteRequest", "FactoryExecuteResult", + "FactoryGetRunProgressRequest", "FactoryGetRunRequest", "FactoryHandler", "FactoryJournalApi", "FactoryJournalGetRequest", "FactoryJournalGetResult", "FactoryJournalPutRequest", + "FactoryListRunsRequest", + "FactoryListRunsResult", "FactoryLogLine", "FactoryLogLineKind", "FactoryLogRequest", + "FactoryPhaseObservation", + "FactoryPhaseStatus", + "FactoryProgressLine", + "FactoryProgressPage", + "FactoryResumeRequest", + "FactoryResumeResult", + "FactoryRunConsumed", + "FactoryRunDetail", "FactoryRunFailure", "FactoryRunFailureKind", "FactoryRunFailureType", @@ -30264,6 +34955,8 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "FactoryRunRequest", "FactoryRunResult", "FactoryRunStatus", + "FactoryRunSummary", + "FactoryRunTerminal", "FilterMapping", "FleetApi", "FleetStartRequest", @@ -30286,9 +34979,24 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "HistoryAbortManualCompactionResult", "HistoryApi", "HistoryCancelBackgroundCompactionResult", + "HistoryClearContextRequest", + "HistoryClearContextResult", "HistoryCompactContextWindow", "HistoryCompactRequest", "HistoryCompactResult", + "HistoryFileRestoreSkipReason", + "HistoryListRewindPointsResult", + "HistoryPreviewRewindRequest", + "HistoryPreviewRewindResult", + "HistoryRewindChangeType", + "HistoryRewindFilePreview", + "HistoryRewindMode", + "HistoryRewindOutcome", + "HistoryRewindPoint", + "HistoryRewindRequest", + "HistoryRewindResult", + "HistoryRewindUnavailableReason", + "HistorySkippedFileRestore", "HistorySummarizeForHandoffResult", "HistoryTruncateRequest", "HistoryTruncateResult", @@ -30313,7 +35021,10 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "InstructionsDiscoverRequest", "InstructionsGetDiscoveryPathsRequest", "InstructionsGetSourcesResult", + "InterruptMainTurnRequest", + "InterruptMainTurnResult", "KindEnum", + "LimitPredictionApi", "LlmInferenceHTTPRequestChunkRequest", "LlmInferenceHTTPRequestChunkResult", "LlmInferenceHTTPRequestStartRequest", @@ -30375,12 +35086,15 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "MCPIsServerRunningResult", "MCPListToolsRequest", "MCPListToolsResult", + "MCPOauthAuthenticationStateChangedRequest", "MCPOauthHandlePendingRequest", "MCPOauthHandlePendingResult", "MCPOauthLoginRequest", "MCPOauthLoginResult", "MCPOauthPendingRequestResponse", "MCPOauthPendingRequestResponseKind", + "MCPOauthRespondRequest", + "MCPOauthRespondResult", "MCPRegisterExternalClientRequest", "MCPReloadWithConfigRequest", "MCPRemoveGitHubResult", @@ -30418,6 +35132,7 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "MCPToolUIVisibility", "MCPTools", "MCPUnregisterExternalClientRequest", + "ManagedSettingsReadResult", "MarketplaceAddResult", "MarketplaceBrowseResult", "MarketplaceInfo", @@ -30507,7 +35222,6 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "PendingPermissionRequest", "PendingPermissionRequestList", "PermissionDecision", - "PermissionDecisionApproveForIonApproval", "PermissionDecisionApproveForLocation", "PermissionDecisionApproveForLocationApproval", "PermissionDecisionApproveForLocationApprovalCommands", @@ -30518,6 +35232,8 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "PermissionDecisionApproveForLocationApprovalExtensionManagementKind", "PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess", "PermissionDecisionApproveForLocationApprovalExtensionPermissionAccessKind", + "PermissionDecisionApproveForLocationApprovalFactory", + "PermissionDecisionApproveForLocationApprovalFactoryKind", "PermissionDecisionApproveForLocationApprovalMCP", "PermissionDecisionApproveForLocationApprovalMCPKind", "PermissionDecisionApproveForLocationApprovalMCPSampling", @@ -30535,6 +35251,7 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "PermissionDecisionApproveForSessionApprovalCustomTool", "PermissionDecisionApproveForSessionApprovalExtensionManagement", "PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess", + "PermissionDecisionApproveForSessionApprovalFactory", "PermissionDecisionApproveForSessionApprovalMCP", "PermissionDecisionApproveForSessionApprovalMCPSampling", "PermissionDecisionApproveForSessionApprovalMemory", @@ -30553,6 +35270,7 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "PermissionDecisionApprovedKind", "PermissionDecisionCancelled", "PermissionDecisionCancelledKind", + "PermissionDecisionContext", "PermissionDecisionDeniedByContentExclusionPolicy", "PermissionDecisionDeniedByContentExclusionPolicyKind", "PermissionDecisionDeniedByPermissionRequestHook", @@ -30564,9 +35282,12 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser", "PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUserKind", "PermissionDecisionKind", + "PermissionDecisionOutcome", "PermissionDecisionReject", "PermissionDecisionRejectKind", "PermissionDecisionRequest", + "PermissionDecisionSource", + "PermissionDecisionSurface", "PermissionDecisionUserNotAvailable", "PermissionDecisionUserNotAvailableKind", "PermissionLocationAddToolApprovalParams", @@ -30604,6 +35325,7 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "PermissionsLocationsAddToolApprovalDetailsCustomTool", "PermissionsLocationsAddToolApprovalDetailsExtensionManagement", "PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess", + "PermissionsLocationsAddToolApprovalDetailsFactory", "PermissionsLocationsAddToolApprovalDetailsMCP", "PermissionsLocationsAddToolApprovalDetailsMCPSampling", "PermissionsLocationsAddToolApprovalDetailsMemory", @@ -30704,7 +35426,6 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "PushAttachmentGitHubReleaseType", "PushAttachmentGitHubRepository", "PushAttachmentGitHubRepositoryType", - "PushAttachmentGitHubSide", "PushAttachmentGitHubSnippet", "PushAttachmentGitHubSnippetType", "PushAttachmentGitHubTreeComparison", @@ -30720,10 +35441,33 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "PushAttachmentType", "PushGitHubRepoRef", "QueueApi", + "QueueBeginDeferredIdleDrainRequest", + "QueueBeginDeferredIdleDrainResult", + "QueueConsumeSystemNotificationsRequest", + "QueueDeferSessionIdleRequest", + "QueueDuplicateAtRequest", + "QueueDuplicateAtResult", + "QueueEnqueueResumePendingResult", + "QueueFinishDeferredIdleDrainRequest", + "QueueFinishDeferredIdleDrainResult", + "QueueHasPendingResult", + "QueueInsertAtRequest", + "QueueInsertAtResult", + "QueueInsertMessage", + "QueueMoveItemRequest", + "QueueMoveItemResult", "QueuePendingItems", "QueuePendingItemsKind", "QueuePendingItemsResult", + "QueueRemoveAtRequest", + "QueueRemoveAtResult", "QueueRemoveMostRecentResult", + "QueueSendNowRequest", + "QueueSendNowResult", + "QueueSetDrainPausedRequest", + "QueueSnapshotResult", + "QueueUpdateTextRequest", + "QueueUpdateTextResult", "QueuedCommandHandled", "QueuedCommandNotHandled", "QueuedCommandResult", @@ -30759,16 +35503,25 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "RemoteSessionRepository", "RunOptions", "SandboxConfig", + "SandboxConfigAuth", "SandboxConfigUserPolicy", "SandboxConfigUserPolicyExperimental", "SandboxConfigUserPolicyExperimentalSeatbelt", "SandboxConfigUserPolicyFilesystem", "SandboxConfigUserPolicyNetwork", + "SandboxConfigUserPolicyNetworkProxy", "SandboxConfigUserPolicySeatbelt", "Saved", + "ScheduleAddAtRequest", + "ScheduleAddCronRequest", + "ScheduleAddRequest", + "ScheduleAddResult", + "ScheduleAddSelfPacedRequest", "ScheduleApi", "ScheduleEntry", + "ScheduleHasSelfPacedResult", "ScheduleList", + "ScheduleRearmSelfPacedRequest", "ScheduleStopRequest", "ScheduleStopResult", "SecretsAddFilterValuesRequest", @@ -30781,14 +35534,17 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "SendMode", "SendRequest", "SendResult", + "SendSystemNotificationRequest", "ServerAccountApi", "ServerAgentList", "ServerAgentRegistryApi", "ServerAgentsApi", "ServerCommandsApi", + "ServerExtensionsApi", "ServerInstructionSourceList", "ServerInstructionsApi", "ServerLlmInferenceApi", + "ServerManagedSettingsApi", "ServerMcpApi", "ServerMcpConfigApi", "ServerModelsApi", @@ -30807,9 +35563,12 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "ServerUserApi", "ServerUserSettingsApi", "SessionActivity", + "SessionAgentListRequest", "SessionAuthStatus", "SessionBulkDeleteResult", + "SessionCancelAllBackgroundAgentsResult", "SessionCapability", + "SessionCommandsListRequest", "SessionCompletionItem", "SessionContext", "SessionContextAttribution", @@ -30840,24 +35599,44 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "SessionFSSqliteQueryRequest", "SessionFSSqliteQueryResult", "SessionFSSqliteQueryType", + "SessionFSSqliteTransactionError", + "SessionFSSqliteTransactionErrorClass", + "SessionFSSqliteTransactionRequest", + "SessionFSSqliteTransactionResult", + "SessionFSSqliteTransactionStatement", "SessionFSStatRequest", "SessionFSStatResult", "SessionFSWriteFileRequest", "SessionFsHandler", "SessionFsReaddirWithTypesEntryType", + "SessionHistoryCompactRequest", "SessionInstalledPlugin", "SessionInstalledPluginSource", "SessionInstalledPluginSourceGitHub", "SessionInstalledPluginSourceLocal", "SessionInstalledPluginSourceURL", + "SessionLimitPredictionBaselineData", + "SessionLimitPredictionClientType", + "SessionLimitPredictionDetails", + "SessionLimitPredictionPredictRequest", + "SessionLimitPredictionRequest", + "SessionLimitPredictionResult", + "SessionLimitPredictionResultKind", + "SessionLimitPredictionSource", + "SessionLimitPredictionTier", + "SessionLimitPredictionTierOption", + "SessionLimitPredictionUnavailableReason", "SessionList", "SessionListEntry", "SessionListFilter", "SessionLoadDeferredRepoHooksResult", "SessionLogLevel", + "SessionManagedPermissions", + "SessionManagedSettings", "SessionMcpAppsCallToolResult", "SessionMetadataSnapshot", "SessionModelList", + "SessionModelListRequest", "SessionModelPriceCategory", "SessionOpenOptions", "SessionOpenOptionsAdditionalContentExclusionPolicy", @@ -30869,6 +35648,8 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "SessionOpenParams", "SessionOpenParamsKind", "SessionOpenResult", + "SessionPluginsReloadRequest", + "SessionProviderGetEndpointRequest", "SessionPruneResult", "SessionRpc", "SessionSetCredentialsParams", @@ -30896,6 +35677,7 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "SessionsCheckInUseResult", "SessionsCloseRequest", "SessionsCloseResult", + "SessionsDeleteRequest", "SessionsEnrichMetadataRequest", "SessionsFindByPrefixRequest", "SessionsFindByPrefixResult", @@ -30909,8 +35691,12 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "SessionsGetEventFilePathResult", "SessionsGetLastForContextRequest", "SessionsGetLastForContextResult", + "SessionsGetMetadataRequest", + "SessionsGetMetadataResult", "SessionsGetPersistedRemoteSteerableRequest", "SessionsGetPersistedRemoteSteerableResult", + "SessionsListNonEmptySessionIDSRequest", + "SessionsListNonEmptySessionIDSResult", "SessionsListRequest", "SessionsLoadDeferredRepoHooksRequest", "SessionsOpenAttach", @@ -30951,9 +35737,13 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "ShellExecRequest", "ShellExecResult", "ShellExecuteUserRequestedRequest", + "ShellInitProfile", + "ShellInitScript", + "ShellInitScriptShell", "ShellKillRequest", "ShellKillResult", "ShellKillSignal", + "ShellOptions", "ShutdownRequest", "Skill", "SkillDiscoveryPath", @@ -31035,6 +35825,7 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "ToolsInitializeAndValidateResult", "ToolsListRequest", "ToolsUpdateSubagentSettingsResult", + "Trigger", "UIAutoModeSwitchResponse", "UIElicitationArrayAnyOfField", "UIElicitationArrayAnyOfFieldItems", @@ -31106,20 +35897,30 @@ async def handle_git_hub_telemetry_event(params: dict) -> None: "WorkspaceDiffResult", "WorkspaceSummary", "WorkspaceSummaryHostType", + "WorkspacesAddSummaryRequest", + "WorkspacesAddSummaryResult", "WorkspacesApi", + "WorkspacesAutopilotObjectiveExistsResult", "WorkspacesCheckpoints", "WorkspacesCreateFileRequest", + "WorkspacesDeleteAutopilotObjectiveResult", "WorkspacesDiffRequest", + "WorkspacesEnsureRequest", "WorkspacesGetWorkspaceResult", "WorkspacesListCheckpointsResult", "WorkspacesListFilesResult", + "WorkspacesReadAutopilotObjectiveResult", "WorkspacesReadCheckpointRequest", "WorkspacesReadCheckpointResult", "WorkspacesReadFileRequest", "WorkspacesReadFileResult", "WorkspacesSaveLargePasteRequest", "WorkspacesSaveLargePasteResult", + "WorkspacesTruncateSummariesRequest", + "WorkspacesUpdateMetadataRequest", "WorkspacesWorkspaceDetailsHostType", + "WorkspacesWriteAutopilotObjectiveRequest", + "WorkspacesWriteAutopilotObjectiveResult", "rpc_from_dict", "rpc_to_dict", ] diff --git a/python/copilot/generated/session_events.py b/python/copilot/generated/session_events.py index fecd5839e..4c3a53e53 100644 --- a/python/copilot/generated/session_events.py +++ b/python/copilot/generated/session_events.py @@ -148,6 +148,7 @@ class SessionEventType(Enum): SESSION_USAGE_CHECKPOINT = "session.usage_checkpoint" SESSION_CONTEXT_CHANGED = "session.context_changed" SESSION_USAGE_INFO = "session.usage_info" + SESSION_CONTEXT_CLEARED = "session.context_cleared" SESSION_COMPACTION_START = "session.compaction_start" SESSION_COMPACTION_COMPLETE = "session.compaction_complete" SESSION_TASK_COMPLETE = "session.task_complete" @@ -223,6 +224,8 @@ class SessionEventType(Enum): EXIT_PLAN_MODE_COMPLETED = "exit_plan_mode.completed" SESSION_TOOLS_UPDATED = "session.tools_updated" SESSION_BACKGROUND_TASKS_CHANGED = "session.background_tasks_changed" + # Experimental: this event is part of an experimental API and may change or be removed. + FACTORY_RUN_UPDATED = "factory.run_updated" SESSION_SKILLS_LOADED = "session.skills_loaded" SESSION_CUSTOM_AGENTS_UPDATED = "session.custom_agents_updated" SESSION_MCP_SERVERS_LOADED = "session.mcp_servers_loaded" @@ -774,6 +777,30 @@ def to_dict(self) -> dict: return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FactoryRunUpdatedData: + "Ephemeral invalidation signal for a changed factory run." + revision: int + run_id: str + + @staticmethod + def from_dict(obj: Any) -> "FactoryRunUpdatedData": + assert isinstance(obj, dict) + revision = from_int(obj.get("revision")) + run_id = from_str(obj.get("runId")) + return FactoryRunUpdatedData( + revision=revision, + run_id=run_id, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["revision"] = to_int(self.revision) + result["runId"] = from_str(self.run_id) + return result + + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class OmittedBinaryResult: @@ -821,21 +848,31 @@ def to_dict(self) -> dict: class PermissionAutoApproval: "Auto-approval judge information attached to a permission request. Present (non-null) only when the session's allow-all mode is \"auto\"; its absence means auto mode was off and the judge did not evaluate the request. The `recommendation` conveys the judge's disposition for this request." recommendation: AutoApprovalRecommendation + failure_reason: AutoApprovalJudgeFailureReason | None = None + model: str | None = None reason: str | None = None @staticmethod def from_dict(obj: Any) -> "PermissionAutoApproval": assert isinstance(obj, dict) recommendation = parse_enum(AutoApprovalRecommendation, obj.get("recommendation")) + failure_reason = from_union([from_none, lambda x: parse_enum(AutoApprovalJudgeFailureReason, x)], obj.get("failureReason")) + model = from_union([from_none, from_str], obj.get("model")) reason = from_union([from_none, from_str], obj.get("reason")) return PermissionAutoApproval( recommendation=recommendation, + failure_reason=failure_reason, + model=model, reason=reason, ) def to_dict(self) -> dict: result: dict = {} result["recommendation"] = to_enum(AutoApprovalRecommendation, self.recommendation) + if self.failure_reason is not None: + result["failureReason"] = from_union([from_none, lambda x: to_enum(AutoApprovalJudgeFailureReason, x)], self.failure_reason) + if self.model is not None: + result["model"] = from_union([from_none, from_str], self.model) if self.reason is not None: result["reason"] = from_union([from_none, from_str], self.reason) return result @@ -846,43 +883,88 @@ def to_dict(self) -> dict: class SessionAutoModeResolvedData: "Auto Intent resolution: the concrete model the session settled on for the first prompt of an auto-mode session, and why. Lets SDK clients render the chosen model and the full reason it was picked. The core selection fields (chosenModel/reasoningBucket/categoryScores) are stable; the routing-analytics fields (predictedLabel/confidence/candidateModels) mirror the upstream intent service and may evolve, hence the event's experimental stability." chosen_model: str + available_models: list[str] | None = None candidate_models: list[str] | None = None category_scores: dict[str, float] | None = None + chosen_shortfall: float | None = None confidence: float | None = None + end_to_end_latency_ms: float | None = None + fallback: bool | None = None + fallback_reason: str | None = None + has_image: bool | None = None predicted_label: str | None = None reasoning_bucket: AutoModeResolvedReasoningBucket | None = None + router_latency_ms: float | None = None + routing_method: str | None = None + sticky_override: bool | None = None @staticmethod def from_dict(obj: Any) -> "SessionAutoModeResolvedData": assert isinstance(obj, dict) chosen_model = from_str(obj.get("chosenModel")) + available_models = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("availableModels")) candidate_models = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("candidateModels")) category_scores = from_union([from_none, lambda x: from_dict(from_float, x)], obj.get("categoryScores")) + chosen_shortfall = from_union([from_none, from_float], obj.get("chosenShortfall")) confidence = from_union([from_none, from_float], obj.get("confidence")) + end_to_end_latency_ms = from_union([from_none, from_float], obj.get("endToEndLatencyMs")) + fallback = from_union([from_none, from_bool], obj.get("fallback")) + fallback_reason = from_union([from_none, from_str], obj.get("fallbackReason")) + has_image = from_union([from_none, from_bool], obj.get("hasImage")) predicted_label = from_union([from_none, from_str], obj.get("predictedLabel")) reasoning_bucket = from_union([from_none, lambda x: parse_enum(AutoModeResolvedReasoningBucket, x)], obj.get("reasoningBucket")) + router_latency_ms = from_union([from_none, from_float], obj.get("routerLatencyMs")) + routing_method = from_union([from_none, from_str], obj.get("routingMethod")) + sticky_override = from_union([from_none, from_bool], obj.get("stickyOverride")) return SessionAutoModeResolvedData( chosen_model=chosen_model, + available_models=available_models, candidate_models=candidate_models, category_scores=category_scores, + chosen_shortfall=chosen_shortfall, confidence=confidence, + end_to_end_latency_ms=end_to_end_latency_ms, + fallback=fallback, + fallback_reason=fallback_reason, + has_image=has_image, predicted_label=predicted_label, reasoning_bucket=reasoning_bucket, + router_latency_ms=router_latency_ms, + routing_method=routing_method, + sticky_override=sticky_override, ) def to_dict(self) -> dict: result: dict = {} result["chosenModel"] = from_str(self.chosen_model) + if self.available_models is not None: + result["availableModels"] = from_union([from_none, lambda x: from_list(from_str, x)], self.available_models) if self.candidate_models is not None: result["candidateModels"] = from_union([from_none, lambda x: from_list(from_str, x)], self.candidate_models) if self.category_scores is not None: result["categoryScores"] = from_union([from_none, lambda x: from_dict(to_float, x)], self.category_scores) + if self.chosen_shortfall is not None: + result["chosenShortfall"] = from_union([from_none, to_float], self.chosen_shortfall) if self.confidence is not None: result["confidence"] = from_union([from_none, to_float], self.confidence) + if self.end_to_end_latency_ms is not None: + result["endToEndLatencyMs"] = from_union([from_none, to_float], self.end_to_end_latency_ms) + if self.fallback is not None: + result["fallback"] = from_union([from_none, from_bool], self.fallback) + if self.fallback_reason is not None: + result["fallbackReason"] = from_union([from_none, from_str], self.fallback_reason) + if self.has_image is not None: + result["hasImage"] = from_union([from_none, from_bool], self.has_image) if self.predicted_label is not None: result["predictedLabel"] = from_union([from_none, from_str], self.predicted_label) if self.reasoning_bucket is not None: result["reasoningBucket"] = from_union([from_none, lambda x: to_enum(AutoModeResolvedReasoningBucket, x)], self.reasoning_bucket) + if self.router_latency_ms is not None: + result["routerLatencyMs"] = from_union([from_none, to_float], self.router_latency_ms) + if self.routing_method is not None: + result["routingMethod"] = from_union([from_none, from_str], self.routing_method) + if self.sticky_override is not None: + result["stickyOverride"] = from_union([from_none, from_bool], self.sticky_override) return result @@ -1126,13 +1208,15 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionManagedSettingsResolvedData: - "Enterprise managed-settings resolution: the effective managed settings the session applied and where they came from, so SDK clients can show users what is enterprise-managed and by which authority. Fires whenever managed policy is (re)applied — at session start, on resume, and on account switch. This is an ephemeral live snapshot (delivered to subscribers but not persisted to the session event log), because at session start it resolves before `session.start` is emitted; for a session-independent pull, use the SDK `getManagedSettings()` API, which returns the identical payload. Managed settings have a single authoritative source, so the highest-authority present layer (server > device) wins wholesale; `bypassPermissionsDisabled` is deny-wins across layers. Marked experimental while the managed-settings surface stabilizes." + "Enterprise managed-settings resolution: the effective managed settings the session applied and which channels contributed, so SDK clients can show users what is enterprise-managed. Fires whenever managed policy is (re)applied — at session start, on resume, and on account switch. This is an ephemeral live snapshot (delivered to subscribers but not persisted to the session event log), because at session start it resolves before `session.start` is emitted. Device values take precedence over server values per ordinary key, while permissions compose restrictively across device, server, and SDK-client layers. The account-scoped `getManagedSettings()` API does not include session-local client injection. Marked experimental while the managed-settings surface stabilizes." bypass_permissions_disabled: bool device_managed: bool fail_closed: bool managed_keys: list[str] server_managed: bool source: ManagedSettingsResolvedSource + client_managed: bool | None = None + permissions_allow_intersected: bool | None = None settings: Any = None @staticmethod @@ -1144,6 +1228,8 @@ def from_dict(obj: Any) -> "SessionManagedSettingsResolvedData": managed_keys = from_list(from_str, obj.get("managedKeys")) server_managed = from_bool(obj.get("serverManaged")) source = parse_enum(ManagedSettingsResolvedSource, obj.get("source")) + client_managed = from_union([from_none, from_bool], obj.get("clientManaged")) + permissions_allow_intersected = from_union([from_none, from_bool], obj.get("permissionsAllowIntersected")) settings = obj.get("settings") return SessionManagedSettingsResolvedData( bypass_permissions_disabled=bypass_permissions_disabled, @@ -1152,6 +1238,8 @@ def from_dict(obj: Any) -> "SessionManagedSettingsResolvedData": managed_keys=managed_keys, server_managed=server_managed, source=source, + client_managed=client_managed, + permissions_allow_intersected=permissions_allow_intersected, settings=settings, ) @@ -1163,6 +1251,10 @@ def to_dict(self) -> dict: result["managedKeys"] = from_list(from_str, self.managed_keys) result["serverManaged"] = from_bool(self.server_managed) result["source"] = to_enum(ManagedSettingsResolvedSource, self.source) + if self.client_managed is not None: + result["clientManaged"] = from_union([from_none, from_bool], self.client_managed) + if self.permissions_allow_intersected is not None: + result["permissionsAllowIntersected"] = from_union([from_none, from_bool], self.permissions_allow_intersected) if self.settings is not None: result["settings"] = self.settings return result @@ -1232,6 +1324,8 @@ class AssistantMessageData: content: str message_id: str api_call_id: str | None = None + chunk_count: int | None = None + chunk_index: int | None = None # Experimental: this field is part of an experimental API and may change or be removed. citations: Citations | None = None client_request_id: str | None = None @@ -1246,6 +1340,7 @@ class AssistantMessageData: reasoning_text: str | None = None reasoning_wire_field: str | None = None request_id: str | None = None + rte: bool | None = None server_tools: AssistantMessageServerTools | None = None service_request_id: str | None = None tool_requests: list[AssistantMessageToolRequest] | None = None @@ -1257,6 +1352,8 @@ def from_dict(obj: Any) -> "AssistantMessageData": content = from_str(obj.get("content")) message_id = from_str(obj.get("messageId")) api_call_id = from_union([from_none, from_str], obj.get("apiCallId")) + chunk_count = from_union([from_none, from_int], obj.get("chunkCount")) + chunk_index = from_union([from_none, from_int], obj.get("chunkIndex")) citations = from_union([from_none, Citations.from_dict], obj.get("citations")) client_request_id = from_union([from_none, from_str], obj.get("clientRequestId")) encrypted_content = from_union([from_none, from_str], obj.get("encryptedContent")) @@ -1269,6 +1366,7 @@ def from_dict(obj: Any) -> "AssistantMessageData": reasoning_text = from_union([from_none, from_str], obj.get("reasoningText")) reasoning_wire_field = from_union([from_none, from_str], obj.get("reasoningWireField")) request_id = from_union([from_none, from_str], obj.get("requestId")) + rte = from_union([from_none, from_bool], obj.get("rte")) server_tools = from_union([from_none, AssistantMessageServerTools.from_dict], obj.get("serverTools")) service_request_id = from_union([from_none, from_str], obj.get("serviceRequestId")) tool_requests = from_union([from_none, lambda x: from_list(AssistantMessageToolRequest.from_dict, x)], obj.get("toolRequests")) @@ -1277,6 +1375,8 @@ def from_dict(obj: Any) -> "AssistantMessageData": content=content, message_id=message_id, api_call_id=api_call_id, + chunk_count=chunk_count, + chunk_index=chunk_index, citations=citations, client_request_id=client_request_id, encrypted_content=encrypted_content, @@ -1289,6 +1389,7 @@ def from_dict(obj: Any) -> "AssistantMessageData": reasoning_text=reasoning_text, reasoning_wire_field=reasoning_wire_field, request_id=request_id, + rte=rte, server_tools=server_tools, service_request_id=service_request_id, tool_requests=tool_requests, @@ -1301,6 +1402,10 @@ def to_dict(self) -> dict: result["messageId"] = from_str(self.message_id) if self.api_call_id is not None: result["apiCallId"] = from_union([from_none, from_str], self.api_call_id) + if self.chunk_count is not None: + result["chunkCount"] = from_union([from_none, to_int], self.chunk_count) + if self.chunk_index is not None: + result["chunkIndex"] = from_union([from_none, to_int], self.chunk_index) if self.citations is not None: result["citations"] = from_union([from_none, lambda x: to_class(Citations, x)], self.citations) if self.client_request_id is not None: @@ -1325,6 +1430,8 @@ def to_dict(self) -> dict: result["reasoningWireField"] = from_union([from_none, from_str], self.reasoning_wire_field) if self.request_id is not None: result["requestId"] = from_union([from_none, from_str], self.request_id) + if self.rte is not None: + result["rte"] = from_union([from_none, from_bool], self.rte) if self.server_tools is not None: result["serverTools"] = from_union([from_none, lambda x: to_class(AssistantMessageServerTools, x)], self.server_tools) if self.service_request_id is not None: @@ -1447,21 +1554,26 @@ class AssistantReasoningData: "Assistant reasoning content for timeline display with complete thinking text" content: str reasoning_id: str + rte: bool | None = None @staticmethod def from_dict(obj: Any) -> "AssistantReasoningData": assert isinstance(obj, dict) content = from_str(obj.get("content")) reasoning_id = from_str(obj.get("reasoningId")) + rte = from_union([from_none, from_bool], obj.get("rte")) return AssistantReasoningData( content=content, reasoning_id=reasoning_id, + rte=rte, ) def to_dict(self) -> dict: result: dict = {} result["content"] = from_str(self.content) result["reasoningId"] = from_str(self.reasoning_id) + if self.rte is not None: + result["rte"] = from_union([from_none, from_bool], self.rte) return result @@ -1711,6 +1823,8 @@ class AssistantUsageData: model: str api_call_id: str | None = None api_endpoint: AssistantUsageApiEndpoint | None = None + # Internal: this field is an internal SDK API and is not part of the public surface. + _available_tool_count: int | None = None cache_expires_at: datetime | None = None cache_read_tokens: int | None = None cache_write_tokens: int | None = None @@ -1722,7 +1836,10 @@ class AssistantUsageData: finish_reason: str | None = None initiator: str | None = None input_tokens: int | None = None + interaction_type: str | None = None inter_token_latency: timedelta | None = None + # Internal: this field is an internal SDK API and is not part of the public surface. + _num_tool_calls: int | None = None output_tokens: int | None = None # Deprecated: this field is deprecated. parent_tool_call_id: str | None = None @@ -1731,8 +1848,13 @@ class AssistantUsageData: _quota_snapshots: dict[str, _AssistantUsageQuotaSnapshot] | None = None reasoning_effort: str | None = None reasoning_tokens: int | None = None + rte: bool | None = None service_request_id: str | None = None time_to_first_token: timedelta | None = None + # Internal: this field is an internal SDK API and is not part of the public surface. + _tool_counts: dict[str, int] | None = None + # Internal: this field is an internal SDK API and is not part of the public surface. + _tool_token_count: int | None = None @staticmethod def from_dict(obj: Any) -> "AssistantUsageData": @@ -1740,6 +1862,7 @@ def from_dict(obj: Any) -> "AssistantUsageData": model = from_str(obj.get("model")) api_call_id = from_union([from_none, from_str], obj.get("apiCallId")) api_endpoint = from_union([from_none, lambda x: parse_enum(AssistantUsageApiEndpoint, x)], obj.get("apiEndpoint")) + _available_tool_count = from_union([from_none, from_int], obj.get("availableToolCount")) cache_expires_at = from_union([from_none, from_datetime], obj.get("cacheExpiresAt")) cache_read_tokens = from_union([from_none, from_int], obj.get("cacheReadTokens")) cache_write_tokens = from_union([from_none, from_int], obj.get("cacheWriteTokens")) @@ -1750,19 +1873,25 @@ def from_dict(obj: Any) -> "AssistantUsageData": finish_reason = from_union([from_none, from_str], obj.get("finishReason")) initiator = from_union([from_none, from_str], obj.get("initiator")) input_tokens = from_union([from_none, from_int], obj.get("inputTokens")) + interaction_type = from_union([from_none, from_str], obj.get("interactionType")) inter_token_latency = from_union([from_none, from_timedelta], obj.get("interTokenLatencyMs")) + _num_tool_calls = from_union([from_none, from_int], obj.get("numToolCalls")) output_tokens = from_union([from_none, from_int], obj.get("outputTokens")) parent_tool_call_id = from_union([from_none, from_str], obj.get("parentToolCallId")) provider_call_id = from_union([from_none, from_str], obj.get("providerCallId")) _quota_snapshots = from_union([from_none, lambda x: from_dict(_AssistantUsageQuotaSnapshot.from_dict, x)], obj.get("quotaSnapshots")) reasoning_effort = from_union([from_none, from_str], obj.get("reasoningEffort")) reasoning_tokens = from_union([from_none, from_int], obj.get("reasoningTokens")) + rte = from_union([from_none, from_bool], obj.get("rte")) service_request_id = from_union([from_none, from_str], obj.get("serviceRequestId")) time_to_first_token = from_union([from_none, from_timedelta], obj.get("timeToFirstTokenMs")) + _tool_counts = from_union([from_none, lambda x: from_dict(from_int, x)], obj.get("toolCounts")) + _tool_token_count = from_union([from_none, from_int], obj.get("toolTokenCount")) return AssistantUsageData( model=model, api_call_id=api_call_id, api_endpoint=api_endpoint, + _available_tool_count=_available_tool_count, cache_expires_at=cache_expires_at, cache_read_tokens=cache_read_tokens, cache_write_tokens=cache_write_tokens, @@ -1773,15 +1902,20 @@ def from_dict(obj: Any) -> "AssistantUsageData": finish_reason=finish_reason, initiator=initiator, input_tokens=input_tokens, + interaction_type=interaction_type, inter_token_latency=inter_token_latency, + _num_tool_calls=_num_tool_calls, output_tokens=output_tokens, parent_tool_call_id=parent_tool_call_id, provider_call_id=provider_call_id, _quota_snapshots=_quota_snapshots, reasoning_effort=reasoning_effort, reasoning_tokens=reasoning_tokens, + rte=rte, service_request_id=service_request_id, time_to_first_token=time_to_first_token, + _tool_counts=_tool_counts, + _tool_token_count=_tool_token_count, ) def to_dict(self) -> dict: @@ -1791,6 +1925,8 @@ def to_dict(self) -> dict: result["apiCallId"] = from_union([from_none, from_str], self.api_call_id) if self.api_endpoint is not None: result["apiEndpoint"] = from_union([from_none, lambda x: to_enum(AssistantUsageApiEndpoint, x)], self.api_endpoint) + if self._available_tool_count is not None: + result["availableToolCount"] = from_union([from_none, to_int], self._available_tool_count) if self.cache_expires_at is not None: result["cacheExpiresAt"] = from_union([from_none, to_datetime], self.cache_expires_at) if self.cache_read_tokens is not None: @@ -1811,8 +1947,12 @@ def to_dict(self) -> dict: result["initiator"] = from_union([from_none, from_str], self.initiator) if self.input_tokens is not None: result["inputTokens"] = from_union([from_none, to_int], self.input_tokens) + if self.interaction_type is not None: + result["interactionType"] = from_union([from_none, from_str], self.interaction_type) if self.inter_token_latency is not None: result["interTokenLatencyMs"] = from_union([from_none, to_timedelta], self.inter_token_latency) + if self._num_tool_calls is not None: + result["numToolCalls"] = from_union([from_none, to_int], self._num_tool_calls) if self.output_tokens is not None: result["outputTokens"] = from_union([from_none, to_int], self.output_tokens) if self.parent_tool_call_id is not None: @@ -1825,10 +1965,16 @@ def to_dict(self) -> dict: result["reasoningEffort"] = from_union([from_none, from_str], self.reasoning_effort) if self.reasoning_tokens is not None: result["reasoningTokens"] = from_union([from_none, to_int], self.reasoning_tokens) + if self.rte is not None: + result["rte"] = from_union([from_none, from_bool], self.rte) if self.service_request_id is not None: result["serviceRequestId"] = from_union([from_none, from_str], self.service_request_id) if self.time_to_first_token is not None: result["timeToFirstTokenMs"] = from_union([from_none, to_timedelta], self.time_to_first_token) + if self._tool_counts is not None: + result["toolCounts"] = from_union([from_none, lambda x: from_dict(to_int, x)], self._tool_counts) + if self._tool_token_count is not None: + result["toolTokenCount"] = from_union([from_none, to_int], self._tool_token_count) return result @@ -3296,6 +3442,65 @@ def to_dict(self) -> dict: return result +@dataclass +class FactoryPermissionPhase: + "A declared phase shown in a factory permission prompt." + title: str + detail: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "FactoryPermissionPhase": + assert isinstance(obj, dict) + title = from_str(obj.get("title")) + detail = from_union([from_none, from_str], obj.get("detail")) + return FactoryPermissionPhase( + title=title, + detail=detail, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["title"] = from_str(self.title) + if self.detail is not None: + result["detail"] = from_union([from_none, from_str], self.detail) + return result + + +@dataclass +class GitHubMcpToolConfig: + "Per-session configuration for the built-in GitHub MCP server" + additional_tools: list[str] | None = None + additional_toolsets: list[str] | None = None + enable_all_tools: bool | None = None + enable_insiders_mode: bool | None = None + + @staticmethod + def from_dict(obj: Any) -> "GitHubMcpToolConfig": + assert isinstance(obj, dict) + additional_tools = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("additionalTools")) + additional_toolsets = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("additionalToolsets")) + enable_all_tools = from_union([from_none, from_bool], obj.get("enableAllTools")) + enable_insiders_mode = from_union([from_none, from_bool], obj.get("enableInsidersMode")) + return GitHubMcpToolConfig( + additional_tools=additional_tools, + additional_toolsets=additional_toolsets, + enable_all_tools=enable_all_tools, + enable_insiders_mode=enable_insiders_mode, + ) + + def to_dict(self) -> dict: + result: dict = {} + if self.additional_tools is not None: + result["additionalTools"] = from_union([from_none, lambda x: from_list(from_str, x)], self.additional_tools) + if self.additional_toolsets is not None: + result["additionalToolsets"] = from_union([from_none, lambda x: from_list(from_str, x)], self.additional_toolsets) + if self.enable_all_tools is not None: + result["enableAllTools"] = from_union([from_none, from_bool], self.enable_all_tools) + if self.enable_insiders_mode is not None: + result["enableInsidersMode"] = from_union([from_none, from_bool], self.enable_insiders_mode) + return result + + @dataclass class GitHubRepoRef: "Pointer to a GitHub repository." @@ -3956,6 +4161,7 @@ class ModelCallFailureData: _quota_snapshots: dict[str, _AssistantUsageQuotaSnapshot] | None = None reasoning_effort: str | None = None request_fingerprint: ModelCallFailureRequestFingerprint | None = None + rte: bool | None = None service_request_id: str | None = None status_code: int | None = None transport: ModelCallFailureTransport | None = None @@ -3982,6 +4188,7 @@ def from_dict(obj: Any) -> "ModelCallFailureData": _quota_snapshots = from_union([from_none, lambda x: from_dict(_AssistantUsageQuotaSnapshot.from_dict, x)], obj.get("quotaSnapshots")) reasoning_effort = from_union([from_none, from_str], obj.get("reasoningEffort")) request_fingerprint = from_union([from_none, ModelCallFailureRequestFingerprint.from_dict], obj.get("requestFingerprint")) + rte = from_union([from_none, from_bool], obj.get("rte")) service_request_id = from_union([from_none, from_str], obj.get("serviceRequestId")) status_code = from_union([from_none, from_int], obj.get("statusCode")) transport = from_union([from_none, lambda x: parse_enum(ModelCallFailureTransport, x)], obj.get("transport")) @@ -4005,6 +4212,7 @@ def from_dict(obj: Any) -> "ModelCallFailureData": _quota_snapshots=_quota_snapshots, reasoning_effort=reasoning_effort, request_fingerprint=request_fingerprint, + rte=rte, service_request_id=service_request_id, status_code=status_code, transport=transport, @@ -4049,6 +4257,8 @@ def to_dict(self) -> dict: result["reasoningEffort"] = from_union([from_none, from_str], self.reasoning_effort) if self.request_fingerprint is not None: result["requestFingerprint"] = from_union([from_none, lambda x: to_class(ModelCallFailureRequestFingerprint, x)], self.request_fingerprint) + if self.rte is not None: + result["rte"] = from_union([from_none, from_bool], self.rte) if self.service_request_id is not None: result["serviceRequestId"] = from_union([from_none, from_str], self.service_request_id) if self.status_code is not None: @@ -4107,15 +4317,19 @@ class ModelCallStartData: "Model API dispatch metadata for internal telemetry" turn_id: str model: str | None = None + # Internal: this field is an internal SDK API and is not part of the public surface. + _previous_response_id: str | None = None @staticmethod def from_dict(obj: Any) -> "ModelCallStartData": assert isinstance(obj, dict) turn_id = from_str(obj.get("turnId")) model = from_union([from_none, from_str], obj.get("model")) + _previous_response_id = from_union([from_none, from_str], obj.get("previousResponseId")) return ModelCallStartData( turn_id=turn_id, model=model, + _previous_response_id=_previous_response_id, ) def to_dict(self) -> dict: @@ -4123,6 +4337,8 @@ def to_dict(self) -> dict: result["turnId"] = from_str(self.turn_id) if self.model is not None: result["model"] = from_union([from_none, from_str], self.model) + if self._previous_response_id is not None: + result["previousResponseId"] = from_union([from_none, from_str], self._previous_response_id) return result @@ -4378,6 +4594,7 @@ class PermissionPromptRequestCommands: kind: ClassVar[str] = "commands" # Experimental: this field is part of an experimental API and may change or be removed. auto_approval: PermissionAutoApproval | None = None + managed_approval_required: bool | None = None tool_call_id: str | None = None warning: str | None = None @@ -4389,6 +4606,7 @@ def from_dict(obj: Any) -> "PermissionPromptRequestCommands": full_command_text = from_str(obj.get("fullCommandText")) intention = from_str(obj.get("intention")) auto_approval = from_union([from_none, PermissionAutoApproval.from_dict], obj.get("autoApproval")) + managed_approval_required = from_union([from_none, from_bool], obj.get("managedApprovalRequired")) tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) warning = from_union([from_none, from_str], obj.get("warning")) return PermissionPromptRequestCommands( @@ -4397,6 +4615,7 @@ def from_dict(obj: Any) -> "PermissionPromptRequestCommands": full_command_text=full_command_text, intention=intention, auto_approval=auto_approval, + managed_approval_required=managed_approval_required, tool_call_id=tool_call_id, warning=warning, ) @@ -4410,6 +4629,8 @@ def to_dict(self) -> dict: result["kind"] = self.kind if self.auto_approval is not None: result["autoApproval"] = from_union([from_none, lambda x: to_class(PermissionAutoApproval, x)], self.auto_approval) + if self.managed_approval_required is not None: + result["managedApprovalRequired"] = from_union([from_none, from_bool], self.managed_approval_required) if self.tool_call_id is not None: result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) if self.warning is not None: @@ -4531,6 +4752,103 @@ def to_dict(self) -> dict: return result +@dataclass +class PermissionPromptRequestFactory: + "Factory run or authoring permission prompt" + approval_key: str + can_persist_approval: bool + description: str + kind: ClassVar[str] = "factory" + name: str + operation: FactoryPermissionOperation + phases: list[FactoryPermissionPhase] + # Experimental: this field is part of an experimental API and may change or be removed. + auto_approval: PermissionAutoApproval | None = None + declared_max_ai_credits: float | None = None + declared_max_concurrent_subagents: int | None = None + declared_max_total_subagents: int | None = None + declared_timeout_seconds: float | None = None + managed_approval_required: bool | None = None + max_ai_credits: float | None = None + max_concurrent_subagents: int | None = None + max_total_subagents: int | None = None + timeout_seconds: float | None = None + tool_call_id: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "PermissionPromptRequestFactory": + assert isinstance(obj, dict) + approval_key = from_str(obj.get("approvalKey")) + can_persist_approval = from_bool(obj.get("canPersistApproval")) + description = from_str(obj.get("description")) + name = from_str(obj.get("name")) + operation = parse_enum(FactoryPermissionOperation, obj.get("operation")) + phases = from_list(FactoryPermissionPhase.from_dict, obj.get("phases")) + auto_approval = from_union([from_none, PermissionAutoApproval.from_dict], obj.get("autoApproval")) + declared_max_ai_credits = from_union([from_none, from_float], obj.get("declaredMaxAiCredits")) + declared_max_concurrent_subagents = from_union([from_none, from_int], obj.get("declaredMaxConcurrentSubagents")) + declared_max_total_subagents = from_union([from_none, from_int], obj.get("declaredMaxTotalSubagents")) + declared_timeout_seconds = from_union([from_none, from_float], obj.get("declaredTimeoutSeconds")) + managed_approval_required = from_union([from_none, from_bool], obj.get("managedApprovalRequired")) + max_ai_credits = from_union([from_none, from_float], obj.get("maxAiCredits")) + max_concurrent_subagents = from_union([from_none, from_int], obj.get("maxConcurrentSubagents")) + max_total_subagents = from_union([from_none, from_int], obj.get("maxTotalSubagents")) + timeout_seconds = from_union([from_none, from_float], obj.get("timeoutSeconds")) + tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) + return PermissionPromptRequestFactory( + approval_key=approval_key, + can_persist_approval=can_persist_approval, + description=description, + name=name, + operation=operation, + phases=phases, + auto_approval=auto_approval, + declared_max_ai_credits=declared_max_ai_credits, + declared_max_concurrent_subagents=declared_max_concurrent_subagents, + declared_max_total_subagents=declared_max_total_subagents, + declared_timeout_seconds=declared_timeout_seconds, + managed_approval_required=managed_approval_required, + max_ai_credits=max_ai_credits, + max_concurrent_subagents=max_concurrent_subagents, + max_total_subagents=max_total_subagents, + timeout_seconds=timeout_seconds, + tool_call_id=tool_call_id, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["approvalKey"] = from_str(self.approval_key) + result["canPersistApproval"] = from_bool(self.can_persist_approval) + result["description"] = from_str(self.description) + result["kind"] = self.kind + result["name"] = from_str(self.name) + result["operation"] = to_enum(FactoryPermissionOperation, self.operation) + result["phases"] = from_list(lambda x: to_class(FactoryPermissionPhase, x), self.phases) + if self.auto_approval is not None: + result["autoApproval"] = from_union([from_none, lambda x: to_class(PermissionAutoApproval, x)], self.auto_approval) + if self.declared_max_ai_credits is not None: + result["declaredMaxAiCredits"] = from_union([from_none, to_float], self.declared_max_ai_credits) + if self.declared_max_concurrent_subagents is not None: + result["declaredMaxConcurrentSubagents"] = from_union([from_none, to_int], self.declared_max_concurrent_subagents) + if self.declared_max_total_subagents is not None: + result["declaredMaxTotalSubagents"] = from_union([from_none, to_int], self.declared_max_total_subagents) + if self.declared_timeout_seconds is not None: + result["declaredTimeoutSeconds"] = from_union([from_none, to_float], self.declared_timeout_seconds) + if self.managed_approval_required is not None: + result["managedApprovalRequired"] = from_union([from_none, from_bool], self.managed_approval_required) + if self.max_ai_credits is not None: + result["maxAiCredits"] = from_union([from_none, to_float], self.max_ai_credits) + if self.max_concurrent_subagents is not None: + result["maxConcurrentSubagents"] = from_union([from_none, to_int], self.max_concurrent_subagents) + if self.max_total_subagents is not None: + result["maxTotalSubagents"] = from_union([from_none, to_int], self.max_total_subagents) + if self.timeout_seconds is not None: + result["timeoutSeconds"] = from_union([from_none, to_float], self.timeout_seconds) + if self.tool_call_id is not None: + result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) + return result + + @dataclass class PermissionPromptRequestHook: "Hook confirmation permission prompt" @@ -4719,6 +5037,7 @@ class PermissionPromptRequestRead: path: str # Experimental: this field is part of an experimental API and may change or be removed. auto_approval: PermissionAutoApproval | None = None + managed_approval_required: bool | None = None tool_call_id: str | None = None @staticmethod @@ -4727,11 +5046,13 @@ def from_dict(obj: Any) -> "PermissionPromptRequestRead": intention = from_str(obj.get("intention")) path = from_str(obj.get("path")) auto_approval = from_union([from_none, PermissionAutoApproval.from_dict], obj.get("autoApproval")) + managed_approval_required = from_union([from_none, from_bool], obj.get("managedApprovalRequired")) tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) return PermissionPromptRequestRead( intention=intention, path=path, auto_approval=auto_approval, + managed_approval_required=managed_approval_required, tool_call_id=tool_call_id, ) @@ -4742,6 +5063,8 @@ def to_dict(self) -> dict: result["path"] = from_str(self.path) if self.auto_approval is not None: result["autoApproval"] = from_union([from_none, lambda x: to_class(PermissionAutoApproval, x)], self.auto_approval) + if self.managed_approval_required is not None: + result["managedApprovalRequired"] = from_union([from_none, from_bool], self.managed_approval_required) if self.tool_call_id is not None: result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) return result @@ -4755,6 +5078,8 @@ class PermissionPromptRequestUrl: url: str # Experimental: this field is part of an experimental API and may change or be removed. auto_approval: PermissionAutoApproval | None = None + managed_approval_required: bool | None = None + redirected_from: str | None = None request_sandbox_bypass: bool | None = None request_sandbox_bypass_reason: str | None = None tool_call_id: str | None = None @@ -4765,6 +5090,8 @@ def from_dict(obj: Any) -> "PermissionPromptRequestUrl": intention = from_str(obj.get("intention")) url = from_str(obj.get("url")) auto_approval = from_union([from_none, PermissionAutoApproval.from_dict], obj.get("autoApproval")) + managed_approval_required = from_union([from_none, from_bool], obj.get("managedApprovalRequired")) + redirected_from = from_union([from_none, from_str], obj.get("redirectedFrom")) request_sandbox_bypass = from_union([from_none, from_bool], obj.get("requestSandboxBypass")) request_sandbox_bypass_reason = from_union([from_none, from_str], obj.get("requestSandboxBypassReason")) tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) @@ -4772,6 +5099,8 @@ def from_dict(obj: Any) -> "PermissionPromptRequestUrl": intention=intention, url=url, auto_approval=auto_approval, + managed_approval_required=managed_approval_required, + redirected_from=redirected_from, request_sandbox_bypass=request_sandbox_bypass, request_sandbox_bypass_reason=request_sandbox_bypass_reason, tool_call_id=tool_call_id, @@ -4784,6 +5113,10 @@ def to_dict(self) -> dict: result["url"] = from_str(self.url) if self.auto_approval is not None: result["autoApproval"] = from_union([from_none, lambda x: to_class(PermissionAutoApproval, x)], self.auto_approval) + if self.managed_approval_required is not None: + result["managedApprovalRequired"] = from_union([from_none, from_bool], self.managed_approval_required) + if self.redirected_from is not None: + result["redirectedFrom"] = from_union([from_none, from_str], self.redirected_from) if self.request_sandbox_bypass is not None: result["requestSandboxBypass"] = from_union([from_none, from_bool], self.request_sandbox_bypass) if self.request_sandbox_bypass_reason is not None: @@ -4803,6 +5136,7 @@ class PermissionPromptRequestWrite: kind: ClassVar[str] = "write" # Experimental: this field is part of an experimental API and may change or be removed. auto_approval: PermissionAutoApproval | None = None + managed_approval_required: bool | None = None new_file_contents: str | None = None tool_call_id: str | None = None @@ -4814,6 +5148,7 @@ def from_dict(obj: Any) -> "PermissionPromptRequestWrite": file_name = from_str(obj.get("fileName")) intention = from_str(obj.get("intention")) auto_approval = from_union([from_none, PermissionAutoApproval.from_dict], obj.get("autoApproval")) + managed_approval_required = from_union([from_none, from_bool], obj.get("managedApprovalRequired")) new_file_contents = from_union([from_none, from_str], obj.get("newFileContents")) tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) return PermissionPromptRequestWrite( @@ -4822,6 +5157,7 @@ def from_dict(obj: Any) -> "PermissionPromptRequestWrite": file_name=file_name, intention=intention, auto_approval=auto_approval, + managed_approval_required=managed_approval_required, new_file_contents=new_file_contents, tool_call_id=tool_call_id, ) @@ -4835,6 +5171,8 @@ def to_dict(self) -> dict: result["kind"] = self.kind if self.auto_approval is not None: result["autoApproval"] = from_union([from_none, lambda x: to_class(PermissionAutoApproval, x)], self.auto_approval) + if self.managed_approval_required is not None: + result["managedApprovalRequired"] = from_union([from_none, from_bool], self.managed_approval_required) if self.new_file_contents is not None: result["newFileContents"] = from_union([from_none, from_str], self.new_file_contents) if self.tool_call_id is not None: @@ -4850,6 +5188,7 @@ class PermissionRequestCustomTool: tool_name: str args: Any = None tool_call_id: str | None = None + managed_approval_required: bool | None = None @staticmethod def from_dict(obj: Any) -> "PermissionRequestCustomTool": @@ -4858,11 +5197,13 @@ def from_dict(obj: Any) -> "PermissionRequestCustomTool": tool_name = from_str(obj.get("toolName")) args = obj.get("args") tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) + managed_approval_required = from_union([from_none, from_bool], obj.get("managedApprovalRequired")) return PermissionRequestCustomTool( tool_description=tool_description, tool_name=tool_name, args=args, tool_call_id=tool_call_id, + managed_approval_required=managed_approval_required, ) def to_dict(self) -> dict: @@ -4874,6 +5215,8 @@ def to_dict(self) -> dict: result["args"] = self.args if self.tool_call_id is not None: result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) + if self.managed_approval_required is not None: + result["managedApprovalRequired"] = from_union([from_none, from_bool], self.managed_approval_required) return result @@ -4884,6 +5227,7 @@ class PermissionRequestExtensionManagement: operation: str extension_name: str | None = None tool_call_id: str | None = None + managed_approval_required: bool | None = None @staticmethod def from_dict(obj: Any) -> "PermissionRequestExtensionManagement": @@ -4891,10 +5235,12 @@ def from_dict(obj: Any) -> "PermissionRequestExtensionManagement": operation = from_str(obj.get("operation")) extension_name = from_union([from_none, from_str], obj.get("extensionName")) tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) + managed_approval_required = from_union([from_none, from_bool], obj.get("managedApprovalRequired")) return PermissionRequestExtensionManagement( operation=operation, extension_name=extension_name, tool_call_id=tool_call_id, + managed_approval_required=managed_approval_required, ) def to_dict(self) -> dict: @@ -4905,6 +5251,8 @@ def to_dict(self) -> dict: result["extensionName"] = from_union([from_none, from_str], self.extension_name) if self.tool_call_id is not None: result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) + if self.managed_approval_required is not None: + result["managedApprovalRequired"] = from_union([from_none, from_bool], self.managed_approval_required) return result @@ -4915,6 +5263,7 @@ class PermissionRequestExtensionPermissionAccess: extension_name: str kind: ClassVar[str] = "extension-permission-access" tool_call_id: str | None = None + managed_approval_required: bool | None = None @staticmethod def from_dict(obj: Any) -> "PermissionRequestExtensionPermissionAccess": @@ -4922,10 +5271,12 @@ def from_dict(obj: Any) -> "PermissionRequestExtensionPermissionAccess": capabilities = from_list(from_str, obj.get("capabilities")) extension_name = from_str(obj.get("extensionName")) tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) + managed_approval_required = from_union([from_none, from_bool], obj.get("managedApprovalRequired")) return PermissionRequestExtensionPermissionAccess( capabilities=capabilities, extension_name=extension_name, tool_call_id=tool_call_id, + managed_approval_required=managed_approval_required, ) def to_dict(self) -> dict: @@ -4935,6 +5286,99 @@ def to_dict(self) -> dict: result["kind"] = self.kind if self.tool_call_id is not None: result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) + if self.managed_approval_required is not None: + result["managedApprovalRequired"] = from_union([from_none, from_bool], self.managed_approval_required) + return result + + +@dataclass +class PermissionRequestFactory: + "Factory run or authoring permission request" + approval_key: str + can_persist_approval: bool + description: str + kind: ClassVar[str] = "factory" + name: str + operation: FactoryPermissionOperation + phases: list[FactoryPermissionPhase] + declared_max_ai_credits: float | None = None + declared_max_concurrent_subagents: int | None = None + declared_max_total_subagents: int | None = None + declared_timeout_seconds: float | None = None + max_ai_credits: float | None = None + max_concurrent_subagents: int | None = None + max_total_subagents: int | None = None + timeout_seconds: float | None = None + tool_call_id: str | None = None + managed_approval_required: bool | None = None + + @staticmethod + def from_dict(obj: Any) -> "PermissionRequestFactory": + assert isinstance(obj, dict) + approval_key = from_str(obj.get("approvalKey")) + can_persist_approval = from_bool(obj.get("canPersistApproval")) + description = from_str(obj.get("description")) + name = from_str(obj.get("name")) + operation = parse_enum(FactoryPermissionOperation, obj.get("operation")) + phases = from_list(FactoryPermissionPhase.from_dict, obj.get("phases")) + declared_max_ai_credits = from_union([from_none, from_float], obj.get("declaredMaxAiCredits")) + declared_max_concurrent_subagents = from_union([from_none, from_int], obj.get("declaredMaxConcurrentSubagents")) + declared_max_total_subagents = from_union([from_none, from_int], obj.get("declaredMaxTotalSubagents")) + declared_timeout_seconds = from_union([from_none, from_float], obj.get("declaredTimeoutSeconds")) + max_ai_credits = from_union([from_none, from_float], obj.get("maxAiCredits")) + max_concurrent_subagents = from_union([from_none, from_int], obj.get("maxConcurrentSubagents")) + max_total_subagents = from_union([from_none, from_int], obj.get("maxTotalSubagents")) + timeout_seconds = from_union([from_none, from_float], obj.get("timeoutSeconds")) + tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) + managed_approval_required = from_union([from_none, from_bool], obj.get("managedApprovalRequired")) + return PermissionRequestFactory( + approval_key=approval_key, + can_persist_approval=can_persist_approval, + description=description, + name=name, + operation=operation, + phases=phases, + declared_max_ai_credits=declared_max_ai_credits, + declared_max_concurrent_subagents=declared_max_concurrent_subagents, + declared_max_total_subagents=declared_max_total_subagents, + declared_timeout_seconds=declared_timeout_seconds, + max_ai_credits=max_ai_credits, + max_concurrent_subagents=max_concurrent_subagents, + max_total_subagents=max_total_subagents, + timeout_seconds=timeout_seconds, + tool_call_id=tool_call_id, + managed_approval_required=managed_approval_required, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["approvalKey"] = from_str(self.approval_key) + result["canPersistApproval"] = from_bool(self.can_persist_approval) + result["description"] = from_str(self.description) + result["kind"] = self.kind + result["name"] = from_str(self.name) + result["operation"] = to_enum(FactoryPermissionOperation, self.operation) + result["phases"] = from_list(lambda x: to_class(FactoryPermissionPhase, x), self.phases) + if self.declared_max_ai_credits is not None: + result["declaredMaxAiCredits"] = from_union([from_none, to_float], self.declared_max_ai_credits) + if self.declared_max_concurrent_subagents is not None: + result["declaredMaxConcurrentSubagents"] = from_union([from_none, to_int], self.declared_max_concurrent_subagents) + if self.declared_max_total_subagents is not None: + result["declaredMaxTotalSubagents"] = from_union([from_none, to_int], self.declared_max_total_subagents) + if self.declared_timeout_seconds is not None: + result["declaredTimeoutSeconds"] = from_union([from_none, to_float], self.declared_timeout_seconds) + if self.max_ai_credits is not None: + result["maxAiCredits"] = from_union([from_none, to_float], self.max_ai_credits) + if self.max_concurrent_subagents is not None: + result["maxConcurrentSubagents"] = from_union([from_none, to_int], self.max_concurrent_subagents) + if self.max_total_subagents is not None: + result["maxTotalSubagents"] = from_union([from_none, to_int], self.max_total_subagents) + if self.timeout_seconds is not None: + result["timeoutSeconds"] = from_union([from_none, to_float], self.timeout_seconds) + if self.tool_call_id is not None: + result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) + if self.managed_approval_required is not None: + result["managedApprovalRequired"] = from_union([from_none, from_bool], self.managed_approval_required) return result @@ -4946,6 +5390,7 @@ class PermissionRequestHook: hook_message: str | None = None tool_args: Any = None tool_call_id: str | None = None + managed_approval_required: bool | None = None @staticmethod def from_dict(obj: Any) -> "PermissionRequestHook": @@ -4954,11 +5399,13 @@ def from_dict(obj: Any) -> "PermissionRequestHook": hook_message = from_union([from_none, from_str], obj.get("hookMessage")) tool_args = obj.get("toolArgs") tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) + managed_approval_required = from_union([from_none, from_bool], obj.get("managedApprovalRequired")) return PermissionRequestHook( tool_name=tool_name, hook_message=hook_message, tool_args=tool_args, tool_call_id=tool_call_id, + managed_approval_required=managed_approval_required, ) def to_dict(self) -> dict: @@ -4971,6 +5418,8 @@ def to_dict(self) -> dict: result["toolArgs"] = self.tool_args if self.tool_call_id is not None: result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) + if self.managed_approval_required is not None: + result["managedApprovalRequired"] = from_union([from_none, from_bool], self.managed_approval_required) return result @@ -4984,6 +5433,7 @@ class PermissionRequestMcp: tool_title: str args: Any = None tool_call_id: str | None = None + managed_approval_required: bool | None = None @staticmethod def from_dict(obj: Any) -> "PermissionRequestMcp": @@ -4994,6 +5444,7 @@ def from_dict(obj: Any) -> "PermissionRequestMcp": tool_title = from_str(obj.get("toolTitle")) args = obj.get("args") tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) + managed_approval_required = from_union([from_none, from_bool], obj.get("managedApprovalRequired")) return PermissionRequestMcp( read_only=read_only, server_name=server_name, @@ -5001,6 +5452,7 @@ def from_dict(obj: Any) -> "PermissionRequestMcp": tool_title=tool_title, args=args, tool_call_id=tool_call_id, + managed_approval_required=managed_approval_required, ) def to_dict(self) -> dict: @@ -5014,6 +5466,8 @@ def to_dict(self) -> dict: result["args"] = self.args if self.tool_call_id is not None: result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) + if self.managed_approval_required is not None: + result["managedApprovalRequired"] = from_union([from_none, from_bool], self.managed_approval_required) return result @@ -5028,6 +5482,7 @@ class PermissionRequestMemory: reason: str | None = None subject: str | None = None tool_call_id: str | None = None + managed_approval_required: bool | None = None @staticmethod def from_dict(obj: Any) -> "PermissionRequestMemory": @@ -5039,6 +5494,7 @@ def from_dict(obj: Any) -> "PermissionRequestMemory": reason = from_union([from_none, from_str], obj.get("reason")) subject = from_union([from_none, from_str], obj.get("subject")) tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) + managed_approval_required = from_union([from_none, from_bool], obj.get("managedApprovalRequired")) return PermissionRequestMemory( fact=fact, action=action, @@ -5047,6 +5503,7 @@ def from_dict(obj: Any) -> "PermissionRequestMemory": reason=reason, subject=subject, tool_call_id=tool_call_id, + managed_approval_required=managed_approval_required, ) def to_dict(self) -> dict: @@ -5065,6 +5522,8 @@ def to_dict(self) -> dict: result["subject"] = from_union([from_none, from_str], self.subject) if self.tool_call_id is not None: result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) + if self.managed_approval_required is not None: + result["managedApprovalRequired"] = from_union([from_none, from_bool], self.managed_approval_required) return result @@ -5074,6 +5533,7 @@ class PermissionRequestRead: intention: str kind: ClassVar[str] = "read" path: str + managed_approval_required: bool | None = None request_sandbox_bypass: bool | None = None request_sandbox_bypass_reason: str | None = None tool_call_id: str | None = None @@ -5083,12 +5543,14 @@ def from_dict(obj: Any) -> "PermissionRequestRead": assert isinstance(obj, dict) intention = from_str(obj.get("intention")) path = from_str(obj.get("path")) + managed_approval_required = from_union([from_none, from_bool], obj.get("managedApprovalRequired")) request_sandbox_bypass = from_union([from_none, from_bool], obj.get("requestSandboxBypass")) request_sandbox_bypass_reason = from_union([from_none, from_str], obj.get("requestSandboxBypassReason")) tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) return PermissionRequestRead( intention=intention, path=path, + managed_approval_required=managed_approval_required, request_sandbox_bypass=request_sandbox_bypass, request_sandbox_bypass_reason=request_sandbox_bypass_reason, tool_call_id=tool_call_id, @@ -5099,6 +5561,8 @@ def to_dict(self) -> dict: result["intention"] = from_str(self.intention) result["kind"] = self.kind result["path"] = from_str(self.path) + if self.managed_approval_required is not None: + result["managedApprovalRequired"] = from_union([from_none, from_bool], self.managed_approval_required) if self.request_sandbox_bypass is not None: result["requestSandboxBypass"] = from_union([from_none, from_bool], self.request_sandbox_bypass) if self.request_sandbox_bypass_reason is not None: @@ -5119,6 +5583,8 @@ class PermissionRequestShell: kind: ClassVar[str] = "shell" possible_paths: list[str] possible_urls: list[PermissionRequestShellPossibleUrl] + command_segments: list[PermissionRequestShellCommandSegment] | None = None + managed_approval_required: bool | None = None request_sandbox_bypass: bool | None = None request_sandbox_bypass_reason: str | None = None tool_call_id: str | None = None @@ -5134,6 +5600,8 @@ def from_dict(obj: Any) -> "PermissionRequestShell": intention = from_str(obj.get("intention")) possible_paths = from_list(from_str, obj.get("possiblePaths")) possible_urls = from_list(PermissionRequestShellPossibleUrl.from_dict, obj.get("possibleUrls")) + command_segments = from_union([from_none, lambda x: from_list(PermissionRequestShellCommandSegment.from_dict, x)], obj.get("commandSegments")) + managed_approval_required = from_union([from_none, from_bool], obj.get("managedApprovalRequired")) request_sandbox_bypass = from_union([from_none, from_bool], obj.get("requestSandboxBypass")) request_sandbox_bypass_reason = from_union([from_none, from_str], obj.get("requestSandboxBypassReason")) tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) @@ -5146,6 +5614,8 @@ def from_dict(obj: Any) -> "PermissionRequestShell": intention=intention, possible_paths=possible_paths, possible_urls=possible_urls, + command_segments=command_segments, + managed_approval_required=managed_approval_required, request_sandbox_bypass=request_sandbox_bypass, request_sandbox_bypass_reason=request_sandbox_bypass_reason, tool_call_id=tool_call_id, @@ -5162,6 +5632,10 @@ def to_dict(self) -> dict: result["kind"] = self.kind result["possiblePaths"] = from_list(from_str, self.possible_paths) result["possibleUrls"] = from_list(lambda x: to_class(PermissionRequestShellPossibleUrl, x), self.possible_urls) + if self.command_segments is not None: + result["commandSegments"] = from_union([from_none, lambda x: from_list(lambda x: to_class(PermissionRequestShellCommandSegment, x), x)], self.command_segments) + if self.managed_approval_required is not None: + result["managedApprovalRequired"] = from_union([from_none, from_bool], self.managed_approval_required) if self.request_sandbox_bypass is not None: result["requestSandboxBypass"] = from_union([from_none, from_bool], self.request_sandbox_bypass) if self.request_sandbox_bypass_reason is not None: @@ -5196,6 +5670,29 @@ def to_dict(self) -> dict: return result +@dataclass +class PermissionRequestShellCommandSegment: + "A parsed shell command segment used for argument-aware managed policy matching." + full_command_text: str + identifier: str + + @staticmethod + def from_dict(obj: Any) -> "PermissionRequestShellCommandSegment": + assert isinstance(obj, dict) + full_command_text = from_str(obj.get("fullCommandText")) + identifier = from_str(obj.get("identifier")) + return PermissionRequestShellCommandSegment( + full_command_text=full_command_text, + identifier=identifier, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["fullCommandText"] = from_str(self.full_command_text) + result["identifier"] = from_str(self.identifier) + return result + + @dataclass class PermissionRequestShellPossibleUrl: "A URL that may be accessed by a command in a shell permission request." @@ -5221,6 +5718,8 @@ class PermissionRequestUrl: intention: str kind: ClassVar[str] = "url" url: str + managed_approval_required: bool | None = None + redirected_from: str | None = None request_sandbox_bypass: bool | None = None request_sandbox_bypass_reason: str | None = None tool_call_id: str | None = None @@ -5230,12 +5729,16 @@ def from_dict(obj: Any) -> "PermissionRequestUrl": assert isinstance(obj, dict) intention = from_str(obj.get("intention")) url = from_str(obj.get("url")) + managed_approval_required = from_union([from_none, from_bool], obj.get("managedApprovalRequired")) + redirected_from = from_union([from_none, from_str], obj.get("redirectedFrom")) request_sandbox_bypass = from_union([from_none, from_bool], obj.get("requestSandboxBypass")) request_sandbox_bypass_reason = from_union([from_none, from_str], obj.get("requestSandboxBypassReason")) tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) return PermissionRequestUrl( intention=intention, url=url, + managed_approval_required=managed_approval_required, + redirected_from=redirected_from, request_sandbox_bypass=request_sandbox_bypass, request_sandbox_bypass_reason=request_sandbox_bypass_reason, tool_call_id=tool_call_id, @@ -5246,6 +5749,10 @@ def to_dict(self) -> dict: result["intention"] = from_str(self.intention) result["kind"] = self.kind result["url"] = from_str(self.url) + if self.managed_approval_required is not None: + result["managedApprovalRequired"] = from_union([from_none, from_bool], self.managed_approval_required) + if self.redirected_from is not None: + result["redirectedFrom"] = from_union([from_none, from_str], self.redirected_from) if self.request_sandbox_bypass is not None: result["requestSandboxBypass"] = from_union([from_none, from_bool], self.request_sandbox_bypass) if self.request_sandbox_bypass_reason is not None: @@ -5263,6 +5770,7 @@ class PermissionRequestWrite: file_name: str intention: str kind: ClassVar[str] = "write" + managed_approval_required: bool | None = None new_file_contents: str | None = None request_sandbox_bypass: bool | None = None request_sandbox_bypass_reason: str | None = None @@ -5275,6 +5783,7 @@ def from_dict(obj: Any) -> "PermissionRequestWrite": diff = from_str(obj.get("diff")) file_name = from_str(obj.get("fileName")) intention = from_str(obj.get("intention")) + managed_approval_required = from_union([from_none, from_bool], obj.get("managedApprovalRequired")) new_file_contents = from_union([from_none, from_str], obj.get("newFileContents")) request_sandbox_bypass = from_union([from_none, from_bool], obj.get("requestSandboxBypass")) request_sandbox_bypass_reason = from_union([from_none, from_str], obj.get("requestSandboxBypassReason")) @@ -5284,6 +5793,7 @@ def from_dict(obj: Any) -> "PermissionRequestWrite": diff=diff, file_name=file_name, intention=intention, + managed_approval_required=managed_approval_required, new_file_contents=new_file_contents, request_sandbox_bypass=request_sandbox_bypass, request_sandbox_bypass_reason=request_sandbox_bypass_reason, @@ -5297,6 +5807,8 @@ def to_dict(self) -> dict: result["fileName"] = from_str(self.file_name) result["intention"] = from_str(self.intention) result["kind"] = self.kind + if self.managed_approval_required is not None: + result["managedApprovalRequired"] = from_union([from_none, from_bool], self.managed_approval_required) if self.new_file_contents is not None: result["newFileContents"] = from_union([from_none, from_str], self.new_file_contents) if self.request_sandbox_bypass is not None: @@ -5315,6 +5827,7 @@ class PermissionRequestedData: request_id: str prompt_request: PermissionPromptRequest | None = None resolved_by_hook: bool | None = None + risk_assessment: Any = None @staticmethod def from_dict(obj: Any) -> "PermissionRequestedData": @@ -5323,11 +5836,13 @@ def from_dict(obj: Any) -> "PermissionRequestedData": request_id = from_str(obj.get("requestId")) prompt_request = from_union([from_none, _load_PermissionPromptRequest], obj.get("promptRequest")) resolved_by_hook = from_union([from_none, from_bool], obj.get("resolvedByHook")) + risk_assessment = obj.get("riskAssessment") return PermissionRequestedData( permission_request=permission_request, request_id=request_id, prompt_request=prompt_request, resolved_by_hook=resolved_by_hook, + risk_assessment=risk_assessment, ) def to_dict(self) -> dict: @@ -5338,6 +5853,8 @@ def to_dict(self) -> dict: result["promptRequest"] = from_union([from_none, lambda x: x.to_dict()], self.prompt_request) if self.resolved_by_hook is not None: result["resolvedByHook"] = from_union([from_none, from_bool], self.resolved_by_hook) + if self.risk_assessment is not None: + result["riskAssessment"] = self.risk_assessment return result @@ -5552,8 +6069,10 @@ class SessionCompactionCompleteData: status_code: int | None = None summary_content: str | None = None system_tokens: int | None = None + token_limit: int | None = None tokens_removed: int | None = None tool_definitions_tokens: int | None = None + trigger: CompactionTrigger | None = None @staticmethod def from_dict(obj: Any) -> "SessionCompactionCompleteData": @@ -5574,8 +6093,10 @@ def from_dict(obj: Any) -> "SessionCompactionCompleteData": status_code = from_union([from_none, from_int], obj.get("statusCode")) summary_content = from_union([from_none, from_str], obj.get("summaryContent")) system_tokens = from_union([from_none, from_int], obj.get("systemTokens")) + token_limit = from_union([from_none, from_int], obj.get("tokenLimit")) tokens_removed = from_union([from_none, from_int], obj.get("tokensRemoved")) tool_definitions_tokens = from_union([from_none, from_int], obj.get("toolDefinitionsTokens")) + trigger = from_union([from_none, lambda x: parse_enum(CompactionTrigger, x)], obj.get("trigger")) return SessionCompactionCompleteData( success=success, checkpoint_number=checkpoint_number, @@ -5593,8 +6114,10 @@ def from_dict(obj: Any) -> "SessionCompactionCompleteData": status_code=status_code, summary_content=summary_content, system_tokens=system_tokens, + token_limit=token_limit, tokens_removed=tokens_removed, tool_definitions_tokens=tool_definitions_tokens, + trigger=trigger, ) def to_dict(self) -> dict: @@ -5630,10 +6153,14 @@ def to_dict(self) -> dict: result["summaryContent"] = from_union([from_none, from_str], self.summary_content) if self.system_tokens is not None: result["systemTokens"] = from_union([from_none, to_int], self.system_tokens) + if self.token_limit is not None: + result["tokenLimit"] = from_union([from_none, to_int], self.token_limit) if self.tokens_removed is not None: result["tokensRemoved"] = from_union([from_none, to_int], self.tokens_removed) if self.tool_definitions_tokens is not None: result["toolDefinitionsTokens"] = from_union([from_none, to_int], self.tool_definitions_tokens) + if self.trigger is not None: + result["trigger"] = from_union([from_none, lambda x: to_enum(CompactionTrigger, x)], self.trigger) return result @@ -5641,34 +6168,49 @@ def to_dict(self) -> dict: class SessionCompactionStartData: "Context window breakdown at the start of LLM-powered conversation compaction" conversation_tokens: int | None = None + current_tokens: int | None = None model: str | None = None system_tokens: int | None = None + token_limit: int | None = None tool_definitions_tokens: int | None = None + trigger: CompactionTrigger | None = None @staticmethod def from_dict(obj: Any) -> "SessionCompactionStartData": assert isinstance(obj, dict) conversation_tokens = from_union([from_none, from_int], obj.get("conversationTokens")) + current_tokens = from_union([from_none, from_int], obj.get("currentTokens")) model = from_union([from_none, from_str], obj.get("model")) system_tokens = from_union([from_none, from_int], obj.get("systemTokens")) + token_limit = from_union([from_none, from_int], obj.get("tokenLimit")) tool_definitions_tokens = from_union([from_none, from_int], obj.get("toolDefinitionsTokens")) + trigger = from_union([from_none, lambda x: parse_enum(CompactionTrigger, x)], obj.get("trigger")) return SessionCompactionStartData( conversation_tokens=conversation_tokens, + current_tokens=current_tokens, model=model, system_tokens=system_tokens, + token_limit=token_limit, tool_definitions_tokens=tool_definitions_tokens, + trigger=trigger, ) def to_dict(self) -> dict: result: dict = {} if self.conversation_tokens is not None: result["conversationTokens"] = from_union([from_none, to_int], self.conversation_tokens) + if self.current_tokens is not None: + result["currentTokens"] = from_union([from_none, to_int], self.current_tokens) if self.model is not None: result["model"] = from_union([from_none, from_str], self.model) if self.system_tokens is not None: result["systemTokens"] = from_union([from_none, to_int], self.system_tokens) + if self.token_limit is not None: + result["tokenLimit"] = from_union([from_none, to_int], self.token_limit) if self.tool_definitions_tokens is not None: result["toolDefinitionsTokens"] = from_union([from_none, to_int], self.tool_definitions_tokens) + if self.trigger is not None: + result["trigger"] = from_union([from_none, lambda x: to_enum(CompactionTrigger, x)], self.trigger) return result @@ -5681,6 +6223,7 @@ class SessionContextChangedData: git_root: str | None = None head_commit: str | None = None host_type: WorkingDirectoryContextHostType | None = None + pending_git_context: bool | None = None repository: str | None = None repository_host: str | None = None @@ -5693,6 +6236,7 @@ def from_dict(obj: Any) -> "SessionContextChangedData": git_root = from_union([from_none, from_str], obj.get("gitRoot")) head_commit = from_union([from_none, from_str], obj.get("headCommit")) host_type = from_union([from_none, lambda x: parse_enum(WorkingDirectoryContextHostType, x)], obj.get("hostType")) + pending_git_context = from_union([from_none, from_bool], obj.get("pendingGitContext")) repository = from_union([from_none, from_str], obj.get("repository")) repository_host = from_union([from_none, from_str], obj.get("repositoryHost")) return SessionContextChangedData( @@ -5702,6 +6246,7 @@ def from_dict(obj: Any) -> "SessionContextChangedData": git_root=git_root, head_commit=head_commit, host_type=host_type, + pending_git_context=pending_git_context, repository=repository, repository_host=repository_host, ) @@ -5719,6 +6264,8 @@ def to_dict(self) -> dict: result["headCommit"] = from_union([from_none, from_str], self.head_commit) if self.host_type is not None: result["hostType"] = from_union([from_none, lambda x: to_enum(WorkingDirectoryContextHostType, x)], self.host_type) + if self.pending_git_context is not None: + result["pendingGitContext"] = from_union([from_none, from_bool], self.pending_git_context) if self.repository is not None: result["repository"] = from_union([from_none, from_str], self.repository) if self.repository_host is not None: @@ -5726,6 +6273,30 @@ def to_dict(self) -> dict: return result +@dataclass +class SessionContextClearedData: + "Context-cleared details emitted when the host clears the conversation (the session.history.clearContext RPC / Session.clearContextMessages)" + messages_cleared: int + initial_message: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "SessionContextClearedData": + assert isinstance(obj, dict) + messages_cleared = from_int(obj.get("messagesCleared")) + initial_message = from_union([from_none, from_str], obj.get("initialMessage")) + return SessionContextClearedData( + messages_cleared=messages_cleared, + initial_message=initial_message, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["messagesCleared"] = to_int(self.messages_cleared) + if self.initial_message is not None: + result["initialMessage"] = from_union([from_none, from_str], self.initial_message) + return result + + @dataclass class SessionCustomAgentsUpdatedData: "Payload of `session.custom_agents_updated` with loaded custom agents plus non-fatal warnings and fatal errors." @@ -6404,6 +6975,7 @@ class SessionScheduleCreatedData: cron: str | None = None display_prompt: str | None = None interval: timedelta | None = None + origin: ScheduleOrigin | None = None recurring: bool | None = None self_paced: bool | None = None tz: str | None = None @@ -6417,6 +6989,7 @@ def from_dict(obj: Any) -> "SessionScheduleCreatedData": cron = from_union([from_none, from_str], obj.get("cron")) display_prompt = from_union([from_none, from_str], obj.get("displayPrompt")) interval = from_union([from_none, from_timedelta], obj.get("intervalMs")) + origin = from_union([from_none, lambda x: parse_enum(ScheduleOrigin, x)], obj.get("origin")) recurring = from_union([from_none, from_bool], obj.get("recurring")) self_paced = from_union([from_none, from_bool], obj.get("selfPaced")) tz = from_union([from_none, from_str], obj.get("tz")) @@ -6427,6 +7000,7 @@ def from_dict(obj: Any) -> "SessionScheduleCreatedData": cron=cron, display_prompt=display_prompt, interval=interval, + origin=origin, recurring=recurring, self_paced=self_paced, tz=tz, @@ -6444,6 +7018,8 @@ def to_dict(self) -> dict: result["displayPrompt"] = from_union([from_none, from_str], self.display_prompt) if self.interval is not None: result["intervalMs"] = from_union([from_none, to_timedelta_int], self.interval) + if self.origin is not None: + result["origin"] = from_union([from_none, lambda x: to_enum(ScheduleOrigin, x)], self.origin) if self.recurring is not None: result["recurring"] = from_union([from_none, from_bool], self.recurring) if self.self_paced is not None: @@ -6636,6 +7212,7 @@ class SessionStartData: context: WorkingDirectoryContext | None = None context_tier: ContextTier | None = None detached_from_spawning_parent_session_id: str | None = None + github_mcp_tool_config: GitHubMcpToolConfig | None = None reasoning_effort: str | None = None reasoning_summary: ReasoningSummary | None = None remote_steerable: bool | None = None @@ -6655,6 +7232,7 @@ def from_dict(obj: Any) -> "SessionStartData": context = from_union([from_none, WorkingDirectoryContext.from_dict], obj.get("context")) context_tier = from_union([from_none, lambda x: parse_enum(ContextTier, x)], obj.get("contextTier")) detached_from_spawning_parent_session_id = from_union([from_none, from_str], obj.get("detachedFromSpawningParentSessionId")) + github_mcp_tool_config = from_union([from_none, GitHubMcpToolConfig.from_dict], obj.get("githubMcpToolConfig")) reasoning_effort = from_union([from_none, from_str], obj.get("reasoningEffort")) reasoning_summary = from_union([from_none, lambda x: parse_enum(ReasoningSummary, x)], obj.get("reasoningSummary")) remote_steerable = from_union([from_none, from_bool], obj.get("remoteSteerable")) @@ -6671,6 +7249,7 @@ def from_dict(obj: Any) -> "SessionStartData": context=context, context_tier=context_tier, detached_from_spawning_parent_session_id=detached_from_spawning_parent_session_id, + github_mcp_tool_config=github_mcp_tool_config, reasoning_effort=reasoning_effort, reasoning_summary=reasoning_summary, remote_steerable=remote_steerable, @@ -6694,6 +7273,8 @@ def to_dict(self) -> dict: result["contextTier"] = from_union([from_none, lambda x: to_enum(ContextTier, x)], self.context_tier) if self.detached_from_spawning_parent_session_id is not None: result["detachedFromSpawningParentSessionId"] = from_union([from_none, from_str], self.detached_from_spawning_parent_session_id) + if self.github_mcp_tool_config is not None: + result["githubMcpToolConfig"] = from_union([from_none, lambda x: to_class(GitHubMcpToolConfig, x)], self.github_mcp_tool_config) if self.reasoning_effort is not None: result["reasoningEffort"] = from_union([from_none, from_str], self.reasoning_effort) if self.reasoning_summary is not None: @@ -6712,21 +7293,36 @@ def to_dict(self) -> dict: @dataclass class SessionTaskCompleteData: "Task completion notification with summary from the agent" + objective_id: int | None = None + outcome: TaskCompletionOutcome | None = None + reason: str | None = None success: bool | None = None summary: str | None = None @staticmethod def from_dict(obj: Any) -> "SessionTaskCompleteData": assert isinstance(obj, dict) + objective_id = from_union([from_none, from_int], obj.get("objectiveId")) + outcome = from_union([from_none, lambda x: parse_enum(TaskCompletionOutcome, x)], obj.get("outcome")) + reason = from_union([from_none, from_str], obj.get("reason")) success = from_union([from_none, from_bool], obj.get("success")) summary = from_union([from_none, from_str], obj.get("summary")) return SessionTaskCompleteData( + objective_id=objective_id, + outcome=outcome, + reason=reason, success=success, summary=summary, ) def to_dict(self) -> dict: result: dict = {} + if self.objective_id is not None: + result["objectiveId"] = from_union([from_none, to_int], self.objective_id) + if self.outcome is not None: + result["outcome"] = from_union([from_none, lambda x: to_enum(TaskCompletionOutcome, x)], self.outcome) + if self.reason is not None: + result["reason"] = from_union([from_none, from_str], self.reason) if self.success is not None: result["success"] = from_union([from_none, from_bool], self.success) if self.summary is not None: @@ -7193,6 +7789,7 @@ class SkillsLoadedSkill: source: SkillSource user_invocable: bool argument_hint: str | None = None + command_name: str | None = None path: str | None = None @staticmethod @@ -7204,6 +7801,7 @@ def from_dict(obj: Any) -> "SkillsLoadedSkill": source = parse_enum(SkillSource, obj.get("source")) user_invocable = from_bool(obj.get("userInvocable")) argument_hint = from_union([from_none, from_str], obj.get("argumentHint")) + command_name = from_union([from_none, from_str], obj.get("commandName")) path = from_union([from_none, from_str], obj.get("path")) return SkillsLoadedSkill( description=description, @@ -7212,6 +7810,7 @@ def from_dict(obj: Any) -> "SkillsLoadedSkill": source=source, user_invocable=user_invocable, argument_hint=argument_hint, + command_name=command_name, path=path, ) @@ -7224,6 +7823,8 @@ def to_dict(self) -> dict: result["userInvocable"] = from_bool(self.user_invocable) if self.argument_hint is not None: result["argumentHint"] = from_union([from_none, from_str], self.argument_hint) + if self.command_name is not None: + result["commandName"] = from_union([from_none, from_str], self.command_name) if self.path is not None: result["path"] = from_union([from_none, from_str], self.path) return result @@ -7235,6 +7836,7 @@ class SubagentCompletedData: agent_display_name: str agent_name: str tool_call_id: str + cancelled: bool | None = None duration: timedelta | None = None model: str | None = None total_tokens: int | None = None @@ -7246,6 +7848,7 @@ def from_dict(obj: Any) -> "SubagentCompletedData": agent_display_name = from_str(obj.get("agentDisplayName")) agent_name = from_str(obj.get("agentName")) tool_call_id = from_str(obj.get("toolCallId")) + cancelled = from_union([from_none, from_bool], obj.get("cancelled")) duration = from_union([from_none, from_timedelta], obj.get("durationMs")) model = from_union([from_none, from_str], obj.get("model")) total_tokens = from_union([from_none, from_int], obj.get("totalTokens")) @@ -7254,6 +7857,7 @@ def from_dict(obj: Any) -> "SubagentCompletedData": agent_display_name=agent_display_name, agent_name=agent_name, tool_call_id=tool_call_id, + cancelled=cancelled, duration=duration, model=model, total_tokens=total_tokens, @@ -7265,6 +7869,8 @@ def to_dict(self) -> dict: result["agentDisplayName"] = from_str(self.agent_display_name) result["agentName"] = from_str(self.agent_name) result["toolCallId"] = from_str(self.tool_call_id) + if self.cancelled is not None: + result["cancelled"] = from_union([from_none, from_bool], self.cancelled) if self.duration is not None: result["durationMs"] = from_union([from_none, to_timedelta_int], self.duration) if self.model is not None: @@ -7407,6 +8013,7 @@ class SystemMessageData: "System/developer instruction content with role and optional template metadata" content: str role: SystemMessageRole + interaction_id: str | None = None metadata: SystemMessageMetadata | None = None name: str | None = None @@ -7415,11 +8022,13 @@ def from_dict(obj: Any) -> "SystemMessageData": assert isinstance(obj, dict) content = from_str(obj.get("content")) role = parse_enum(SystemMessageRole, obj.get("role")) + interaction_id = from_union([from_none, from_str], obj.get("interactionId")) metadata = from_union([from_none, SystemMessageMetadata.from_dict], obj.get("metadata")) name = from_union([from_none, from_str], obj.get("name")) return SystemMessageData( content=content, role=role, + interaction_id=interaction_id, metadata=metadata, name=name, ) @@ -7428,6 +8037,8 @@ def to_dict(self) -> dict: result: dict = {} result["content"] = from_str(self.content) result["role"] = to_enum(SystemMessageRole, self.role) + if self.interaction_id is not None: + result["interactionId"] = from_union([from_none, from_str], self.interaction_id) if self.metadata is not None: result["metadata"] = from_union([from_none, lambda x: to_class(SystemMessageMetadata, x)], self.metadata) if self.name is not None: @@ -7552,6 +8163,66 @@ def to_dict(self) -> dict: return result +@dataclass +class SystemNotificationFactoryCompleted: + "System notification metadata for a factory execution attempt that reached a terminal state." + attempt: int + consumed_nano_aiu: int + consumed_subagents: int + elapsed_ms: int + factory_name: str + run_id: str + status: SystemNotificationFactoryCompletedStatus + type: ClassVar[str] = "factory_completed" + failure: Any = None + result_preview: str | None = None + retry_guidance: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "SystemNotificationFactoryCompleted": + assert isinstance(obj, dict) + attempt = from_int(obj.get("attempt")) + consumed_nano_aiu = from_int(obj.get("consumedNanoAiu")) + consumed_subagents = from_int(obj.get("consumedSubagents")) + elapsed_ms = from_int(obj.get("elapsedMs")) + factory_name = from_str(obj.get("factoryName")) + run_id = from_str(obj.get("runId")) + status = parse_enum(SystemNotificationFactoryCompletedStatus, obj.get("status")) + failure = obj.get("failure") + result_preview = from_union([from_none, from_str], obj.get("resultPreview")) + retry_guidance = from_union([from_none, from_str], obj.get("retryGuidance")) + return SystemNotificationFactoryCompleted( + attempt=attempt, + consumed_nano_aiu=consumed_nano_aiu, + consumed_subagents=consumed_subagents, + elapsed_ms=elapsed_ms, + factory_name=factory_name, + run_id=run_id, + status=status, + failure=failure, + result_preview=result_preview, + retry_guidance=retry_guidance, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["attempt"] = to_int(self.attempt) + result["consumedNanoAiu"] = to_int(self.consumed_nano_aiu) + result["consumedSubagents"] = to_int(self.consumed_subagents) + result["elapsedMs"] = to_int(self.elapsed_ms) + result["factoryName"] = from_str(self.factory_name) + result["runId"] = from_str(self.run_id) + result["status"] = to_enum(SystemNotificationFactoryCompletedStatus, self.status) + result["type"] = self.type + if self.failure is not None: + result["failure"] = self.failure + if self.result_preview is not None: + result["resultPreview"] = from_union([from_none, from_str], self.result_preview) + if self.retry_guidance is not None: + result["retryGuidance"] = from_union([from_none, from_str], self.retry_guidance) + return result + + @dataclass class SystemNotificationInstructionDiscovered: "System notification metadata for an instruction file discovered during tool access, including source, trigger file, and tool." @@ -7676,6 +8347,28 @@ def to_dict(self) -> dict: return result +@dataclass +class SystemNotificationUnclassified: + "System notification metadata from an external host that does not match a runtime-owned notification kind." + type: ClassVar[str] = "unclassified" + metadata: Any = None + + @staticmethod + def from_dict(obj: Any) -> "SystemNotificationUnclassified": + assert isinstance(obj, dict) + metadata = obj.get("metadata") + return SystemNotificationUnclassified( + metadata=metadata, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["type"] = self.type + if self.metadata is not None: + result["metadata"] = self.metadata + return result + + @dataclass class ToolExecutionCompleteContentAudio: "Audio content block with base64-encoded data" @@ -7906,6 +8599,7 @@ class ToolExecutionCompleteData: # Deprecated: this field is deprecated. parent_tool_call_id: str | None = None result: ToolExecutionCompleteResult | None = None + rte: bool | None = None sandboxed: bool | None = None tool_description: ToolExecutionCompleteToolDescription | None = None tool_telemetry: dict[str, Any] | None = None @@ -7923,6 +8617,7 @@ def from_dict(obj: Any) -> "ToolExecutionCompleteData": model = from_union([from_none, from_str], obj.get("model")) parent_tool_call_id = from_union([from_none, from_str], obj.get("parentToolCallId")) result = from_union([from_none, ToolExecutionCompleteResult.from_dict], obj.get("result")) + rte = from_union([from_none, from_bool], obj.get("rte")) sandboxed = from_union([from_none, from_bool], obj.get("sandboxed")) tool_description = from_union([from_none, ToolExecutionCompleteToolDescription.from_dict], obj.get("toolDescription")) tool_telemetry = from_union([from_none, lambda x: from_dict(lambda x: x, x)], obj.get("toolTelemetry")) @@ -7937,6 +8632,7 @@ def from_dict(obj: Any) -> "ToolExecutionCompleteData": model=model, parent_tool_call_id=parent_tool_call_id, result=result, + rte=rte, sandboxed=sandboxed, tool_description=tool_description, tool_telemetry=tool_telemetry, @@ -7961,6 +8657,8 @@ def to_dict(self) -> dict: result["parentToolCallId"] = from_union([from_none, from_str], self.parent_tool_call_id) if self.result is not None: result["result"] = from_union([from_none, lambda x: to_class(ToolExecutionCompleteResult, x)], self.result) + if self.rte is not None: + result["rte"] = from_union([from_none, from_bool], self.rte) if self.sandboxed is not None: result["sandboxed"] = from_union([from_none, from_bool], self.sandboxed) if self.tool_description is not None: @@ -8396,6 +9094,7 @@ class ToolExecutionStartData: model: str | None = None # Deprecated: this field is deprecated. parent_tool_call_id: str | None = None + rte: bool | None = None shell_tool_info: ToolExecutionStartShellToolInfo | None = None tool_description: ToolExecutionStartToolDescription | None = None turn_id: str | None = None @@ -8411,6 +9110,7 @@ def from_dict(obj: Any) -> "ToolExecutionStartData": mcp_tool_name = from_union([from_none, from_str], obj.get("mcpToolName")) model = from_union([from_none, from_str], obj.get("model")) parent_tool_call_id = from_union([from_none, from_str], obj.get("parentToolCallId")) + rte = from_union([from_none, from_bool], obj.get("rte")) shell_tool_info = from_union([from_none, ToolExecutionStartShellToolInfo.from_dict], obj.get("shellToolInfo")) tool_description = from_union([from_none, ToolExecutionStartToolDescription.from_dict], obj.get("toolDescription")) turn_id = from_union([from_none, from_str], obj.get("turnId")) @@ -8423,6 +9123,7 @@ def from_dict(obj: Any) -> "ToolExecutionStartData": mcp_tool_name=mcp_tool_name, model=model, parent_tool_call_id=parent_tool_call_id, + rte=rte, shell_tool_info=shell_tool_info, tool_description=tool_description, turn_id=turn_id, @@ -8444,6 +9145,8 @@ def to_dict(self) -> dict: result["model"] = from_union([from_none, from_str], self.model) if self.parent_tool_call_id is not None: result["parentToolCallId"] = from_union([from_none, from_str], self.parent_tool_call_id) + if self.rte is not None: + result["rte"] = from_union([from_none, from_bool], self.rte) if self.shell_tool_info is not None: result["shellToolInfo"] = from_union([from_none, lambda x: to_class(ToolExecutionStartShellToolInfo, x)], self.shell_tool_info) if self.tool_description is not None: @@ -8458,21 +9161,27 @@ class ToolExecutionStartShellToolInfo: "Shell-aware path hints for a shell tool's command, captured at start time so consumers can snapshot a file's pre-image before the tool runs." has_write_file_redirection: bool possible_paths: list[str] + # Experimental: this field is part of an experimental API and may change or be removed. + display_command: str | None = None @staticmethod def from_dict(obj: Any) -> "ToolExecutionStartShellToolInfo": assert isinstance(obj, dict) has_write_file_redirection = from_bool(obj.get("hasWriteFileRedirection")) possible_paths = from_list(from_str, obj.get("possiblePaths")) + display_command = from_union([from_none, from_str], obj.get("displayCommand")) return ToolExecutionStartShellToolInfo( has_write_file_redirection=has_write_file_redirection, possible_paths=possible_paths, + display_command=display_command, ) def to_dict(self) -> dict: result: dict = {} result["hasWriteFileRedirection"] = from_bool(self.has_write_file_redirection) result["possiblePaths"] = from_list(from_str, self.possible_paths) + if self.display_command is not None: + result["displayCommand"] = from_union([from_none, from_str], self.display_command) return result @@ -8850,6 +9559,28 @@ def to_dict(self) -> dict: return result +@dataclass +class UserToolSessionApprovalFactory: + "Session-scoped factory approval, optionally narrowed by approval key." + kind: ClassVar[str] = "factory" + approval_key: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "UserToolSessionApprovalFactory": + assert isinstance(obj, dict) + approval_key = from_union([from_none, from_str], obj.get("approvalKey")) + return UserToolSessionApprovalFactory( + approval_key=approval_key, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = self.kind + if self.approval_key is not None: + result["approvalKey"] = from_union([from_none, from_str], self.approval_key) + return result + + @dataclass class UserToolSessionApprovalMcp: "Session-scoped tool-approval rule for an MCP server tool, or all tools on the server when `toolName` is null." @@ -8935,6 +9666,7 @@ class WorkingDirectoryContext: git_root: str | None = None head_commit: str | None = None host_type: WorkingDirectoryContextHostType | None = None + pending_git_context: bool | None = None repository: str | None = None repository_host: str | None = None @@ -8947,6 +9679,7 @@ def from_dict(obj: Any) -> "WorkingDirectoryContext": git_root = from_union([from_none, from_str], obj.get("gitRoot")) head_commit = from_union([from_none, from_str], obj.get("headCommit")) host_type = from_union([from_none, lambda x: parse_enum(WorkingDirectoryContextHostType, x)], obj.get("hostType")) + pending_git_context = from_union([from_none, from_bool], obj.get("pendingGitContext")) repository = from_union([from_none, from_str], obj.get("repository")) repository_host = from_union([from_none, from_str], obj.get("repositoryHost")) return WorkingDirectoryContext( @@ -8956,6 +9689,7 @@ def from_dict(obj: Any) -> "WorkingDirectoryContext": git_root=git_root, head_commit=head_commit, host_type=host_type, + pending_git_context=pending_git_context, repository=repository, repository_host=repository_host, ) @@ -8973,6 +9707,8 @@ def to_dict(self) -> dict: result["headCommit"] = from_union([from_none, from_str], self.head_commit) if self.host_type is not None: result["hostType"] = from_union([from_none, lambda x: to_enum(WorkingDirectoryContextHostType, x)], self.host_type) + if self.pending_git_context is not None: + result["pendingGitContext"] = from_union([from_none, from_bool], self.pending_git_context) if self.repository is not None: result["repository"] = from_union([from_none, from_str], self.repository) if self.repository_host is not None: @@ -9026,6 +9762,7 @@ def _load_PermissionPromptRequest(obj: Any) -> "PermissionPromptRequest": case "path": return PermissionPromptRequestPath.from_dict(obj) case "hook": return PermissionPromptRequestHook.from_dict(obj) case "extension-management": return PermissionPromptRequestExtensionManagement.from_dict(obj) + case "factory": return PermissionPromptRequestFactory.from_dict(obj) case "extension-permission-access": return PermissionPromptRequestExtensionPermissionAccess.from_dict(obj) case _: raise ValueError(f"Unknown PermissionPromptRequest kind: {kind!r}") @@ -9043,6 +9780,7 @@ def _load_PermissionRequest(obj: Any) -> "PermissionRequest": case "custom-tool": return PermissionRequestCustomTool.from_dict(obj) case "hook": return PermissionRequestHook.from_dict(obj) case "extension-management": return PermissionRequestExtensionManagement.from_dict(obj) + case "factory": return PermissionRequestFactory.from_dict(obj) case "extension-permission-access": return PermissionRequestExtensionPermissionAccess.from_dict(obj) case _: raise ValueError(f"Unknown PermissionRequest kind: {kind!r}") @@ -9073,6 +9811,8 @@ def _load_SystemNotification(obj: Any) -> "SystemNotification": case "shell_completed": return SystemNotificationShellCompleted.from_dict(obj) case "shell_detached_completed": return SystemNotificationShellDetachedCompleted.from_dict(obj) case "instruction_discovered": return SystemNotificationInstructionDiscovered.from_dict(obj) + case "factory_completed": return SystemNotificationFactoryCompleted.from_dict(obj) + case "unclassified": return SystemNotificationUnclassified.from_dict(obj) case _: raise ValueError(f"Unknown SystemNotification type: {kind!r}") @@ -9101,6 +9841,7 @@ def _load_UserToolSessionApproval(obj: Any) -> "UserToolSessionApproval": case "memory": return UserToolSessionApprovalMemory.from_dict(obj) case "custom-tool": return UserToolSessionApprovalCustomTool.from_dict(obj) case "extension-management": return UserToolSessionApprovalExtensionManagement.from_dict(obj) + case "factory": return UserToolSessionApprovalFactory.from_dict(obj) case "extension-permission-access": return UserToolSessionApprovalExtensionPermissionAccess.from_dict(obj) case _: raise ValueError(f"Unknown UserToolSessionApproval kind: {kind!r}") @@ -9118,11 +9859,11 @@ def _load_UserToolSessionApproval(obj: Any) -> "UserToolSessionApproval": # Derived user-facing permission prompt details for UI consumers -PermissionPromptRequest = PermissionPromptRequestCommands | PermissionPromptRequestWrite | PermissionPromptRequestRead | PermissionPromptRequestMcp | PermissionPromptRequestUrl | PermissionPromptRequestMemory | PermissionPromptRequestCustomTool | PermissionPromptRequestPath | PermissionPromptRequestHook | PermissionPromptRequestExtensionManagement | PermissionPromptRequestExtensionPermissionAccess +PermissionPromptRequest = PermissionPromptRequestCommands | PermissionPromptRequestWrite | PermissionPromptRequestRead | PermissionPromptRequestMcp | PermissionPromptRequestUrl | PermissionPromptRequestMemory | PermissionPromptRequestCustomTool | PermissionPromptRequestPath | PermissionPromptRequestHook | PermissionPromptRequestExtensionManagement | PermissionPromptRequestFactory | PermissionPromptRequestExtensionPermissionAccess # Details of the permission being requested -PermissionRequest = PermissionRequestShell | PermissionRequestWrite | PermissionRequestRead | PermissionRequestMcp | PermissionRequestUrl | PermissionRequestMemory | PermissionRequestCustomTool | PermissionRequestHook | PermissionRequestExtensionManagement | PermissionRequestExtensionPermissionAccess +PermissionRequest = PermissionRequestShell | PermissionRequestWrite | PermissionRequestRead | PermissionRequestMcp | PermissionRequestUrl | PermissionRequestMemory | PermissionRequestCustomTool | PermissionRequestHook | PermissionRequestExtensionManagement | PermissionRequestFactory | PermissionRequestExtensionPermissionAccess # Location within a cited source (character, page, or content-block range) that supports a span. @@ -9130,11 +9871,11 @@ def _load_UserToolSessionApproval(obj: Any) -> "UserToolSessionApproval": # Structured metadata identifying what triggered this notification -SystemNotification = SystemNotificationAgentCompleted | SystemNotificationAgentIdle | SystemNotificationNewInboxMessage | SystemNotificationShellCompleted | SystemNotificationShellDetachedCompleted | SystemNotificationInstructionDiscovered +SystemNotification = SystemNotificationAgentCompleted | SystemNotificationAgentIdle | SystemNotificationNewInboxMessage | SystemNotificationShellCompleted | SystemNotificationShellDetachedCompleted | SystemNotificationInstructionDiscovered | SystemNotificationFactoryCompleted | SystemNotificationUnclassified # The approval to add as a session-scoped rule -UserToolSessionApproval = UserToolSessionApprovalCommands | UserToolSessionApprovalRead | UserToolSessionApprovalWrite | UserToolSessionApprovalMcp | UserToolSessionApprovalMemory | UserToolSessionApprovalCustomTool | UserToolSessionApprovalExtensionManagement | UserToolSessionApprovalExtensionPermissionAccess +UserToolSessionApproval = UserToolSessionApprovalCommands | UserToolSessionApprovalRead | UserToolSessionApprovalWrite | UserToolSessionApprovalMcp | UserToolSessionApprovalMemory | UserToolSessionApprovalCustomTool | UserToolSessionApprovalExtensionManagement | UserToolSessionApprovalFactory | UserToolSessionApprovalExtensionPermissionAccess # The embedded resource contents, either text or base64-encoded binary @@ -9145,6 +9886,21 @@ def _load_UserToolSessionApproval(obj: Any) -> "UserToolSessionApproval": PermissionResult = PermissionApproved | PermissionApprovedForSession | PermissionApprovedForLocation | PermissionCancelled | PermissionDeniedByRules | PermissionDeniedNoApprovalRuleAndCouldNotRequestFromUser | PermissionDeniedInteractivelyByUser | PermissionDeniedByContentExclusionPolicy | PermissionDeniedByPermissionRequestHook +# Experimental: this enum is part of an experimental API and may change or be removed. +class AutoApprovalJudgeFailureReason(Enum): + "Why the auto-approval judge produced no usable recommendation. Present only alongside an `error` recommendation, where the human-readable reason is a fixed string and therefore cannot distinguish these cases. Intended to make a judge failure reportable by a consumer that has no access to the host's logs." + # The judge model call exceeded its deadline. + TIMEOUT = "timeout" + # The judge model call was cancelled before it returned. + ABORT = "abort" + # The judge model call completed but returned no content. + EMPTY_RESPONSE = "empty_response" + # The judge model call failed (for example a transport, authentication, or rate-limit error). + MODEL_ERROR = "model_error" + # The judge model replied, but the reply carried no ALLOW/DENY verdict. + PARSE_ERROR = "parse_error" + + # Experimental: this enum is part of an experimental API and may change or be removed. class AutoApprovalRecommendation(Enum): "Outcome of the auto-approval safety judge for a permission request. Present only when auto mode is enabled; its absence means the judge did not evaluate the request (auto mode was off)." @@ -9188,6 +9944,8 @@ class AbortReason(Enum): REMOTE_COMMAND = "remote_command" # An MCP server delivered a user.abort notification. USER_ABORT = "user_abort" + # Autopilot stopped the run because the active objective reached its user-set --max-ai-credits limit. + AUTOPILOT_CREDIT_LIMIT = "autopilot_credit_limit" class AssistantMessageToolRequestType(Enum): @@ -9278,6 +10036,20 @@ class BinaryAssetType(Enum): RESOURCE = "resource" +class CompactionTrigger(Enum): + "What initiated a conversation compaction" + # Background compaction started automatically because context utilization crossed the background threshold. + THRESHOLD = "threshold" + # Compaction forced by a context-limit model response (e.g. HTTP 413) before retrying the request. + CONTEXT_LIMIT_RETRY = "context_limit_retry" + # User-requested compaction, e.g. the /compact command or the history.compact API. + MANUAL = "manual" + # Emergency compaction triggered by high process memory usage. + MEMORY_PRESSURE = "memory_pressure" + # Compaction requested while switching to a model with a smaller context window. + MODEL_SWITCH = "model_switch" + + class ContextTier(Enum): "Allowed values for the `ContextTier` enumeration." # Default context tier with standard context window size. @@ -9340,6 +10112,14 @@ class ExtensionsLoadedExtensionStatus(Enum): STARTING = "starting" +class FactoryPermissionOperation(Enum): + "Operation gated by a factory permission request." + # Running a registered factory, which spends subagents, active time, and AI credits under the approved limits. + RUN = "run" + # Authoring a factory, which writes JavaScript into a session-scoped extension and loads it. + AUTHOR = "author" + + class HandoffSourceType(Enum): "Origin type of the session being handed off" # The handoff originated from a remote session. @@ -9369,12 +10149,16 @@ class ManagedSettingsEnforcedEscalation(Enum): class ManagedSettingsResolvedSource(Enum): - "Which channel supplied the effective enterprise managed settings (highest-authority present layer wins wholesale)" - # Account/org policy self-fetched from the GitHub managed-settings endpoint (higher authority). + "Summary of which managed-settings channels contributed to the effective session policy. Use the per-channel booleans for exact provenance." + # Only the server/account channel contributed. SERVER = "server" - # Device-level MDM policy discovered from plist/registry/file (lower authority). + # Only the device MDM/plist/registry/file channel contributed. DEVICE = "device" - # No managed policy is in force (no layer contributed). + # Only session-local SDK-host injection contributed. + CLIENT = "client" + # More than one channel contributed. Ordinary keys resolve device over server per key, while permissions compose restrictively across all present layers. + MIXED = "mixed" + # No managed policy is in force (no channel contributed). NONE = "none" @@ -9431,7 +10215,7 @@ class McpServerSource(Enum): class McpServerStatus(Enum): - "Connection status: connected, failed, needs-auth, pending, disabled, or not_configured" + "Connection status: connected, failed, needs-auth, pending, disabled, stopped, or not_configured" # The server is connected and available. CONNECTED = "connected" # The server failed to connect or initialize. @@ -9442,6 +10226,8 @@ class McpServerStatus(Enum): PENDING = "pending" # The server is configured but disabled. DISABLED = "disabled" + # The server was intentionally stopped and can be restarted on demand when policy permits; a server quarantined by restrictive managed policy stays stopped and cannot be restarted until the policy allows it. + STOPPED = "stopped" # The server is not configured for this session. NOT_CONFIGURED = "not_configured" @@ -9562,6 +10348,14 @@ class ReasoningSummary(Enum): DETAILED = "detailed" +class ScheduleOrigin(Enum): + "Who created the schedule: `user` (an explicit user action such as `/every` or `/after`) or `model` (the agent via the `manage_schedule` tool). Gates whether a scheduled skill that opted out of model invocation may fire: only user-created schedules may." + # The schedule was created by an explicit user action, such as `/every` or `/after`. + USER = "user" + # The schedule was created by the agent via the `manage_schedule` tool. + MODEL = "model" + + class SessionLimitsExhaustedResponseAction(Enum): "User action selected for an exhausted session limit." # Increase the current max by an exact AI Credits amount. @@ -9636,6 +10430,28 @@ class SystemNotificationAgentCompletedStatus(Enum): FAILED = "failed" +class SystemNotificationFactoryCompletedStatus(Enum): + "Terminal status reached by a factory execution attempt." + # The factory completed successfully. + COMPLETED = "completed" + # The factory was halted. + HALTED = "halted" + # The factory was cancelled. + CANCELLED = "cancelled" + # The factory failed. + ERROR = "error" + + +class TaskCompletionOutcome(Enum): + "Semantic result of evaluating a task completion request" + # The completion request was accepted and the objective is complete. + COMPLETED = "completed" + # The completion request was rejected because more work or validation remains. + CONTINUE = "continue" + # Completion cannot proceed without intervention; the active objective is paused when one is identified. + BLOCKED = "blocked" + + class ToolExecutionCompleteContentResourceLinkIconTheme(Enum): "Theme variant this icon is intended for" # Icon intended for light themes. @@ -9708,7 +10524,7 @@ class WorkspaceFileChangedOperation(Enum): UPDATE = "update" -SessionEventData = SessionStartData | SessionResumeData | SessionRemoteSteerableChangedData | SessionErrorData | SessionIdleData | SessionTitleChangedData | SessionScheduleCreatedData | SessionScheduleCancelledData | SessionScheduleRearmedData | SessionAutopilotObjectiveChangedData | SessionInfoData | SessionWarningData | SessionModelChangeData | SessionModeChangedData | SessionSessionLimitsChangedData | SessionPermissionsChangedData | SessionPlanChangedData | SessionTodosChangedData | SessionWorkspaceFileChangedData | SessionHandoffData | SessionTruncationData | SessionSnapshotRewindData | SessionShutdownData | SessionUsageCheckpointData | SessionContextChangedData | SessionUsageInfoData | SessionCompactionStartData | SessionCompactionCompleteData | SessionTaskCompleteData | UserMessageData | PendingMessagesModifiedData | AssistantTurnStartData | AssistantTurnRetryData | AssistantIntentData | AssistantServerToolProgressData | AssistantReasoningData | AssistantReasoningDeltaData | AssistantToolCallDeltaData | AssistantStreamingDeltaData | AssistantMessageData | AssistantMessageStartData | AssistantMessageDeltaData | AssistantTurnEndData | AssistantIdleData | AssistantUsageData | ModelCallFailureData | ModelCallStartData | AbortData | ToolUserRequestedData | ToolExecutionStartData | ToolExecutionPartialResultData | ToolExecutionProgressData | ToolExecutionCompleteData | ToolSearchActivatedData | SkillInvokedData | SubagentStartedData | SubagentCompletedData | SubagentFailedData | SubagentSelectedData | SubagentDeselectedData | HookStartData | HookEndData | HookProgressData | SessionBinaryAssetData | SystemMessageData | SystemNotificationData | PermissionRequestedData | PermissionCompletedData | UserInputRequestedData | UserInputCompletedData | ElicitationRequestedData | ElicitationCompletedData | SamplingRequestedData | SamplingCompletedData | McpOauthRequiredData | McpOauthCompletedData | McpHeadersRefreshRequiredData | McpHeadersRefreshCompletedData | SessionCustomNotificationData | ExternalToolRequestedData | ExternalToolCompletedData | CommandQueuedData | CommandExecuteData | CommandCompletedData | AutoModeSwitchRequestedData | AutoModeSwitchCompletedData | SessionLimitsExhaustedRequestedData | SessionLimitsExhaustedCompletedData | SessionAutoModeResolvedData | SessionManagedSettingsResolvedData | SessionManagedSettingsEnforcedData | CommandsChangedData | CapabilitiesChangedData | ExitPlanModeRequestedData | ExitPlanModeCompletedData | SessionToolsUpdatedData | SessionBackgroundTasksChangedData | SessionSkillsLoadedData | SessionCustomAgentsUpdatedData | SessionMcpServersLoadedData | SessionMcpServerStatusChangedData | McpToolsListChangedData | McpResourcesListChangedData | McpPromptsListChangedData | SessionExtensionsLoadedData | SessionCanvasOpenedData | SessionCanvasRegistryChangedData | SessionCanvasClosedData | SessionCanvasUnavailableData | SessionCanvasRecordedData | SessionCanvasRemovedData | SessionExtensionsAttachmentsPushedData | McpAppToolCallCompleteData | RawSessionEventData | Data +SessionEventData = SessionStartData | SessionResumeData | SessionRemoteSteerableChangedData | SessionErrorData | SessionIdleData | SessionTitleChangedData | SessionScheduleCreatedData | SessionScheduleCancelledData | SessionScheduleRearmedData | SessionAutopilotObjectiveChangedData | SessionInfoData | SessionWarningData | SessionModelChangeData | SessionModeChangedData | SessionSessionLimitsChangedData | SessionPermissionsChangedData | SessionPlanChangedData | SessionTodosChangedData | SessionWorkspaceFileChangedData | SessionHandoffData | SessionTruncationData | SessionSnapshotRewindData | SessionShutdownData | SessionUsageCheckpointData | SessionContextChangedData | SessionUsageInfoData | SessionContextClearedData | SessionCompactionStartData | SessionCompactionCompleteData | SessionTaskCompleteData | UserMessageData | PendingMessagesModifiedData | AssistantTurnStartData | AssistantTurnRetryData | AssistantIntentData | AssistantServerToolProgressData | AssistantReasoningData | AssistantReasoningDeltaData | AssistantToolCallDeltaData | AssistantStreamingDeltaData | AssistantMessageData | AssistantMessageStartData | AssistantMessageDeltaData | AssistantTurnEndData | AssistantIdleData | AssistantUsageData | ModelCallFailureData | ModelCallStartData | AbortData | ToolUserRequestedData | ToolExecutionStartData | ToolExecutionPartialResultData | ToolExecutionProgressData | ToolExecutionCompleteData | ToolSearchActivatedData | SkillInvokedData | SubagentStartedData | SubagentCompletedData | SubagentFailedData | SubagentSelectedData | SubagentDeselectedData | HookStartData | HookEndData | HookProgressData | SessionBinaryAssetData | SystemMessageData | SystemNotificationData | PermissionRequestedData | PermissionCompletedData | UserInputRequestedData | UserInputCompletedData | ElicitationRequestedData | ElicitationCompletedData | SamplingRequestedData | SamplingCompletedData | McpOauthRequiredData | McpOauthCompletedData | McpHeadersRefreshRequiredData | McpHeadersRefreshCompletedData | SessionCustomNotificationData | ExternalToolRequestedData | ExternalToolCompletedData | CommandQueuedData | CommandExecuteData | CommandCompletedData | AutoModeSwitchRequestedData | AutoModeSwitchCompletedData | SessionLimitsExhaustedRequestedData | SessionLimitsExhaustedCompletedData | SessionAutoModeResolvedData | SessionManagedSettingsResolvedData | SessionManagedSettingsEnforcedData | CommandsChangedData | CapabilitiesChangedData | ExitPlanModeRequestedData | ExitPlanModeCompletedData | SessionToolsUpdatedData | SessionBackgroundTasksChangedData | FactoryRunUpdatedData | SessionSkillsLoadedData | SessionCustomAgentsUpdatedData | SessionMcpServersLoadedData | SessionMcpServerStatusChangedData | McpToolsListChangedData | McpResourcesListChangedData | McpPromptsListChangedData | SessionExtensionsLoadedData | SessionCanvasOpenedData | SessionCanvasRegistryChangedData | SessionCanvasClosedData | SessionCanvasUnavailableData | SessionCanvasRecordedData | SessionCanvasRemovedData | SessionExtensionsAttachmentsPushedData | McpAppToolCallCompleteData | RawSessionEventData | Data @dataclass @@ -9760,6 +10576,7 @@ def from_dict(obj: Any) -> "SessionEvent": case SessionEventType.SESSION_USAGE_CHECKPOINT: data = SessionUsageCheckpointData.from_dict(data_obj) case SessionEventType.SESSION_CONTEXT_CHANGED: data = SessionContextChangedData.from_dict(data_obj) case SessionEventType.SESSION_USAGE_INFO: data = SessionUsageInfoData.from_dict(data_obj) + case SessionEventType.SESSION_CONTEXT_CLEARED: data = SessionContextClearedData.from_dict(data_obj) case SessionEventType.SESSION_COMPACTION_START: data = SessionCompactionStartData.from_dict(data_obj) case SessionEventType.SESSION_COMPACTION_COMPLETE: data = SessionCompactionCompleteData.from_dict(data_obj) case SessionEventType.SESSION_TASK_COMPLETE: data = SessionTaskCompleteData.from_dict(data_obj) @@ -9831,6 +10648,7 @@ def from_dict(obj: Any) -> "SessionEvent": case SessionEventType.EXIT_PLAN_MODE_COMPLETED: data = ExitPlanModeCompletedData.from_dict(data_obj) case SessionEventType.SESSION_TOOLS_UPDATED: data = SessionToolsUpdatedData.from_dict(data_obj) case SessionEventType.SESSION_BACKGROUND_TASKS_CHANGED: data = SessionBackgroundTasksChangedData.from_dict(data_obj) + case SessionEventType.FACTORY_RUN_UPDATED: data = FactoryRunUpdatedData.from_dict(data_obj) case SessionEventType.SESSION_SKILLS_LOADED: data = SessionSkillsLoadedData.from_dict(data_obj) case SessionEventType.SESSION_CUSTOM_AGENTS_UPDATED: data = SessionCustomAgentsUpdatedData.from_dict(data_obj) case SessionEventType.SESSION_MCP_SERVERS_LOADED: data = SessionMcpServersLoadedData.from_dict(data_obj) @@ -9926,6 +10744,7 @@ def session_event_to_dict(x: SessionEvent) -> Any: "AttachmentSelectionDetails", "AttachmentSelectionDetailsEnd", "AttachmentSelectionDetailsStart", + "AutoApprovalJudgeFailureReason", "AutoApprovalRecommendation", "AutoModeResolvedReasoningBucket", "AutoModeSwitchCompletedData", @@ -9957,6 +10776,7 @@ def session_event_to_dict(x: SessionEvent) -> Any: "CommandsChangedData", "CompactionCompleteCompactionTokensUsed", "CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail", + "CompactionTrigger", "ContextTier", "CustomAgentsUpdatedAgent", "Data", @@ -9975,6 +10795,10 @@ def session_event_to_dict(x: SessionEvent) -> Any: "ExtensionsLoadedExtensionStatus", "ExternalToolCompletedData", "ExternalToolRequestedData", + "FactoryPermissionOperation", + "FactoryPermissionPhase", + "FactoryRunUpdatedData", + "GitHubMcpToolConfig", "GitHubRepoRef", "HandoffRepository", "HandoffSourceType", @@ -10036,6 +10860,7 @@ def session_event_to_dict(x: SessionEvent) -> Any: "PermissionPromptRequestCustomTool", "PermissionPromptRequestExtensionManagement", "PermissionPromptRequestExtensionPermissionAccess", + "PermissionPromptRequestFactory", "PermissionPromptRequestHook", "PermissionPromptRequestMcp", "PermissionPromptRequestMemory", @@ -10048,6 +10873,7 @@ def session_event_to_dict(x: SessionEvent) -> Any: "PermissionRequestCustomTool", "PermissionRequestExtensionManagement", "PermissionRequestExtensionPermissionAccess", + "PermissionRequestFactory", "PermissionRequestHook", "PermissionRequestMcp", "PermissionRequestMemory", @@ -10056,6 +10882,7 @@ def session_event_to_dict(x: SessionEvent) -> Any: "PermissionRequestRead", "PermissionRequestShell", "PermissionRequestShellCommand", + "PermissionRequestShellCommandSegment", "PermissionRequestShellPossibleUrl", "PermissionRequestUrl", "PermissionRequestWrite", @@ -10070,6 +10897,7 @@ def session_event_to_dict(x: SessionEvent) -> Any: "ReasoningSummary", "SamplingCompletedData", "SamplingRequestedData", + "ScheduleOrigin", "SessionAutoModeResolvedData", "SessionAutopilotObjectiveChangedData", "SessionBackgroundTasksChangedData", @@ -10083,6 +10911,7 @@ def session_event_to_dict(x: SessionEvent) -> Any: "SessionCompactionCompleteData", "SessionCompactionStartData", "SessionContextChangedData", + "SessionContextClearedData", "SessionCustomAgentsUpdatedData", "SessionCustomNotificationData", "SessionErrorData", @@ -10151,10 +10980,14 @@ def session_event_to_dict(x: SessionEvent) -> Any: "SystemNotificationAgentCompletedStatus", "SystemNotificationAgentIdle", "SystemNotificationData", + "SystemNotificationFactoryCompleted", + "SystemNotificationFactoryCompletedStatus", "SystemNotificationInstructionDiscovered", "SystemNotificationNewInboxMessage", "SystemNotificationShellCompleted", "SystemNotificationShellDetachedCompleted", + "SystemNotificationUnclassified", + "TaskCompletionOutcome", "ToolExecutionCompleteContent", "ToolExecutionCompleteContentAudio", "ToolExecutionCompleteContentImage", @@ -10202,6 +11035,7 @@ def session_event_to_dict(x: SessionEvent) -> Any: "UserToolSessionApprovalCustomTool", "UserToolSessionApprovalExtensionManagement", "UserToolSessionApprovalExtensionPermissionAccess", + "UserToolSessionApprovalFactory", "UserToolSessionApprovalMcp", "UserToolSessionApprovalMemory", "UserToolSessionApprovalRead", diff --git a/python/copilot/session.py b/python/copilot/session.py index b6736939a..2399ab36e 100644 --- a/python/copilot/session.py +++ b/python/copilot/session.py @@ -36,7 +36,6 @@ CanvasProviderOpenResult, ClientSessionApiHandlers, CommandsHandlePendingCommandRequest, - ExternalToolTextResultForLlm, HandlePendingToolCallRequest, LogRequest, MCPOauthHandlePendingRequest, @@ -45,6 +44,7 @@ ModelSwitchToRequest, PermissionDecision, PermissionDecisionApproveOnce, + PermissionDecisionContext, PermissionDecisionRequest, PermissionDecisionUserNotAvailable, ProviderTokenAcquireRequest, @@ -83,7 +83,13 @@ from .generated.session_events import ( ReasoningSummary as _RpcReasoningSummary, ) -from .tools import Tool, ToolHandler, ToolInvocation, ToolResult +from .tools import ( + Tool, + ToolHandler, + ToolInvocation, + ToolResult, + tool_result_to_external_tool_text_result_for_llm, +) logger = logging.getLogger(__name__) @@ -164,7 +170,7 @@ def _capabilities_to_dict(caps: ModelCapabilitiesOverride) -> dict: return result -ReasoningEffort = Literal["low", "medium", "high", "xhigh"] +ReasoningEffort = Literal["low", "medium", "high", "xhigh", "max"] ReasoningSummary = Literal["none", "concise", "detailed"] ContextTier = Literal["default", "long_context"] SessionFsConventions = Literal["posix", "windows"] @@ -342,12 +348,11 @@ class SystemMessageCustomizeConfig(TypedDict, total=False): @dataclass class PermissionNoResult: - """Sentinel returned by a permission handler to leave the request unanswered. + """Sentinel that leaves an event-dispatched permission request unanswered. - Only meaningful against protocol-v1 servers. v2 servers reject ``no-result`` - responses; the SDK raises :class:`ValueError` if a v2 server receives one. - Mirrors the ``{kind: "no-result"}`` extension TS adds to its ``PermissionDecision`` - union (see ``nodejs/src/types.ts:883``). + During event-based permission dispatch, the SDK suppresses its response so + another connected client, such as a human-facing host, can answer the pending + request. Legacy direct callbacks require a concrete decision and cannot abstain. """ kind: Literal["no-result"] = "no-result" @@ -355,24 +360,74 @@ class PermissionNoResult: # The decision returned by a permission handler. Identical shape to the wire # ``PermissionDecision`` discriminated union, plus a :class:`PermissionNoResult` -# sentinel for v1 servers. Construct via the generated variant classes: +# sentinel that suppresses this SDK client's response. Construct via the +# generated variant classes: # ``PermissionDecisionApproveOnce()``, ``PermissionDecisionReject(feedback=...)``, # etc. The ``kind`` discriminator is baked in as a ``ClassVar`` default by # codegen, so callers must not pass it. PermissionRequestResult = PermissionDecision | PermissionNoResult +@dataclass +class AttributedPermissionResult: + """A permission result annotated with the context describing how it was reached. + + The Copilot runtime emits an ``auto_approval_decision`` telemetry event only + when a client supplies an explicit :class:`PermissionDecisionContext` alongside + its permission reply. Wrapping a :data:`PermissionRequestResult` with this class + forwards that context to the runtime as a sibling of the decision on the wire. + + The context is informational only — it never changes permission behavior. Build + instances via :func:`create_attributed_permission_result` rather than constructing + directly, so re-attributing an already-wrapped result replaces the context instead + of nesting. + """ + + result: PermissionRequestResult + """The underlying permission decision (or :class:`PermissionNoResult`).""" + + decision_context: PermissionDecisionContext + """Context describing how and where the decision was reached.""" + + +def create_attributed_permission_result( + result: PermissionRequestResult | AttributedPermissionResult, + decision_context: PermissionDecisionContext, +) -> AttributedPermissionResult: + """Annotate a permission result with the context describing how it was reached. + + Returns an :class:`AttributedPermissionResult` carrying ``result`` and + ``decision_context`` as siblings. If ``result`` is already an + :class:`AttributedPermissionResult`, its underlying decision is preserved and the + context is *replaced* — attribution never nests. + """ + if isinstance(result, AttributedPermissionResult): + result = result.result + return AttributedPermissionResult(result=result, decision_context=decision_context) + + +class PermissionInvocation(TypedDict, total=False): + session_id: Required[str] + managed_settings_enabled: NotRequired[bool] + + _PermissionHandlerFn = Callable[ - [PermissionRequest, dict[str, str]], - PermissionRequestResult | Awaitable[PermissionRequestResult], + [PermissionRequest, PermissionInvocation], + PermissionRequestResult + | AttributedPermissionResult + | Awaitable[PermissionRequestResult | AttributedPermissionResult], ] class PermissionHandler: @staticmethod def approve_all( - request: PermissionRequest, invocation: dict[str, str] + request: PermissionRequest, invocation: PermissionInvocation ) -> PermissionRequestResult: + if invocation.get("managed_settings_enabled", False): + raise RuntimeError("approve_all cannot be used when managed settings are enabled") + if getattr(request, "managed_approval_required", False) is True: + return PermissionNoResult() return PermissionDecisionApproveOnce() @@ -943,6 +998,28 @@ class UserPromptSubmittedHookOutput(TypedDict, total=False): ] +class UserPromptTransformedHookInput(TypedDict): + """Input for the user-prompt-transformed hook.""" + + sessionId: str + timestamp: datetime + workingDirectory: str + prompt: str + transformedPrompt: str + + +class UserPromptTransformedHookOutput(TypedDict, total=False): + """Output for the user-prompt-transformed hook.""" + + modifiedTransformedPrompt: str + + +UserPromptTransformedHandler = Callable[ + [UserPromptTransformedHookInput, dict[str, str]], + UserPromptTransformedHookOutput | None | Awaitable[UserPromptTransformedHookOutput | None], +] + + class SessionStartHookInput(TypedDict): """Input for session-start hook""" @@ -1017,6 +1094,30 @@ class ErrorOccurredHookOutput(TypedDict, total=False): ] +class AgentStopHookInput(TypedDict): + """Input for the agent-stop hook.""" + + sessionId: str + timestamp: datetime + workingDirectory: str + stopReason: NotRequired[str] + transcriptPath: NotRequired[str] + stopHookActive: NotRequired[bool] + + +class AgentStopHookOutput(TypedDict, total=False): + """Output for the agent-stop hook.""" + + decision: Literal["block"] + reason: str + + +AgentStopHandler = Callable[ + [AgentStopHookInput, dict[str, str]], + AgentStopHookOutput | None | Awaitable[AgentStopHookOutput | None], +] + + class SessionHooks(TypedDict, total=False): """Configuration for session hooks""" @@ -1025,9 +1126,11 @@ class SessionHooks(TypedDict, total=False): on_post_tool_use: PostToolUseHandler on_post_tool_use_failure: PostToolUseFailureHandler on_user_prompt_submitted: UserPromptSubmittedHandler + on_user_prompt_transformed: UserPromptTransformedHandler on_session_start: SessionStartHandler on_session_end: SessionEndHandler on_error_occurred: ErrorOccurredHandler + on_agent_stop: AgentStopHandler # ============================================================================ @@ -1059,6 +1162,22 @@ class MCPHTTPServerConfig(TypedDict, total=False): MCPServerConfig = MCPStdioServerConfig | MCPHTTPServerConfig + +class GitHubMcpToolConfig(TypedDict, total=False): + """Configuration for the built-in GitHub MCP server. + + ``disable_form_deferral`` only applies to the built-in GitHub MCP server + and only has an effect when MCP Apps and form-backed GitHub tools are + enabled. + """ + + enable_all_tools: bool + additional_toolsets: list[str] + additional_tools: list[str] + enable_insiders_mode: bool + disable_form_deferral: bool + + # ============================================================================ # Custom Agent Configuration Types # ============================================================================ @@ -1080,8 +1199,8 @@ class CustomAgentConfig(TypedDict, total=False): skills: NotRequired[list[str]] # Model identifier (e.g. "claude-haiku-4.5"); runtime falls back to parent model if unavailable model: NotRequired[str] - # Reasoning effort for this agent's model. When omitted, no per-agent override - # is sent and the backend chooses its default; the parent effort is not inherited. + # Reasoning effort for this agent's model. When omitted, the runtime resolves + # model configuration, then inherits the parent effort only for the same model. reasoning_effort: NotRequired[ReasoningEffort] @@ -1183,7 +1302,8 @@ class MemoryConfiguration(TypedDict): class AzureProviderOptions(TypedDict, total=False): """Azure-specific provider configuration""" - api_version: str # Azure API version. Defaults to "2024-10-21". + # Azure API version. When omitted, the runtime uses the GA versionless v1 route. + api_version: str class ProviderTokenArgs(TypedDict): @@ -1423,7 +1543,11 @@ class CopilotSession: """ def __init__( - self, session_id: str, client: Any, workspace_path: os.PathLike[str] | str | None = None + self, + session_id: str, + client: Any, + workspace_path: os.PathLike[str] | str | None = None, + managed_settings_enabled: bool = False, ): """ Initialize a new CopilotSession. @@ -1437,8 +1561,11 @@ def __init__( client: The internal client connection to the Copilot CLI. workspace_path: Path to the session workspace directory (when infinite sessions enabled). + managed_settings_enabled: Whether managed settings were enabled when + creating or resuming the session. """ self.session_id = session_id + self._managed_settings_enabled = managed_settings_enabled self._client = client self._workspace_path = os.fsdecode(workspace_path) if workspace_path is not None else None self._event_handlers: set[Callable[[SessionEvent], None]] = set() @@ -2037,13 +2164,7 @@ async def _execute_tool_and_respond( await self.rpc.tools.handle_pending_tool_call( HandlePendingToolCallRequest( request_id=request_id, - result=ExternalToolTextResultForLlm( - text_result_for_llm=tool_result.text_result_for_llm, - error=tool_result.error, - result_type=tool_result.result_type, - tool_references=tool_result.tool_references, - tool_telemetry=tool_result.tool_telemetry, - ), + result=tool_result_to_external_tool_text_result_for_llm(tool_result), ) ) log_timing( @@ -2076,7 +2197,13 @@ async def _execute_permission_and_respond( """Execute a permission handler and respond via RPC.""" try: handler_start = time.perf_counter() - result = handler(permission_request, {"session_id": self.session_id}) + result = handler( + permission_request, + { + "session_id": self.session_id, + "managed_settings_enabled": self._managed_settings_enabled, + }, + ) if inspect.isawaitable(result): result = await result log_timing( @@ -2088,7 +2215,11 @@ async def _execute_permission_and_respond( request_id=request_id, ) - result = cast(PermissionRequestResult, result) + result = cast("PermissionRequestResult | AttributedPermissionResult", result) + decision_context: PermissionDecisionContext | None = None + if isinstance(result, AttributedPermissionResult): + decision_context = result.decision_context + result = result.result if isinstance(result, PermissionNoResult): return @@ -2097,6 +2228,7 @@ async def _execute_permission_and_respond( PermissionDecisionRequest( request_id=request_id, result=result, + decision_context=decision_context, ) ) log_timing( @@ -2108,6 +2240,10 @@ async def _execute_permission_and_respond( request_id=request_id, ) except Exception: + logger.exception( + "Permission handler or response delivery failed", + extra={"session_id": self.session_id, "request_id": request_id}, + ) try: await self.rpc.permissions.handle_pending_permission_request( PermissionDecisionRequest( @@ -2502,7 +2638,13 @@ async def _handle_permission_request( try: handler_start = time.perf_counter() - result = handler(request, {"session_id": self.session_id}) + result = handler( + request, + { + "session_id": self.session_id, + "managed_settings_enabled": self._managed_settings_enabled, + }, + ) if inspect.isawaitable(result): result = await result log_timing( @@ -2512,11 +2654,14 @@ async def _handle_permission_request( handler_start, session_id=self.session_id, ) - return cast(PermissionRequestResult, result) + result = cast(PermissionRequestResult, result) + if isinstance(result, PermissionNoResult): + return PermissionDecisionUserNotAvailable() + return result except Exception: # pylint: disable=broad-except # Handler failed, deny permission. - logger.debug( - "Error handling permission request", + logger.error( + "Permission handler failed", extra={"session_id": self.session_id}, exc_info=True, ) @@ -2718,9 +2863,11 @@ async def _handle_hooks_invoke(self, hook_type: str, input_data: Any) -> Any: "postToolUse": hooks.get("on_post_tool_use"), "postToolUseFailure": hooks.get("on_post_tool_use_failure"), "userPromptSubmitted": hooks.get("on_user_prompt_submitted"), + "userPromptTransformed": hooks.get("on_user_prompt_transformed"), "sessionStart": hooks.get("on_session_start"), "sessionEnd": hooks.get("on_session_end"), "errorOccurred": hooks.get("on_error_occurred"), + "agentStop": hooks.get("on_agent_stop"), } handler = handler_map.get(hook_type) @@ -2737,6 +2884,8 @@ async def _handle_hooks_invoke(self, hook_type: str, input_data: Any) -> Any: transformed: dict[str, Any] = dict(input_data) if "cwd" in transformed: transformed["workingDirectory"] = transformed.pop("cwd") + if "stop_hook_active" in transformed: + transformed["stopHookActive"] = transformed.pop("stop_hook_active") timestamp = transformed.get("timestamp") if isinstance(timestamp, (int, float)): transformed["timestamp"] = datetime.fromtimestamp(timestamp / 1000, tz=UTC) @@ -2897,7 +3046,7 @@ async def set_model( Args: model: Model ID to switch to (e.g., "gpt-5.4", "claude-sonnet-4"). reasoning_effort: Optional reasoning effort level for the new model - (e.g., "low", "medium", "high", "xhigh"). + (e.g., "low", "medium", "high", "xhigh", "max"). reasoning_summary: Optional reasoning summary mode for supported models. Use "none" to suppress summary output regardless of whether reasoning is enabled. diff --git a/python/copilot/session_fs_provider.py b/python/copilot/session_fs_provider.py index 355724da4..c9e90a644 100644 --- a/python/copilot/session_fs_provider.py +++ b/python/copilot/session_fs_provider.py @@ -34,11 +34,19 @@ SessionFSReadFileResult, SessionFSSqliteExistsResult, SessionFSSqliteQueryType, + SessionFSSqliteTransactionErrorClass, + SessionFSSqliteTransactionStatement, SessionFSStatResult, ) from .generated.rpc import ( SessionFSSqliteQueryResult as _GeneratedSqliteQueryResult, ) +from .generated.rpc import ( + SessionFSSqliteTransactionError as _GeneratedSqliteTransactionError, +) +from .generated.rpc import ( + SessionFSSqliteTransactionResult as _GeneratedSqliteTransactionResult, +) @dataclass @@ -130,11 +138,45 @@ async def sqlite_query( no result set is produced; the adapter will substitute an empty result. """ + async def sqlite_transaction( + self, + statements: list[SessionFSSqliteTransactionStatement], + ) -> list[SessionFsSqliteQueryResult]: + """Execute ``statements`` atomically against the per-session database. + + Return one result per statement, in order. Raise + :class:`SessionFsSqliteTransactionFailure` to tell the runtime how the + failure should be classified; any other exception is reported as + ``fatal``. + """ + raise SessionFsSqliteTransactionFailure( + "SQLite transactions are not supported by this SessionFs provider", + SessionFSSqliteTransactionErrorClass.FATAL, + ) + @abc.abstractmethod async def sqlite_exists(self) -> bool: """Return whether the provider has a SQLite database for this session.""" +class SessionFsSqliteTransactionFailure(Exception): + """Raised by a provider to classify a failed SQLite transaction. + + ``busy_or_locked`` guarantees the transaction rolled back and is safe to + retry; ``post_commit_ambiguous`` must never be retried. + """ + + def __init__( + self, + message: str, + error_class: SessionFSSqliteTransactionErrorClass = ( + SessionFSSqliteTransactionErrorClass.FATAL + ), + ) -> None: + super().__init__(message) + self.error_class = error_class + + @dataclass class SessionFsSqliteQueryResult: """Result of a SQLite query execution. @@ -294,6 +336,45 @@ async def sqlite_query(self, params: Any) -> _GeneratedSqliteQueryResult: last_insert_rowid=result.last_insert_rowid, ) + async def sqlite_transaction(self, params: Any) -> _GeneratedSqliteTransactionResult: + if not isinstance(self._p, SessionFsSqliteProvider): + return _GeneratedSqliteTransactionResult( + results=[], + error=_GeneratedSqliteTransactionError( + error_class=SessionFSSqliteTransactionErrorClass.FATAL, + message="SQLite is not supported by this SessionFs provider", + ), + ) + try: + results = await self._p.sqlite_transaction(list(params.statements)) + except SessionFsSqliteTransactionFailure as exc: + return _GeneratedSqliteTransactionResult( + results=[], + error=_GeneratedSqliteTransactionError( + error_class=exc.error_class, + message=str(exc), + ), + ) + except Exception as exc: + return _GeneratedSqliteTransactionResult( + results=[], + error=_GeneratedSqliteTransactionError( + error_class=SessionFSSqliteTransactionErrorClass.FATAL, + message=str(exc), + ), + ) + return _GeneratedSqliteTransactionResult( + results=[ + _GeneratedSqliteQueryResult( + columns=result.columns, + rows=result.rows, + rows_affected=result.rows_affected, + last_insert_rowid=result.last_insert_rowid, + ) + for result in results + ], + ) + async def sqlite_exists(self, params: Any) -> SessionFSSqliteExistsResult: if not isinstance(self._p, SessionFsSqliteProvider): return SessionFSSqliteExistsResult.from_dict({"exists": False}) diff --git a/python/copilot/tools.py b/python/copilot/tools.py index b96e303e3..dc709cf7d 100644 --- a/python/copilot/tools.py +++ b/python/copilot/tools.py @@ -18,6 +18,12 @@ if TYPE_CHECKING: from .generated.rpc import CurrentToolMetadata +from .generated.rpc import ( + ExternalToolTextResultForLlm, + ExternalToolTextResultForLlmBinaryResultsForLlm, + ExternalToolTextResultForLlmBinaryResultsForLlmType, +) + ToolResultType = Literal["success", "failure", "rejected", "denied", "timeout"] @@ -76,6 +82,11 @@ class Tool: skip_permission: bool = False defer: Literal["auto", "never"] | None = None metadata: dict[str, Any] | None = None + #: When true, a successful call to this tool ends the agent turn: the + #: runtime halts instead of feeding the result back to the model for + #: another round. A failed call leaves the loop running so the model can + #: read the error and retry. + is_terminal: bool = False T = TypeVar("T", bound=BaseModel) @@ -91,6 +102,7 @@ def define_tool( skip_permission: bool = False, defer: Literal["auto", "never"] | None = None, metadata: dict[str, Any] | None = None, + is_terminal: bool = False, ) -> Callable[[Callable[..., Any]], Tool]: pass @@ -106,6 +118,7 @@ def define_tool( skip_permission: bool = False, defer: Literal["auto", "never"] | None = None, metadata: dict[str, Any] | None = None, + is_terminal: bool = False, ) -> Tool: pass @@ -121,6 +134,7 @@ def define_tool( skip_permission: bool = False, defer: Literal["auto", "never"] | None = None, metadata: dict[str, Any] | None = None, + is_terminal: bool = False, ) -> Tool: pass @@ -135,6 +149,7 @@ def define_tool( skip_permission: bool = False, defer: Literal["auto", "never"] | None = None, metadata: dict[str, Any] | None = None, + is_terminal: bool = False, ) -> Tool | Callable[[Callable[[Any, ToolInvocation], Any]], Tool]: """ Define a tool with automatic JSON schema generation from Pydantic models. @@ -187,6 +202,10 @@ def lookup_issue(params: LookupIssueParams) -> str: Keys are namespaced and not part of the stable public API; values are not interpreted and may be recognized to inform host-specific behavior. Unknown keys are preserved. + is_terminal: When True, a successful call to this tool ends the agent turn: + the runtime halts instead of feeding the result back to the model + for another round. A failed call leaves the loop running so the + model can read the error and retry. Returns: A Tool instance @@ -282,6 +301,7 @@ async def wrapped_handler(invocation: ToolInvocation) -> ToolResult: skip_permission=skip_permission, defer=defer, metadata=metadata, + is_terminal=is_terminal, ) # If handler is provided, call decorator immediately @@ -302,6 +322,7 @@ async def wrapped_handler(invocation: ToolInvocation) -> ToolResult: skip_permission=skip_permission, defer=defer, metadata=metadata, + is_terminal=is_terminal, ) # Otherwise return decorator for @define_tool(...) usage @@ -345,7 +366,7 @@ def _normalize_result(result: Any) -> ToolResult: # Everything else gets JSON-serialized (with Pydantic model support) def default(obj: Any) -> Any: if isinstance(obj, BaseModel): - return obj.model_dump() + return obj.model_dump(mode="json") raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable") try: @@ -408,3 +429,30 @@ def convert_mcp_call_tool_result(call_result: dict[str, Any]) -> ToolResult: result_type="failure" if call_result.get("isError") is True else "success", binary_results_for_llm=binary_results if binary_results else None, ) + + +def tool_result_to_external_tool_text_result_for_llm( + tool_result: ToolResult, +) -> ExternalToolTextResultForLlm: + """Convert a ToolResult into the RPC payload sent to HandlePendingToolCall.""" + binary_results_for_llm = None + if tool_result.binary_results_for_llm: + binary_results_for_llm = [ + ExternalToolTextResultForLlmBinaryResultsForLlm( + data=binary_result.data, + mime_type=binary_result.mime_type, + type=ExternalToolTextResultForLlmBinaryResultsForLlmType(binary_result.type), + description=binary_result.description or None, + ) + for binary_result in tool_result.binary_results_for_llm + ] + + return ExternalToolTextResultForLlm( + text_result_for_llm=tool_result.text_result_for_llm, + binary_results_for_llm=binary_results_for_llm, + error=tool_result.error, + result_type=tool_result.result_type, + session_log=tool_result.session_log, + tool_references=tool_result.tool_references, + tool_telemetry=tool_result.tool_telemetry, + ) diff --git a/python/e2e/test_client_options_e2e.py b/python/e2e/test_client_options_e2e.py index bca4dbb72..fe1ed5482 100644 --- a/python/e2e/test_client_options_e2e.py +++ b/python/e2e/test_client_options_e2e.py @@ -164,6 +164,10 @@ def _get_available_port() -> int: }); return; } + if (message.method === "session.options.update") { + writeResponse(message.id, { success: true }); + return; + } writeResponse(message.id, {}); } @@ -284,7 +288,9 @@ async def test_should_propagate_process_options_to_spawned_cli(self, ctx: E2ETes enable_config_discovery=True, enable_on_demand_instruction_discovery=True, include_sub_agent_streaming_events=False, + custom_agents_local_only=False, ) + session_id = session.session_id try: with open(capture_path) as f: capture = json.load(f) @@ -295,8 +301,72 @@ async def test_should_propagate_process_options_to_spawned_cli(self, ctx: E2ETes assert params["enableConfigDiscovery"] is True assert params["enableOnDemandInstructionDiscovery"] is True assert params["includeSubAgentStreamingEvents"] is False + assert params["customAgentsLocalOnly"] is False finally: await session.disconnect() + + resumed = await client.resume_session( + session_id, + on_permission_request=PermissionHandler.approve_all, + custom_agents_local_only=False, + ) + try: + with open(capture_path) as f: + capture = json.load(f) + resume_request = next( + r for r in capture["requests"] if r["method"] == "session.resume" + ) + assert resume_request["params"]["customAgentsLocalOnly"] is False + finally: + await resumed.disconnect() + finally: + try: + await client.stop() + except Exception: + await client.force_stop() + + async def test_should_send_empty_mode_custom_agent_locality_defaults(self, ctx: E2ETestContext): + cli_path = os.path.join(ctx.work_dir, "fake-cli-empty.js") + capture_path = os.path.join(ctx.work_dir, "fake-cli-empty-capture.json") + with open(cli_path, "w") as f: + f.write(FAKE_STDIO_CLI_SCRIPT) + + client = CopilotClient( + **_make_options( + ctx, + cli_path=cli_path, + cli_args=["--capture-file", capture_path], + mode="empty", + base_directory=ctx.work_dir, + use_logged_in_user=False, + ), + ) + try: + session = await client.create_session( + available_tools=["builtin:ask_user"], + on_permission_request=PermissionHandler.approve_all, + ) + session_id = session.session_id + await session.disconnect() + + resumed = await client.resume_session( + session_id, + available_tools=["builtin:ask_user"], + on_permission_request=PermissionHandler.approve_all, + ) + try: + with open(capture_path) as f: + capture = json.load(f) + create_request = next( + r for r in capture["requests"] if r["method"] == "session.create" + ) + resume_request = next( + r for r in capture["requests"] if r["method"] == "session.resume" + ) + assert create_request["params"]["customAgentsLocalOnly"] is True + assert resume_request["params"]["customAgentsLocalOnly"] is True + finally: + await resumed.disconnect() finally: try: await client.stop() diff --git a/python/e2e/test_hooks_extended_e2e.py b/python/e2e/test_hooks_extended_e2e.py index 841c14c77..7af20f32b 100644 --- a/python/e2e/test_hooks_extended_e2e.py +++ b/python/e2e/test_hooks_extended_e2e.py @@ -3,8 +3,9 @@ E2E coverage for every handler exposed on ``SessionHooks``: ``on_pre_tool_use``, ``on_post_tool_use``, ``on_post_tool_use_failure``, -``on_user_prompt_submitted``, ``on_session_start``, ``on_session_end``, -``on_error_occurred``. Output-shape behavior (modifiedPrompt / +``on_user_prompt_submitted``, ``on_user_prompt_transformed``, ``on_session_start``, +``on_session_end``, +``on_error_occurred``, ``on_agent_stop``. Output-shape behavior (modifiedPrompt / additionalContext / errorHandling / modifiedArgs / modifiedResult / sessionSummary) is asserted alongside hook invocation. """ @@ -48,6 +49,32 @@ async def on_user_prompt_submitted(input_data, invocation): finally: await session.disconnect() + async def test_should_invoke_userprompttransformed_hook_and_modify_transformed_prompt( + self, ctx: E2ETestContext + ): + inputs: list[dict] = [] + + async def on_user_prompt_transformed(input_data, invocation): + assert invocation["session_id"] + inputs.append(input_data) + return {"modifiedTransformedPrompt": "Reply with exactly: HOOKED_TRANSFORMED_PROMPT"} + + session = await ctx.client.create_session( + on_permission_request=PermissionHandler.approve_all, + hooks={"on_user_prompt_transformed": on_user_prompt_transformed}, + ) + try: + response = await session.send_and_wait("Answer the request above.") + assert inputs + assert "Answer the request above." in inputs[0]["prompt"] + assert "Answer the request above." in inputs[0]["transformedPrompt"] + assert "" in inputs[0]["transformedPrompt"] + assert inputs[0]["timestamp"].timestamp() > 0 + assert inputs[0]["workingDirectory"] + assert "HOOKED_TRANSFORMED_PROMPT" in (response.data.content or "") + finally: + await session.disconnect() + async def test_should_invoke_sessionstart_hook(self, ctx: E2ETestContext): inputs: list[dict] = [] invocation_session_ids: list[str] = [] @@ -114,6 +141,34 @@ async def on_error_occurred(input_data, invocation): finally: await session.disconnect() + async def test_should_invoke_agentstop_hook_and_apply_block_response(self, ctx: E2ETestContext): + inputs: list[dict] = [] + + async def on_agent_stop(input_data, invocation): + assert invocation["session_id"] == session.session_id + inputs.append(input_data) + if len(inputs) == 1: + return { + "decision": "block", + "reason": "Reply with exactly: AGENT_STOP_CONTINUED", + } + return None + + session = await ctx.client.create_session( + on_permission_request=PermissionHandler.approve_all, + hooks={"on_agent_stop": on_agent_stop}, + ) + try: + response = await session.send_and_wait("Reply with exactly: AGENT_STOP_INITIAL") + assert len(inputs) == 2 + assert inputs[0].get("stopHookActive") is not True + assert inputs[1].get("stopHookActive") is True + assert inputs[0].get("stopReason") == "end_turn" + assert inputs[0].get("transcriptPath") + assert "AGENT_STOP_CONTINUED" in (response.data.content or "") + finally: + await session.disconnect() + async def test_should_allow_pretooluse_to_return_modifiedargs_and_suppressoutput( self, ctx: E2ETestContext ): diff --git a/python/e2e/test_rewind_e2e.py b/python/e2e/test_rewind_e2e.py new file mode 100644 index 000000000..e3db37915 --- /dev/null +++ b/python/e2e/test_rewind_e2e.py @@ -0,0 +1,88 @@ +"""E2E coverage for rewinding tracked files and conversation history.""" + +from __future__ import annotations + +import asyncio +import os +from pathlib import Path + +import pytest + +from copilot.rpc import ( + HistoryPreviewRewindRequest, + HistoryRewindMode, + HistoryRewindOutcome, + HistoryRewindRequest, +) +from copilot.session import PermissionHandler + +from .testharness import E2ETestContext + +pytestmark = pytest.mark.asyncio(loop_scope="module") + +FILE_NAME = "rewind-sdk.txt" +FILE_CONTENT = "SDK rewind content" + + +def _same_path(left: str | Path, right: str | Path) -> bool: + return os.path.normcase(os.path.abspath(left)) == os.path.normcase(os.path.abspath(right)) + + +class TestRewind: + async def test_should_restore_tracked_file_and_conversation(self, ctx: E2ETestContext): + file_path = Path(ctx.work_dir) / FILE_NAME + session = await ctx.client.create_session( + model="claude-sonnet-4.5", + enable_file_change_tracking=True, + on_permission_request=PermissionHandler.approve_all, + ) + + try: + response = await session.send_and_wait( + f"Use the create tool to create {FILE_NAME} containing exactly {FILE_CONTENT}. " + "After the tool succeeds, reply with exactly SDK_REWIND_DONE." + ) + + assert response is not None + assert response.data.content == "SDK_REWIND_DONE" + assert file_path.read_text(encoding="utf-8") == FILE_CONTENT + + rewind_points = await session.rpc.history.list_rewind_points() + deadline = asyncio.get_running_loop().time() + 10 + while ( + rewind_points.unavailable_reason is not None + and asyncio.get_running_loop().time() < deadline + ): + await asyncio.sleep(0.1) + rewind_points = await session.rpc.history.list_rewind_points() + + assert rewind_points.unavailable_reason is None + assert rewind_points.file_change_tracking_enabled + assert len(rewind_points.points) == 1 + rewind_point = rewind_points.points[0] + assert rewind_point.can_restore_files + assert rewind_point.file_count == 1 + + preview = await session.rpc.history.preview_rewind( + HistoryPreviewRewindRequest(event_id=rewind_point.event_id) + ) + assert preview.available + assert len(preview.files) == 1 + assert _same_path(preview.files[0].path, file_path) + + rewind = await session.rpc.history.rewind( + HistoryRewindRequest( + event_id=rewind_point.event_id, + mode=HistoryRewindMode.CONVERSATION_AND_FILES, + ) + ) + assert rewind.outcome == HistoryRewindOutcome.SUCCESS + assert rewind.events_removed is not None and rewind.events_removed > 0 + assert len(rewind.restored_files) == 1 + assert _same_path(rewind.restored_files[0], file_path) + assert not file_path.exists() + + events = await session.get_events() + assert all(str(event.id) != rewind_point.event_id for event in events) + finally: + await session.disconnect() diff --git a/python/e2e/test_rpc_commands_e2e.py b/python/e2e/test_rpc_commands_e2e.py index 747eb4d1e..32fbc5b18 100644 --- a/python/e2e/test_rpc_commands_e2e.py +++ b/python/e2e/test_rpc_commands_e2e.py @@ -6,10 +6,10 @@ from copilot.rpc import ( CommandsInvokeRequest, - CommandsListRequest, CommandsRespondToQueuedCommandRequest, ExecuteCommandParams, QueuedCommandHandled, + SessionCommandsListRequest, SlashCommandKind, SlashCommandTextResult, ) @@ -33,7 +33,7 @@ async def test_should_list_builtin_and_client_commands(self, ctx: E2ETestContext ], ) try: - commands = await session.rpc.commands.list(CommandsListRequest()) + commands = await session.rpc.commands.list(SessionCommandsListRequest()) by_name = {command.name: command for command in commands.commands} builtins = [ diff --git a/python/e2e/test_rpc_server_e2e.py b/python/e2e/test_rpc_server_e2e.py index bcef26754..e7c4a446c 100644 --- a/python/e2e/test_rpc_server_e2e.py +++ b/python/e2e/test_rpc_server_e2e.py @@ -55,7 +55,7 @@ ) from copilot.session import PermissionHandler -from .testharness import E2ETestContext +from .testharness import E2ETestContext, wait_for_condition pytestmark = pytest.mark.asyncio(loop_scope="module") @@ -282,30 +282,63 @@ async def test_should_add_secret_filter_values(self, ctx: E2ETestContext): # error from anyio. We don't want it to fail the test. pass - async def test_should_list_find_and_inspect_persisted_session_state(self, ctx: E2ETestContext): + async def test_should_list_find_and_inspect_persisted_session_state( + self, authed_ctx: E2ETestContext + ): + token = os.environ.get("GITHUB_TOKEN", "fakevalue") + await _configure_user(authed_ctx, token) + client = _make_authed_client(authed_ctx, token) + session_id = str(uuid.uuid4()) - working_directory = Path(ctx.work_dir) / f"server-rpc-list-{uuid.uuid4().hex}" + working_directory = Path(authed_ctx.work_dir) / f"server-rpc-list-{uuid.uuid4().hex}" working_directory.mkdir(parents=True, exist_ok=True) missing_task_id = f"missing-task-{uuid.uuid4().hex}" missing_session_id = str(uuid.uuid4()) - - session = await ctx.client.create_session( - session_id=session_id, - working_directory=str(working_directory), - on_permission_request=PermissionHandler.approve_all, - ) + session = None try: - await session.log("SERVER_RPC_LIST_READY") - save = await ctx.client.rpc.sessions.save(SessionsSaveRequest(session_id=session_id)) - assert save is not None - - listed = await ctx.client.rpc.sessions.list( - SessionsListRequest( - filter=SessionListFilter(cwd=str(working_directory)), - metadata_limit=0, + await client.start() + session = await client.create_session( + session_id=session_id, + working_directory=str(working_directory), + on_permission_request=PermissionHandler.approve_all, + ) + + await session.send( + "Record a turn for sessions.list discriminator coverage", mode="enqueue" + ) + + listed = None + + async def session_is_listed() -> bool: + nonlocal listed + # Re-save on every attempt: on slower runners the enqueued turn is not + # necessarily recorded yet when the first save runs, so a single save + # followed by a fixed sleep races the CLI's own persistence. + save = await client.rpc.sessions.save(SessionsSaveRequest(session_id=session_id)) + assert save is not None + listed = await client.rpc.sessions.list( + SessionsListRequest( + filter=SessionListFilter(cwd=str(working_directory)), + metadata_limit=0, + ) ) + return any(item.session_id == session_id for item in listed.sessions or []) + + await wait_for_condition( + session_is_listed, + timeout=60.0, + timeout_message=( + "Timed out waiting for the saved session to be returned by sessions.list." + ), ) + + assert listed is not None assert listed.sessions is not None + assert len(listed.sessions) >= 1 + matching = [item for item in listed.sessions if item.session_id == session_id] + assert len(matching) == 1 + assert isinstance(matching[0], LocalSessionMetadataValue) + assert matching[0].is_remote is False assert all( item.context is None or os.path.normcase(os.path.abspath(item.context.cwd)) @@ -313,32 +346,40 @@ async def test_should_list_find_and_inspect_persisted_session_state(self, ctx: E for item in listed.sessions ) - by_prefix = await ctx.client.rpc.sessions.find_by_prefix( + by_prefix = await client.rpc.sessions.find_by_prefix( SessionsFindByPrefixRequest(prefix=session_id[:8]) ) assert by_prefix.session_id in (None, session_id) - by_task = await ctx.client.rpc.sessions.find_by_task_id( + by_task = await client.rpc.sessions.find_by_task_id( SessionsFindByTaskIDRequest(task_id=missing_task_id) ) assert by_task.session_id is None - last_for_context = await ctx.client.rpc.sessions.get_last_for_context( + last_for_context = await client.rpc.sessions.get_last_for_context( SessionsGetLastForContextRequest(context=SessionContext(cwd=str(working_directory))) ) assert last_for_context.session_id in (None, session_id) - sizes = await ctx.client.rpc.sessions.get_sizes() + sizes = await client.rpc.sessions.get_sizes() assert sizes.sizes is not None if session_id in sizes.sizes: assert sizes.sizes[session_id] >= 0 - in_use = await ctx.client.rpc.sessions.check_in_use( + in_use = await client.rpc.sessions.check_in_use( SessionsCheckInUseRequest(session_ids=[session_id, missing_session_id]) ) assert missing_session_id not in in_use.in_use finally: - await session.disconnect() + if session is not None: + await session.disconnect() + try: + await client.stop() + except ExceptionGroup: + # Intentional: shutting down the per-test client can race the + # CLI's own teardown and surface as an aggregated cancellation + # error from anyio. We don't want it to fail the test. + pass async def test_should_enrich_basic_session_metadata(self, ctx: E2ETestContext): session_id = str(uuid.uuid4()) diff --git a/python/e2e/test_rpc_session_state_e2e.py b/python/e2e/test_rpc_session_state_e2e.py index 51c1059bb..f4b03d2e6 100644 --- a/python/e2e/test_rpc_session_state_e2e.py +++ b/python/e2e/test_rpc_session_state_e2e.py @@ -33,6 +33,7 @@ ModeSetRequest, NameSetAutoRequest, NameSetRequest, + PermissionsResetSessionApprovalsRequest, PermissionsSetApproveAllRequest, PlanUpdateRequest, SessionSetCredentialsParams, @@ -594,7 +595,9 @@ async def test_should_call_session_usage_and_permission_rpcs(self, ctx: E2ETestC ) assert approve_all.success - reset = await session.rpc.permissions.reset_session_approvals() + reset = await session.rpc.permissions.reset_session_approvals( + PermissionsResetSessionApprovalsRequest() + ) assert reset.success finally: await session.rpc.permissions.set_approve_all( diff --git a/python/e2e/test_rpc_tasks_and_handlers_e2e.py b/python/e2e/test_rpc_tasks_and_handlers_e2e.py index 6a99cbb75..f0dd8f757 100644 --- a/python/e2e/test_rpc_tasks_and_handlers_e2e.py +++ b/python/e2e/test_rpc_tasks_and_handlers_e2e.py @@ -445,20 +445,28 @@ def on_event(event): 60.0, f"Task {task_id} did not produce a final observable state", ) - assert found_task is not None, f"Task {task_id} disappeared before it completed" - assert "TASK_AGENT_DONE" in (found_task.latest_response or found_task.result or "") - await asyncio.wait_for(task_completion_notification, timeout=30.0) - - if found_task.status == TaskInfoStatus.IDLE: - cancel = await session.rpc.tasks.cancel(TasksCancelRequest(id=task_id)) - assert cancel.cancelled is True - - # Remove the task - remove = await session.rpc.tasks.remove(TasksRemoveRequest(id=task_id)) - assert remove.removed is True + if found_task is not None: + assert "TASK_AGENT_DONE" in (found_task.latest_response or found_task.result or "") + + if found_task.status == TaskInfoStatus.IDLE: + cancel = await session.rpc.tasks.cancel(TasksCancelRequest(id=task_id)) + assert cancel.cancelled is True + + remove = await session.rpc.tasks.remove(TasksRemoveRequest(id=task_id)) + # Completion delivery also removes finished tasks, so this call may lose that race. + assert remove.removed or task_completion_notification.done(), ( + f"Task {task_id} was not removed before its completion " + "notification was delivered" + ) after_remove = await session.rpc.tasks.list() - assert not any(t.id == task_id for t in (after_remove.tasks or [])) + task_after_remove = next( + (task for task in (after_remove.tasks or []) if task.id == task_id), + None, + ) + assert task_after_remove is None + + await asyncio.wait_for(task_completion_notification, timeout=30.0) finally: unsubscribe() await session.disconnect() diff --git a/python/e2e/test_session_e2e.py b/python/e2e/test_session_e2e.py index aed1340f5..b6f173f75 100644 --- a/python/e2e/test_session_e2e.py +++ b/python/e2e/test_session_e2e.py @@ -679,27 +679,36 @@ async def test_should_set_model_with_reasoning_effort(self, ctx: E2ETestContext) """Test that setModel passes reasoningEffort and it appears in the model_change event.""" import asyncio - session = await ctx.client.create_session( - on_permission_request=PermissionHandler.approve_all - ) + isolated_ctx = E2ETestContext() + await isolated_ctx.setup() + try: + await isolated_ctx.configure_for_test( + "session", "should_set_model_with_reasoningeffort" + ) + session = await isolated_ctx.client.create_session( + on_permission_request=PermissionHandler.approve_all + ) - model_change_event = asyncio.get_event_loop().create_future() + model_change_event = asyncio.get_event_loop().create_future() - def on_event(event): - if model_change_event.done(): - return + def on_event(event): + if model_change_event.done(): + return - match event.data: - case SessionModelChangeData() as data: - model_change_event.set_result(data) + match event.data: + case SessionModelChangeData() as data: + model_change_event.set_result(data) - session.on(on_event) + session.on(on_event) - await session.set_model("gpt-4.1", reasoning_effort="high") + await session.set_model("gpt-5.4", reasoning_effort="high") - data = await asyncio.wait_for(model_change_event, timeout=30) - assert data.new_model == "gpt-4.1" - assert data.reasoning_effort == "high" + data = await asyncio.wait_for(model_change_event, timeout=30) + assert data.new_model == "gpt-5.4" + assert data.reasoning_effort == "high" + await session.disconnect() + finally: + await isolated_ctx.teardown() async def test_should_accept_blob_attachments(self, ctx: E2ETestContext): # Write the image to disk so the model can view it diff --git a/python/e2e/test_session_fs_e2e.py b/python/e2e/test_session_fs_e2e.py index fbfde6ee7..bb6e47af7 100644 --- a/python/e2e/test_session_fs_e2e.py +++ b/python/e2e/test_session_fs_e2e.py @@ -280,6 +280,8 @@ async def test_should_map_all_sessionfs_handler_operations(self, ctx: E2ETestCon SessionFSSqliteExistsRequest, SessionFSSqliteQueryRequest, SessionFSSqliteQueryType, + SessionFSSqliteTransactionErrorClass, + SessionFSSqliteTransactionRequest, SessionFSStatRequest, SessionFSWriteFileRequest, ) @@ -403,6 +405,15 @@ async def test_should_map_all_sessionfs_handler_operations(self, ctx: E2ETestCon assert sqlite_query.error is not None assert sqlite_query.error.code == SessionFSErrorCode.UNKNOWN + sqlite_transaction = await handler.sqlite_transaction( + SessionFSSqliteTransactionRequest(session_id=session_id, statements=[]) + ) + assert sqlite_transaction.results == [] + assert sqlite_transaction.error is not None + assert ( + sqlite_transaction.error.error_class == SessionFSSqliteTransactionErrorClass.FATAL + ) + sqlite_exists = await handler.sqlite_exists( SessionFSSqliteExistsRequest(session_id=session_id) ) @@ -429,6 +440,8 @@ async def test_sessionfsprovider_converts_exceptions_to_rpc_errors(self): SessionFSSqliteExistsRequest, SessionFSSqliteQueryRequest, SessionFSSqliteQueryType, + SessionFSSqliteTransactionErrorClass, + SessionFSSqliteTransactionRequest, SessionFSStatRequest, SessionFSWriteFileRequest, ) @@ -536,6 +549,12 @@ def assert_fs_error(error) -> None: assert sqlite_query.columns == [] assert sqlite_query.rows == [] assert sqlite_query.rows_affected == 0 + sqlite_transaction = await handler.sqlite_transaction( + SessionFSSqliteTransactionRequest(session_id=sid, statements=[]) + ) + assert sqlite_transaction.results == [] + assert sqlite_transaction.error is not None + assert sqlite_transaction.error.error_class == SessionFSSqliteTransactionErrorClass.FATAL sqlite_exists = await handler.sqlite_exists(SessionFSSqliteExistsRequest(session_id=sid)) assert sqlite_exists.exists is False @@ -624,7 +643,8 @@ def create_handler(session): def provider_path(provider_root: Path, session_id: str, path: str) -> Path: - return provider_root / session_id / path.lstrip("/") + relative_path = path.replace("\\", "/").lstrip("/") + return provider_root / session_id / relative_path def find_tool_call_result(messages: list[SessionEvent], tool_name: str) -> str | None: diff --git a/python/e2e/test_session_fs_sqlite_e2e.py b/python/e2e/test_session_fs_sqlite_e2e.py index c82cc793f..f48bcd2cd 100644 --- a/python/e2e/test_session_fs_sqlite_e2e.py +++ b/python/e2e/test_session_fs_sqlite_e2e.py @@ -8,6 +8,7 @@ import sqlite3 import tempfile from pathlib import Path +from typing import Any import pytest import pytest_asyncio @@ -17,6 +18,8 @@ SessionFSReaddirWithTypesEntry, SessionFSReaddirWithTypesEntryType, SessionFSSqliteQueryType, + SessionFSSqliteTransactionErrorClass, + SessionFSSqliteTransactionStatement, ) from copilot.session import PermissionHandler from copilot.session_fs_provider import ( @@ -24,6 +27,7 @@ SessionFsProvider, SessionFsSqliteProvider, SessionFsSqliteQueryResult, + SessionFsSqliteTransactionFailure, ) from .testharness import DEFAULT_GITHUB_TOKEN, E2ETestContext @@ -151,6 +155,46 @@ async def sqlite_query( query: str, params: dict[str, float | str | None] | None = None, ) -> SessionFsSqliteQueryResult | None: + return self._run_statement(self._get_or_create_db(), query_type, query, params) + + async def sqlite_transaction( + self, + statements: list[SessionFSSqliteTransactionStatement], + ) -> list[SessionFsSqliteQueryResult]: + db = self._get_or_create_db() + db.execute("BEGIN IMMEDIATE") + try: + results = [ + self._run_statement( + db, statement.query_type, statement.query, statement.params, commit=False + ) + for statement in statements + ] + except Exception as exc: + db.rollback() + message = str(exc) + error_class = ( + SessionFSSqliteTransactionErrorClass.BUSY_OR_LOCKED + if "locked" in message or "busy" in message + else SessionFSSqliteTransactionErrorClass.FATAL + ) + raise SessionFsSqliteTransactionFailure(message, error_class) from exc + try: + db.commit() + except Exception as exc: + raise SessionFsSqliteTransactionFailure( + str(exc), SessionFSSqliteTransactionErrorClass.POST_COMMIT_AMBIGUOUS + ) from exc + return results + + def _run_statement( + self, + db: sqlite3.Connection, + query_type: SessionFSSqliteQueryType, + query: str, + params: dict[str, Any] | None = None, + commit: bool = True, + ) -> SessionFsSqliteQueryResult: self._sqlite_calls.append( { "sessionId": self._session_id, @@ -159,14 +203,16 @@ async def sqlite_query( } ) - db = self._get_or_create_db() trimmed = query.strip() if not trimmed: return SessionFsSqliteQueryResult(columns=[], rows=[], rows_affected=0) if query_type == SessionFSSqliteQueryType.EXEC: - db.executescript(trimmed) - db.commit() + if commit: + db.executescript(trimmed) + db.commit() + else: + db.execute(trimmed) return SessionFsSqliteQueryResult(columns=[], rows=[], rows_affected=0) if query_type == SessionFSSqliteQueryType.QUERY: @@ -177,7 +223,8 @@ async def sqlite_query( # run (INSERT/UPDATE/DELETE) cursor = db.execute(trimmed, params or {}) - db.commit() + if commit: + db.commit() return SessionFsSqliteQueryResult( columns=[], rows=[], diff --git a/python/e2e/test_streaming_fidelity_e2e.py b/python/e2e/test_streaming_fidelity_e2e.py index a82c7f674..a644acb83 100644 --- a/python/e2e/test_streaming_fidelity_e2e.py +++ b/python/e2e/test_streaming_fidelity_e2e.py @@ -155,34 +155,44 @@ async def test_should_not_produce_deltas_after_session_resume_with_streaming_dis finally: await new_client.force_stop() - async def test_should_emit_streaming_deltas_with_reasoning_effort_configured( - self, ctx: E2ETestContext - ): + async def test_should_emit_streaming_deltas_with_reasoning_effort_configured(self): """Streaming + reasoning_effort produces delta events and session.start shows effort.""" from copilot.session_events import SessionStartData - session = await ctx.client.create_session( - on_permission_request=PermissionHandler.approve_all, - streaming=True, - reasoning_effort="high", - ) - - events = [] - session.on(lambda event: events.append(event)) - + isolated_ctx = E2ETestContext() + await isolated_ctx.setup() try: - await session.send_and_wait("What is 15 * 17?", timeout=60.0) - - delta_events = [e for e in events if e.type.value == "assistant.message_delta"] - assert len(delta_events) >= 1, "Expected delta events with streaming=True" - - assistant_events = [e for e in events if e.type.value == "assistant.message"] - assert len(assistant_events) >= 1, "Expected final assistant.message" + await isolated_ctx.configure_for_test( + "streaming_fidelity", + "should_emit_streaming_deltas_with_reasoning_effort_configured", + ) + session = await isolated_ctx.client.create_session( + on_permission_request=PermissionHandler.approve_all, + model="gpt-5.4", + streaming=True, + reasoning_effort="high", + ) - # Check session.start event (from get_events) has reasoning_effort - all_msgs = await session.get_events() - start_event = next((e for e in all_msgs if isinstance(e.data, SessionStartData)), None) - assert start_event is not None, "Expected session.start event" - assert start_event.data.reasoning_effort == "high" + events = [] + session.on(lambda event: events.append(event)) + + try: + await session.send_and_wait("What is 15 * 17?", timeout=60.0) + + delta_events = [e for e in events if e.type.value == "assistant.message_delta"] + assert len(delta_events) >= 1, "Expected delta events with streaming=True" + + assistant_events = [e for e in events if e.type.value == "assistant.message"] + assert len(assistant_events) >= 1, "Expected final assistant.message" + + # Check session.start event (from get_events) has reasoning_effort + all_msgs = await session.get_events() + start_event = next( + (e for e in all_msgs if isinstance(e.data, SessionStartData)), None + ) + assert start_event is not None, "Expected session.start event" + assert start_event.data.reasoning_effort == "high" + finally: + await session.disconnect() finally: - await session.disconnect() + await isolated_ctx.teardown() diff --git a/python/e2e/testharness/context.py b/python/e2e/testharness/context.py index 679a36e28..2171e25f2 100644 --- a/python/e2e/testharness/context.py +++ b/python/e2e/testharness/context.py @@ -11,18 +11,66 @@ import shutil import tempfile import time +from collections.abc import Sequence from pathlib import Path from typing import Any from copilot import CopilotClient, RuntimeConnection +from copilot._cli_version import get_npm_platform from .proxy import CapiProxy +def _cli_platform_package_names(npm_platform: str | None = None) -> list[str]: + """Return candidate ``@github/copilot-*`` directory names, best match first. + + Mirrors ``getCliPlatformPackageNames()`` in ``nodejs/src/client.ts``: as of CLI + 1.0.64-1 the runnable ``index.js`` ships in a platform package such as + ``copilot-darwin-arm64``. On Linux both libc variants are listed (the detected + one first) because npm installs exactly one of them and musl probing can come up + empty in minimal containers. + """ + primary = npm_platform or get_npm_platform() + names = [f"copilot-{primary}"] + if primary.startswith("linux"): + arch = primary.rsplit("-", 1)[-1] + for variant in (f"linux-{arch}", f"linuxmusl-{arch}"): + name = f"copilot-{variant}" + if name not in names: + names.append(name) + return names + + +def _find_cli_in_node_modules(github_modules: Path, package_names: Sequence[str]) -> str | None: + """Return the resolved ``index.js`` of the first installed candidate package. + + Only exact package names are probed, so unrelated ``copilot-*`` directories + (e.g. ``copilot-language-server``) can never be mistaken for the CLI. + """ + for name in package_names: + candidate = github_modules / name / "index.js" + if candidate.exists(): + return str(candidate.resolve()) + return None + + +def _installed_cli_package_names(github_modules: Path) -> list[str]: + """Return the ``copilot-*`` directory names present, for error messages only. + + Selection never globs — that was the #2103 bug. This exists so a failure can + say what *is* installed, which is the difference between a dead-end "run npm + install" and a message that diagnoses itself on a mixed-architecture host. + """ + if not github_modules.is_dir(): + return [] + return sorted(path.name for path in github_modules.glob("copilot-*") if path.is_dir()) + + def get_cli_path_for_tests() -> str: """Get CLI path for E2E tests. - Uses COPILOT_CLI_PATH env var if set, otherwise node_modules CLI. + Uses COPILOT_CLI_PATH env var if set, otherwise the platform-specific CLI + package in the sibling nodejs directory's node_modules. """ env_path = os.environ.get("COPILOT_CLI_PATH") if env_path and Path(env_path).exists(): @@ -30,15 +78,22 @@ def get_cli_path_for_tests() -> str: # Look for CLI in sibling nodejs directory's node_modules. As of CLI 1.0.64-1 # the @github/copilot package is a thin loader; the runnable index.js ships in - # the installed platform package (e.g. @github/copilot-linux-x64). + # the installed platform package (e.g. @github/copilot-linux-x64), so pick the + # one built for this host rather than whichever sorts first (#2103). base_path = Path(__file__).parents[3] github_modules = base_path / "nodejs" / "node_modules" / "@github" - for platform_pkg in sorted(github_modules.glob("copilot-*")): - candidate = platform_pkg / "index.js" - if candidate.exists(): - return str(candidate.resolve()) - - raise RuntimeError("CLI not found for tests. Run 'npm install' in the nodejs directory.") + package_names = _cli_platform_package_names() + found = _find_cli_in_node_modules(github_modules, package_names) + if found is not None: + return found + + installed = _installed_cli_package_names(github_modules) + raise RuntimeError( + f"CLI not found for tests under {github_modules} " + f"(tried: {', '.join(package_names)}; " + f"present: {', '.join(installed) or 'none'}). " + "Run 'npm install' in the nodejs directory, or set COPILOT_CLI_PATH." + ) CLI_PATH = get_cli_path_for_tests() diff --git a/python/pyproject.toml b/python/pyproject.toml index 43c18658d..e96c587a6 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -41,11 +41,12 @@ telemetry = [ [dependency-groups] dev = [ - "ruff>=0.1.0", + "ruff==0.16.0", "ty>=0.0.2,<0.0.25", "pytest>=7.0.0", "pytest-asyncio>=0.21.0", "pytest-timeout>=2.0.0", + "pytest-xdist>=3.6.0", "websockets>=12.0", "opentelemetry-sdk>=1.0.0", ] @@ -92,3 +93,7 @@ python_files = "test_*.py" python_classes = "Test*" python_functions = "test_*" asyncio_mode = "auto" +# Bound every test so a deadlock fails fast with a stack dump instead of occupying the +# whole CI leg until GitHub's 6-hour job limit. The full suite runs in ~10 minutes, so no +# individual test legitimately approaches this. +timeout = 300 diff --git a/python/test_client.py b/python/test_client.py index d48bfaf4b..cf4bdf192 100644 --- a/python/test_client.py +++ b/python/test_client.py @@ -6,7 +6,9 @@ import asyncio import inspect +import os from datetime import UTC, datetime +from tempfile import TemporaryDirectory from unittest.mock import AsyncMock, Mock, patch import pytest @@ -27,6 +29,8 @@ CloudSessionRepository, CopilotExpAssignmentResponse, ExpConfigEntry, + ManagedSettings, + ManagedSettingsPermissions, ModelBilling, ModelCapabilities, ModelInfo, @@ -54,6 +58,50 @@ def test_inprocess_connection_has_no_child_process_options(): assert not hasattr(connection, "args") +class TestBuiltinPluginDirectories: + @staticmethod + async def _start_client(paths=None): + client = CopilotClient( + connection=RuntimeConnection.for_uri("localhost:1234"), + builtin_plugin_directories=paths, + ) + client._connect_to_server = AsyncMock() + client._verify_protocol_version = AsyncMock() + client._client = Mock() + client._client.request = AsyncMock(return_value={}) + + await client.start() + return client + + @pytest.mark.asyncio + @pytest.mark.parametrize("paths", [None, []]) + async def test_default_or_empty_does_not_call_rpc(self, paths): + client = await self._start_client(paths) + + client._client.request.assert_not_awaited() + + @pytest.mark.asyncio + async def test_configured_paths_call_rpc_once_before_start_completes(self): + paths = [ + os.path.abspath("plugins/core"), + os.path.abspath("plugins/github"), + ] + + client = await self._start_client(paths) + + client._client.request.assert_awaited_once_with( + "plugins.builtin.set", + {"paths": paths}, + ) + + def test_relative_path_is_rejected(self): + with pytest.raises(ValueError, match="builtin_plugin_directories.*absolute paths"): + CopilotClient( + connection=RuntimeConnection.for_uri("localhost:1234"), + builtin_plugin_directories=["plugins/core"], + ) + + class TestClientShutdown: @pytest.mark.asyncio async def test_stop_requests_runtime_shutdown_for_owned_process(self): @@ -162,6 +210,46 @@ async def test_resume_session_allows_none_permission_handler(self): class TestCreateSessionConfig: + @pytest.mark.asyncio + async def test_additional_directories_forwarded_on_create_and_resume(self): + client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH)) + await client.start() + try: + captured: list[tuple[str, dict]] = [] + + async def mock_request(method, params, **kwargs): + captured.append((method, params)) + if method == "session.create": + result = {"sessionId": params["sessionId"], "workspacePath": None} + callback = kwargs.get("on_response_inline") + if callback is not None: + callback(result) + return result + if method == "session.resume": + return {"sessionId": params["sessionId"], "workspacePath": None} + return {} + + client._client.request = mock_request + await client.create_session( + session_id="create-with-additional-directories", + additional_directories=["/repo/shared", "/repo/generated"], + ) + await client.resume_session( + "resume-with-additional-directories", + additional_directories=["/repo/resumed"], + ) + + create_payload = next( + params for method, params in captured if method == "session.create" + ) + resume_payload = next( + params for method, params in captured if method == "session.resume" + ) + assert create_payload["additionalDirectories"] == ["/repo/shared", "/repo/generated"] + assert resume_payload["additionalDirectories"] == ["/repo/resumed"] + finally: + await client.force_stop() + @pytest.mark.asyncio async def test_mcp_auth_handler_registers_interest_in_create_session(self): client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH)) @@ -504,6 +592,46 @@ async def mock_request(method, params, **kwargs): finally: await client.force_stop() + @pytest.mark.asyncio + async def test_create_and_resume_session_forward_github_mcp_tool_config(self): + client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH)) + await client.start() + try: + captured = {} + + async def mock_request(method, params, **kwargs): + captured[method] = params + if method in ("session.create", "session.resume"): + result = {"sessionId": params.get("sessionId") or "session-1"} + callback = kwargs.get("on_response_inline") + if callback is not None: + callback(result) + return result + return {} + + client._client.request = mock_request + config = { + "enable_all_tools": True, + "additional_toolsets": ["repos"], + "additional_tools": ["get_issue"], + "enable_insiders_mode": True, + "disable_form_deferral": True, + } + session = await client.create_session(github_mcp_tool_config=config) + await client.resume_session(session.session_id, github_mcp_tool_config=config) + + expected = { + "enableAllTools": True, + "additionalToolsets": ["repos"], + "additionalTools": ["get_issue"], + "enableInsidersMode": True, + "disableFormDeferral": True, + } + assert captured["session.create"]["githubMcpToolConfig"] == expected + assert captured["session.resume"]["githubMcpToolConfig"] == expected + finally: + await client.force_stop() + @pytest.mark.asyncio async def test_create_and_resume_session_forward_reasoning_summary(self): client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH)) @@ -537,6 +665,190 @@ async def mock_request(method, params, **kwargs): finally: await client.force_stop() + @pytest.mark.asyncio + async def test_create_and_resume_session_forward_enable_experimental_mode(self): + client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH)) + await client.start() + try: + captured = {} + + async def mock_request(method, params, **kwargs): + captured[method] = params + if method in ("session.create", "session.resume"): + result = {"sessionId": params.get("sessionId") or "session-1"} + callback = kwargs.get("on_response_inline") + if callback is not None: + callback(result) + return result + return {} + + client._client.request = mock_request + session = await client.create_session( + on_permission_request=PermissionHandler.approve_all, + enable_experimental_mode=False, + ) + await client.resume_session( + session.session_id, + on_permission_request=PermissionHandler.approve_all, + enable_experimental_mode=True, + ) + + assert captured["session.create"]["isExperimentalMode"] is False + assert captured["session.resume"]["isExperimentalMode"] is True + finally: + await client.force_stop() + + @pytest.mark.asyncio + async def test_create_and_resume_session_forward_managed_settings(self): + client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH)) + await client.start() + try: + captured = {} + + async def mock_request(method, params, **kwargs): + captured[method] = params + if method in ("session.create", "session.resume"): + result = {"sessionId": params.get("sessionId") or "session-1"} + callback = kwargs.get("on_response_inline") + if callback is not None: + callback(result) + return result + return {} + + client._client.request = mock_request + session = await client.create_session( + on_permission_request=PermissionHandler.approve_all, + enable_managed_settings=True, + managed_settings=ManagedSettings( + permissions=ManagedSettingsPermissions( + disable_bypass_permissions_mode="disable", + deny=["Shell(git push)"], + ask=["Domain(publish.example)"], + allow=["Read(**)"], + ) + ), + ) + resumed_session = await client.resume_session( + session.session_id, + on_permission_request=PermissionHandler.approve_all, + managed_settings=ManagedSettings( + permissions=ManagedSettingsPermissions(ask=["Domain(publish.example)"]) + ), + ) + + assert session._managed_settings_enabled is True + assert resumed_session._managed_settings_enabled is True + assert captured["session.create"]["enableManagedSettings"] is True + assert captured["session.create"]["managedSettings"] == { + "permissions": { + "disableBypassPermissionsMode": "disable", + "deny": ["Shell(git push)"], + "ask": ["Domain(publish.example)"], + "allow": ["Read(**)"], + } + } + assert captured["session.resume"]["managedSettings"] == { + "permissions": {"ask": ["Domain(publish.example)"]} + } + finally: + await client.force_stop() + + @pytest.mark.asyncio + async def test_create_and_resume_session_default_enable_experimental_mode_by_mode(self): + with TemporaryDirectory() as base_directory: + client = CopilotClient( + connection=RuntimeConnection.for_stdio(path=CLI_PATH), + mode="empty", + base_directory=base_directory, + ) + await client.start() + try: + captured = {} + + async def mock_request(method, params, **kwargs): + captured[method] = params + if method in ("session.create", "session.resume"): + result = {"sessionId": params.get("sessionId") or "session-1"} + callback = kwargs.get("on_response_inline") + if callback is not None: + callback(result) + return result + if method == "session.options.update": + return {"success": True} + return {} + + client._client.request = mock_request + session = await client.create_session( + on_permission_request=PermissionHandler.approve_all, + available_tools=[], + ) + await client.resume_session( + session.session_id, + on_permission_request=PermissionHandler.approve_all, + available_tools=[], + ) + + assert captured["session.create"]["isExperimentalMode"] is False + assert captured["session.resume"]["isExperimentalMode"] is False + finally: + await client.force_stop() + + async def test_managed_settings_omitted_when_not_supplied(self): + client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH)) + await client.start() + try: + captured = {} + + async def mock_request(method, params, **kwargs): + captured[method] = params + if method == "session.create": + result = {"sessionId": params.get("sessionId") or "session-1"} + callback = kwargs.get("on_response_inline") + if callback is not None: + callback(result) + return result + return {} + + client._client.request = mock_request + await client.create_session( + on_permission_request=PermissionHandler.approve_all, + ) + + assert "managedSettings" not in captured["session.create"] + finally: + await client.force_stop() + + @pytest.mark.asyncio + async def test_managed_settings_preserves_empty_arrays(self): + client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH)) + await client.start() + try: + captured = {} + + async def mock_request(method, params, **kwargs): + captured[method] = params + if method == "session.create": + result = {"sessionId": params.get("sessionId") or "session-1"} + callback = kwargs.get("on_response_inline") + if callback is not None: + callback(result) + return result + return {} + + client._client.request = mock_request + await client.create_session( + on_permission_request=PermissionHandler.approve_all, + managed_settings=ManagedSettings( + permissions=ManagedSettingsPermissions(deny=[], ask=[], allow=[]) + ), + ) + + assert captured["session.create"]["managedSettings"] == { + "permissions": {"deny": [], "ask": [], "allow": []} + } + finally: + await client.force_stop() + @pytest.mark.asyncio async def test_create_and_resume_session_forward_context_tier(self): client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH)) @@ -610,6 +922,45 @@ async def mock_request(method, params, **kwargs): finally: await client.force_stop() + @pytest.mark.asyncio + async def test_create_and_resume_session_forward_tool_is_terminal(self): + client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH)) + await client.start() + try: + captured = {} + + async def mock_request(method, params, **kwargs): + captured[method] = params + if method in ("session.create", "session.resume"): + result = {"sessionId": params.get("sessionId") or "session-1"} + callback = kwargs.get("on_response_inline") + if callback is not None: + callback(result) + return result + return {} + + client._client.request = mock_request + tool = Tool(name="my_tool", description="a tool", is_terminal=True) + plain_tool = Tool(name="plain_tool", description="a tool") + + session = await client.create_session( + on_permission_request=PermissionHandler.approve_all, + tools=[tool, plain_tool], + ) + await client.resume_session( + session.session_id, + on_permission_request=PermissionHandler.approve_all, + tools=[tool], + ) + + create_tools = captured["session.create"]["tools"] + assert create_tools[0]["isTerminal"] is True + # Omitted when left at its default. + assert "isTerminal" not in create_tools[1] + assert captured["session.resume"]["tools"][0]["isTerminal"] is True + finally: + await client.force_stop() + @pytest.mark.asyncio async def test_create_and_resume_session_forward_canvas_provider(self): client = CopilotClient(connection=RuntimeConnection.for_stdio(path=CLI_PATH)) @@ -674,6 +1025,7 @@ async def mock_request(method, params, **kwargs): session = await client.create_session( on_permission_request=PermissionHandler.approve_all, enable_citations=True, + enable_file_change_tracking=True, excluded_builtin_agents=["explore"], session_limits={"max_ai_credits": 30}, ) @@ -681,14 +1033,17 @@ async def mock_request(method, params, **kwargs): session.session_id, on_permission_request=PermissionHandler.approve_all, enable_citations=False, + enable_file_change_tracking=False, excluded_builtin_agents=["task"], session_limits={"max_ai_credits": 15}, ) assert captured["session.create"]["enableCitations"] is True + assert captured["session.create"]["enableFileChangeTracking"] is True assert captured["session.create"]["excludedBuiltinAgents"] == ["explore"] assert captured["session.create"]["sessionLimits"] == {"maxAiCredits": 30} assert captured["session.resume"]["enableCitations"] is False + assert captured["session.resume"]["enableFileChangeTracking"] is False assert captured["session.resume"]["excludedBuiltinAgents"] == ["task"] assert captured["session.resume"]["sessionLimits"] == {"maxAiCredits": 15} finally: @@ -754,6 +1109,7 @@ async def mock_request(method, params, **kwargs): client._client.request = mock_request plugin_dirs = ["/tmp/plugins/a", "/tmp/plugins/b"] + disabled_mcp_servers = ["local-files", "remote-github"] large_output = { "enabled": True, "max_size_bytes": 1024, @@ -768,19 +1124,45 @@ async def mock_request(method, params, **kwargs): session = await client.create_session( on_permission_request=PermissionHandler.approve_all, plugin_directories=plugin_dirs, + disabled_mcp_servers=disabled_mcp_servers, large_output=large_output, ) await client.resume_session( session.session_id, on_permission_request=PermissionHandler.approve_all, plugin_directories=plugin_dirs, + disabled_mcp_servers=disabled_mcp_servers, large_output=large_output, ) assert captured["session.create"]["pluginDirectories"] == plugin_dirs + assert captured["session.create"]["disabledMcpServers"] == disabled_mcp_servers assert captured["session.create"]["largeOutput"] == expected_large_output_wire assert captured["session.resume"]["pluginDirectories"] == plugin_dirs + assert captured["session.resume"]["disabledMcpServers"] == disabled_mcp_servers assert captured["session.resume"]["largeOutput"] == expected_large_output_wire + + empty_session = await client.create_session( + on_permission_request=PermissionHandler.approve_all, + disabled_mcp_servers=[], + ) + await client.resume_session( + empty_session.session_id, + on_permission_request=PermissionHandler.approve_all, + disabled_mcp_servers=[], + ) + assert captured["session.create"]["disabledMcpServers"] == [] + assert captured["session.resume"]["disabledMcpServers"] == [] + + omitted_session = await client.create_session( + on_permission_request=PermissionHandler.approve_all, + ) + await client.resume_session( + omitted_session.session_id, + on_permission_request=PermissionHandler.approve_all, + ) + assert "disabledMcpServers" not in captured["session.create"] + assert "disabledMcpServers" not in captured["session.resume"] finally: await client.force_stop() @@ -2411,6 +2793,45 @@ def on_failure(input_data, invocation): assert result == {"additionalContext": "sync-ok"} +class TestAgentStopHookDispatch: + """Unit tests for the agentStop handler dispatch.""" + + @pytest.mark.asyncio + async def test_dispatches_to_on_agent_stop(self): + from copilot.session import CopilotSession, SessionHooks + + captured: dict = {} + + async def on_agent_stop(input_data, invocation): + captured["input"] = input_data + captured["invocation"] = invocation + return {"decision": "block", "reason": "finish the remaining work"} + + session = CopilotSession.__new__(CopilotSession) + CopilotSession.__init__(session, "sess-123", client=None) + session._hooks = SessionHooks(on_agent_stop=on_agent_stop) # type: ignore[typeddict-item] + + result = await session._handle_hooks_invoke( + "agentStop", + { + "sessionId": "sess-x", + "timestamp": 1700000000, + "cwd": "/work", + "stopReason": "end_turn", + "transcriptPath": "/tmp/transcript.jsonl", + "stop_hook_active": True, + }, + ) + + assert result == {"decision": "block", "reason": "finish the remaining work"} + assert captured["input"]["stopReason"] == "end_turn" + assert captured["input"]["transcriptPath"] == "/tmp/transcript.jsonl" + assert captured["input"]["stopHookActive"] is True + assert captured["input"]["workingDirectory"] == "/work" + assert captured["input"]["timestamp"] == datetime.fromtimestamp(1700000000 / 1000, tz=UTC) + assert captured["invocation"] == {"session_id": "sess-123"} + + class TestGitHubTelemetry: """Unit tests for the experimental gitHubTelemetry.event consumer surface.""" diff --git a/python/test_codegen_type_names.py b/python/test_codegen_type_names.py new file mode 100644 index 000000000..5242f1c78 --- /dev/null +++ b/python/test_codegen_type_names.py @@ -0,0 +1,29 @@ +import re +import types + +from copilot.generated import rpc + + +def test_permission_approval_exports_are_union_aliases(): + approval_exports = [ + name + for name in rpc.__all__ + if re.fullmatch(r"PermissionDecisionApproveFor.*Approval", name) + ] + assert approval_exports + + for name in approval_exports: + exported = getattr(rpc, name) + assert isinstance(exported, types.UnionType), ( + f"{name} must be a union alias, not a synthetic dataclass" + ) + + +def test_permission_approval_union_loaders_deserialize_expected_variants(): + session = rpc._load_PermissionDecisionApproveForSessionApproval( + {"kind": "commands", "commandIdentifiers": ["git status"]} + ) + location = rpc._load_PermissionDecisionApproveForLocationApproval({"kind": "read"}) + + assert isinstance(session, rpc.PermissionDecisionApproveForSessionApprovalCommands) + assert isinstance(location, rpc.PermissionDecisionApproveForLocationApprovalRead) diff --git a/python/test_e2e_harness_cli_path.py b/python/test_e2e_harness_cli_path.py new file mode 100644 index 000000000..8a50ba7a5 --- /dev/null +++ b/python/test_e2e_harness_cli_path.py @@ -0,0 +1,146 @@ +"""Unit tests for the E2E harness's Copilot CLI platform-package resolution. + +Regression coverage for github/copilot-sdk#2103: the harness used to return the +first ``@github/copilot-*`` directory in alphabetical order instead of the package +built for the current platform. +""" + +from __future__ import annotations + +from pathlib import Path + +import pytest + +from copilot._cli_version import get_npm_platform +from e2e.testharness import context + + +def _make_package(github_modules: Path, name: str) -> Path: + """Create ``//index.js`` and return the entrypoint path.""" + package_dir = github_modules / name + package_dir.mkdir(parents=True, exist_ok=True) + index = package_dir / "index.js" + index.write_text("// fake CLI entrypoint\n") + return index + + +class TestCliPlatformPackageNames: + def test_non_linux_platform_yields_single_candidate(self): + assert context._cli_platform_package_names("darwin-arm64") == ["copilot-darwin-arm64"] + + def test_windows_platform_yields_single_candidate(self): + assert context._cli_platform_package_names("win32-x64") == ["copilot-win32-x64"] + + def test_glibc_linux_also_considers_musl_variant(self): + assert context._cli_platform_package_names("linux-x64") == [ + "copilot-linux-x64", + "copilot-linuxmusl-x64", + ] + + def test_musl_linux_prefers_musl_then_falls_back_to_glibc(self): + assert context._cli_platform_package_names("linuxmusl-arm64") == [ + "copilot-linuxmusl-arm64", + "copilot-linux-arm64", + ] + + def test_defaults_to_current_host_platform(self): + assert context._cli_platform_package_names()[0] == f"copilot-{get_npm_platform()}" + + +class TestFindCliInNodeModules: + def test_skips_alphabetically_earlier_foreign_package(self, tmp_path): + # The #2103 regression: "aardvark" sorts before every real platform name. + _make_package(tmp_path, "copilot-aardvark-x64") + expected = _make_package(tmp_path, "copilot-darwin-arm64") + found = context._find_cli_in_node_modules(tmp_path, ["copilot-darwin-arm64"]) + assert found == str(expected.resolve()) + + def test_returns_none_when_no_candidate_is_installed(self, tmp_path): + _make_package(tmp_path, "copilot-win32-x64") + assert context._find_cli_in_node_modules(tmp_path, ["copilot-darwin-arm64"]) is None + + def test_ignores_non_platform_copilot_packages(self, tmp_path): + _make_package(tmp_path, "copilot-language-server") + assert context._find_cli_in_node_modules(tmp_path, ["copilot-linux-x64"]) is None + + def test_prefers_earlier_candidate_when_both_libc_variants_exist(self, tmp_path): + expected = _make_package(tmp_path, "copilot-linuxmusl-x64") + _make_package(tmp_path, "copilot-linux-x64") + found = context._find_cli_in_node_modules( + tmp_path, ["copilot-linuxmusl-x64", "copilot-linux-x64"] + ) + assert found == str(expected.resolve()) + + def test_returns_none_when_package_dir_has_no_index_js(self, tmp_path): + (tmp_path / "copilot-linux-x64").mkdir() + assert context._find_cli_in_node_modules(tmp_path, ["copilot-linux-x64"]) is None + + def test_returns_none_when_github_modules_is_absent(self, tmp_path): + missing = tmp_path / "missing" + assert context._find_cli_in_node_modules(missing, ["copilot-linux-x64"]) is None + + +class TestInstalledCliPackageNames: + def test_lists_platform_directories_sorted(self, tmp_path): + _make_package(tmp_path, "copilot-win32-x64") + _make_package(tmp_path, "copilot-darwin-arm64") + (tmp_path / "not-copilot").mkdir() + assert context._installed_cli_package_names(tmp_path) == [ + "copilot-darwin-arm64", + "copilot-win32-x64", + ] + + def test_returns_empty_when_directory_is_absent(self, tmp_path): + assert context._installed_cli_package_names(tmp_path / "missing") == [] + + +class TestGetCliPathForTests: + def test_env_var_takes_precedence(self, tmp_path, monkeypatch): + cli = tmp_path / "custom-cli.js" + cli.write_text("// custom entrypoint\n") + monkeypatch.setenv("COPILOT_CLI_PATH", str(cli)) + assert context.get_cli_path_for_tests() == str(cli.resolve()) + + def test_error_names_the_packages_tried_and_the_remedy(self, monkeypatch): + monkeypatch.delenv("COPILOT_CLI_PATH", raising=False) + monkeypatch.setattr( + context, "_cli_platform_package_names", lambda *_: ["copilot-linux-x64"] + ) + monkeypatch.setattr(context, "_find_cli_in_node_modules", lambda *_: None) + with pytest.raises(RuntimeError) as excinfo: + context.get_cli_path_for_tests() + message = str(excinfo.value) + assert "copilot-linux-x64" in message + assert "npm install" in message + assert "COPILOT_CLI_PATH" in message + + def test_error_names_the_searched_directory(self, monkeypatch): + monkeypatch.delenv("COPILOT_CLI_PATH", raising=False) + seen: list[Path] = [] + + def fake_find(github_modules, package_names): + seen.append(github_modules) + return None + + monkeypatch.setattr(context, "_cli_platform_package_names", lambda *_: ["copilot-nope-x64"]) + monkeypatch.setattr(context, "_find_cli_in_node_modules", fake_find) + with pytest.raises(RuntimeError) as excinfo: + context.get_cli_path_for_tests() + assert seen, "get_cli_path_for_tests must consult _find_cli_in_node_modules" + assert seen[0].name == "@github" + assert seen[0].parent.name == "node_modules" + assert seen[0].parent.parent.name == "nodejs" + assert str(seen[0]) in str(excinfo.value) + + def test_error_lists_the_packages_actually_installed(self, monkeypatch): + monkeypatch.delenv("COPILOT_CLI_PATH", raising=False) + monkeypatch.setattr(context, "_cli_platform_package_names", lambda *_: ["copilot-nope-x64"]) + monkeypatch.setattr(context, "_find_cli_in_node_modules", lambda *_: None) + monkeypatch.setattr( + context, "_installed_cli_package_names", lambda *_: ["copilot-darwin-arm64"] + ) + with pytest.raises(RuntimeError) as excinfo: + context.get_cli_path_for_tests() + message = str(excinfo.value) + assert "present: copilot-darwin-arm64" in message + assert "copilot-nope-x64" in message diff --git a/python/test_event_forward_compatibility.py b/python/test_event_forward_compatibility.py index 1ffbd59c5..2e8015a97 100644 --- a/python/test_event_forward_compatibility.py +++ b/python/test_event_forward_compatibility.py @@ -18,10 +18,12 @@ ElicitationCompletedAction, ElicitationRequestedMode, ElicitationRequestedSchema, + ManagedSettingsResolvedSource, PermissionPromptRequestMemory, PermissionRequestMemory, PermissionRequestMemoryAction, SessionEventType, + SessionManagedSettingsResolvedData, SessionTaskCompleteData, UserMessageAgentMode, session_event_from_dict, @@ -136,6 +138,42 @@ def test_explicit_generated_symbols_remain_available(self): ) assert schema.to_dict()["type"] == "object" + def test_managed_settings_client_provenance_round_trips(self): + """Managed settings events should preserve truthful client provenance.""" + assert [source.value for source in ManagedSettingsResolvedSource] == [ + "server", + "device", + "client", + "mixed", + "none", + ] + + client = SessionManagedSettingsResolvedData( + bypass_permissions_disabled=True, + client_managed=True, + device_managed=False, + fail_closed=False, + managed_keys=["permissions"], + server_managed=False, + source=ManagedSettingsResolvedSource.CLIENT, + ) + serialized = client.to_dict() + assert serialized["source"] == "client" + assert serialized["clientManaged"] is True + assert SessionManagedSettingsResolvedData.from_dict(serialized) == client + + mixed = SessionManagedSettingsResolvedData( + bypass_permissions_disabled=True, + device_managed=True, + fail_closed=False, + managed_keys=["permissions"], + server_managed=True, + source=ManagedSettingsResolvedSource.MIXED, + ) + serialized = mixed.to_dict() + assert serialized["source"] == "mixed" + assert "clientManaged" not in serialized + def test_data_shim_preserves_raw_mapping_values(self): """Compatibility Data should keep arbitrary nested mappings as plain dicts.""" parsed = Data.from_dict( diff --git a/python/test_managed_permissions.py b/python/test_managed_permissions.py new file mode 100644 index 000000000..ca07556da --- /dev/null +++ b/python/test_managed_permissions.py @@ -0,0 +1,121 @@ +import pytest + +from copilot.rpc import PermissionDecisionApproveOnce, PermissionDecisionUserNotAvailable +from copilot.session import CopilotSession, PermissionHandler, PermissionNoResult +from copilot.session_events import ( + PermissionRequestCustomTool, + PermissionRequestedData, + PermissionRequestRead, +) + + +def test_permission_event_exposes_managed_approval_required() -> None: + data = PermissionRequestedData.from_dict( + { + "permissionRequest": { + "kind": "read", + "intention": "Read managed content", + "path": "/workspace/file.txt", + "managedApprovalRequired": True, + }, + "requestId": "permission-1", + } + ) + + assert data.permission_request.managed_approval_required is True + assert data.to_dict()["permissionRequest"]["managedApprovalRequired"] is True + + +def test_managed_metadata_preserves_existing_positional_constructor_order() -> None: + request = PermissionRequestCustomTool( + "Run a custom tool", + "custom_tool", + {"value": 1}, + "tool-call-1", + ) + + assert request.tool_call_id == "tool-call-1" + assert request.managed_approval_required is None + + read_request = PermissionRequestRead( + "Read content", + "/workspace/file.txt", + True, + False, + "Use the sandbox", + "tool-call-2", + ) + + assert read_request.managed_approval_required is True + assert read_request.request_sandbox_bypass is False + assert read_request.request_sandbox_bypass_reason == "Use the sandbox" + assert read_request.tool_call_id == "tool-call-2" + + +def test_approve_all_rejects_managed_settings_session() -> None: + request = PermissionRequestRead( + intention="Read ordinary content", + path="/workspace/file.txt", + ) + + with pytest.raises(RuntimeError, match="managed settings are enabled"): + PermissionHandler.approve_all( + request, + {"session_id": "session-1", "managed_settings_enabled": True}, + ) + + +def test_approve_all_rejects_managed_request_in_managed_settings_session() -> None: + request = PermissionRequestRead( + intention="Read managed content", + path="/workspace/file.txt", + managed_approval_required=True, + ) + + with pytest.raises(RuntimeError, match="managed settings are enabled"): + PermissionHandler.approve_all( + request, + {"session_id": "session-1", "managed_settings_enabled": True}, + ) + + +def test_approve_all_approves_ordinary_request() -> None: + request = PermissionRequestRead( + intention="Read ordinary content", + path="/workspace/file.txt", + ) + + assert isinstance( + PermissionHandler.approve_all( + request, + {"session_id": "session-1", "managed_settings_enabled": False}, + ), + PermissionDecisionApproveOnce, + ) + + +def test_approve_all_leaves_managed_request_pending_when_session_flag_is_absent() -> None: + request = PermissionRequestRead( + intention="Read managed content", + path="/workspace/file.txt", + managed_approval_required=True, + ) + + assert isinstance( + PermissionHandler.approve_all(request, {"session_id": "session-1"}), + PermissionNoResult, + ) + + +async def test_legacy_permission_callback_rejects_no_result() -> None: + request = PermissionRequestRead( + intention="Read managed content", + path="/workspace/file.txt", + managed_approval_required=True, + ) + session = CopilotSession("session-1", client=None) + session._register_permission_handler(lambda _request, _invocation: PermissionNoResult()) + + result = await session._handle_permission_request(request) + + assert isinstance(result, PermissionDecisionUserNotAvailable) diff --git a/python/test_permission_decision_context.py b/python/test_permission_decision_context.py new file mode 100644 index 000000000..2b013942d --- /dev/null +++ b/python/test_permission_decision_context.py @@ -0,0 +1,99 @@ +from unittest.mock import AsyncMock, MagicMock + +from copilot.rpc import ( + PermissionDecisionApproveOnce, + PermissionDecisionContext, + PermissionDecisionOutcome, + PermissionDecisionSource, + PermissionDecisionSurface, +) +from copilot.session import ( + AttributedPermissionResult, + CopilotSession, + PermissionNoResult, + create_attributed_permission_result, +) +from copilot.session_events import PermissionRequestRead + + +def _context() -> PermissionDecisionContext: + return PermissionDecisionContext( + outcome=PermissionDecisionOutcome.AUTO_APPROVED, + source=PermissionDecisionSource.HOST_POLICY, + surface=PermissionDecisionSurface.SDK, + ) + + +def _session_with_captured_rpc() -> tuple[CopilotSession, AsyncMock]: + session = CopilotSession("session-1", client=None) + handle = AsyncMock() + rpc = MagicMock() + rpc.permissions.handle_pending_permission_request = handle + session._rpc = rpc + return session, handle + + +async def test_decision_context_serialized_as_sibling_of_result() -> None: + session, handle = _session_with_captured_rpc() + request = PermissionRequestRead(intention="Read", path="/workspace/file.txt") + + def handler(_request, _invocation): + return create_attributed_permission_result(PermissionDecisionApproveOnce(), _context()) + + await session._execute_permission_and_respond("permission-1", request, handler) + + handle.assert_awaited_once() + sent = handle.await_args.args[0] + params = sent.to_dict() + + assert params["decisionContext"] == { + "outcome": "auto_approved", + "source": "host_policy", + "surface": "sdk", + } + assert "decisionContext" not in params["result"] + assert params["result"]["kind"] == "approve-once" + + +async def test_no_context_omits_decision_context_key() -> None: + session, handle = _session_with_captured_rpc() + request = PermissionRequestRead(intention="Read", path="/workspace/file.txt") + + def handler(_request, _invocation): + return PermissionDecisionApproveOnce() + + await session._execute_permission_and_respond("permission-1", request, handler) + + handle.assert_awaited_once() + params = handle.await_args.args[0].to_dict() + + assert "decisionContext" not in params + assert params["result"]["kind"] == "approve-once" + + +def test_attributed_result_replaces_rather_than_nests() -> None: + first = PermissionDecisionContext( + outcome=PermissionDecisionOutcome.PROMPTED_USER, + source=PermissionDecisionSource.HUMAN_RESPONSE, + surface=PermissionDecisionSurface.TUI, + ) + second = _context() + + once_wrapped = create_attributed_permission_result(PermissionDecisionApproveOnce(), first) + twice_wrapped = create_attributed_permission_result(once_wrapped, second) + + assert isinstance(twice_wrapped, AttributedPermissionResult) + assert isinstance(twice_wrapped.result, PermissionDecisionApproveOnce) + assert twice_wrapped.decision_context is second + + +async def test_no_result_with_context_still_suppresses_response() -> None: + session, handle = _session_with_captured_rpc() + request = PermissionRequestRead(intention="Read", path="/workspace/file.txt") + + def handler(_request, _invocation): + return create_attributed_permission_result(PermissionNoResult(), _context()) + + await session._execute_permission_and_respond("permission-1", request, handler) + + handle.assert_not_awaited() diff --git a/python/test_rpc_generated.py b/python/test_rpc_generated.py index 8b9423c13..5556a77c3 100644 --- a/python/test_rpc_generated.py +++ b/python/test_rpc_generated.py @@ -1,5 +1,6 @@ """Tests for generated RPC method behavior.""" +import json from unittest.mock import AsyncMock import pytest @@ -7,6 +8,14 @@ from copilot.rpc import ( CommandsApi, CommandsInvokeRequest, + CommandsRespondToQueuedCommandRequest, + LocalSessionMetadataValue, + QueuedCommandHandled, + QueuedCommandNotHandled, + RemoteControlStatusOff, + RemoteControlStatusResult, + RemoteSessionMetadataValue, + SessionList, SlashCommandTextResult, ) @@ -22,3 +31,77 @@ async def test_commands_invoke_deserializes_slash_command_result(): assert isinstance(result, SlashCommandTextResult) assert result.text == "hello" assert result.markdown is True + + +def test_remote_control_status_deserializes_string_discriminated_union(): + result = RemoteControlStatusResult.from_dict({"status": {"state": "off"}}) + + assert isinstance(result.status, RemoteControlStatusOff) + assert result.status.state == "off" + assert result.status.to_dict() == {"state": "off"} + + +def test_session_list_deserializes_boolean_discriminated_entries(): + payload = { + "sessions": [ + { + "sessionId": "example-local", + "startTime": "2026-07-26T10:00:00.000Z", + "modifiedTime": "2026-07-26T10:05:00.000Z", + "isRemote": False, + }, + { + "sessionId": "example-remote", + "startTime": "2026-07-26T11:00:00.000Z", + "modifiedTime": "2026-07-26T11:05:00.000Z", + "isRemote": True, + "remoteSessionIds": ["example-remote"], + "repository": {"owner": "github", "name": "copilot-sdk", "branch": "main"}, + }, + ] + } + + result = SessionList.from_dict(payload) + + local, remote = result.sessions + assert isinstance(local, LocalSessionMetadataValue) + assert local.session_id == "example-local" + assert local.is_remote is False + assert isinstance(remote, RemoteSessionMetadataValue) + assert remote.session_id == "example-remote" + assert remote.is_remote is True + assert remote.repository.owner == "github" + + +@pytest.mark.parametrize( + ("handled", "expected_type"), + [(True, QueuedCommandHandled), (False, QueuedCommandNotHandled)], +) +def test_queued_command_result_deserializes_boolean_discriminator(handled, expected_type): + request = CommandsRespondToQueuedCommandRequest.from_dict( + {"requestId": "example-request", "result": {"handled": handled}} + ) + + assert isinstance(request.result, expected_type) + + +@pytest.mark.parametrize( + ("variant", "expected_handled", "expected_json"), + [ + (QueuedCommandHandled(), True, '{"handled": true}'), + (QueuedCommandNotHandled(), False, '{"handled": false}'), + ], +) +def test_queued_command_result_serializes_boolean_discriminator( + variant, expected_handled, expected_json +): + encoded = variant.to_dict() + + assert encoded["handled"] is expected_handled + assert json.dumps(encoded) == expected_json + + request = CommandsRespondToQueuedCommandRequest(request_id="example-request", result=variant) + round_tripped = CommandsRespondToQueuedCommandRequest.from_dict(request.to_dict()) + + assert request.to_dict()["result"]["handled"] is expected_handled + assert isinstance(round_tripped.result, type(variant)) diff --git a/python/test_tool_set.py b/python/test_tool_set.py index 6a65e0df2..0674b488a 100644 --- a/python/test_tool_set.py +++ b/python/test_tool_set.py @@ -6,6 +6,7 @@ from copilot import BUILTIN_TOOLS_ISOLATED, CopilotClient, ToolSet, UriRuntimeConnection from copilot._mode import ( + _custom_agents_local_only_default, _embedding_cache_storage_default, _enable_file_hooks_default, _enable_host_git_operations_default, @@ -198,6 +199,7 @@ class TestEmptyModeBooleanDefaults: (_enable_host_git_operations_default, False), (_enable_session_store_default, False), (_enable_skills_default, False), + (_custom_agents_local_only_default, True), ], ) def test_empty_mode_defaults(self, helper, empty_default): @@ -213,6 +215,7 @@ def test_empty_mode_defaults(self, helper, empty_default): _enable_host_git_operations_default, _enable_session_store_default, _enable_skills_default, + _custom_agents_local_only_default, ], ) def test_caller_wins(self, helper): @@ -229,6 +232,7 @@ def test_caller_wins(self, helper): _enable_host_git_operations_default, _enable_session_store_default, _enable_skills_default, + _custom_agents_local_only_default, ], ) def test_copilot_cli_does_not_change(self, helper): diff --git a/python/test_tools.py b/python/test_tools.py index 90498c2b8..97de41df4 100644 --- a/python/test_tools.py +++ b/python/test_tools.py @@ -8,10 +8,12 @@ from copilot import define_tool from copilot.generated.rpc import ExternalToolTextResultForLlm from copilot.tools import ( + ToolBinaryResult, ToolInvocation, ToolResult, _normalize_result, convert_mcp_call_tool_result, + tool_result_to_external_tool_text_result_for_llm, ) @@ -387,6 +389,44 @@ class Item(BaseModel): assert parsed == [{"name": "a", "value": 1}, {"name": "b", "value": 2}] assert result.result_type == "success" + def test_pydantic_model_with_non_primitive_fields_is_serialized(self): + from datetime import date, datetime + from decimal import Decimal + from enum import Enum + from uuid import UUID + + class Status(Enum): + ACTIVE = "active" + + class Record(BaseModel): + id: UUID + created: datetime + day: date + score: Decimal + status: Status + tags: set[str] + + record = Record( + id=UUID("12345678-1234-5678-1234-567812345678"), + created=datetime(2026, 1, 15, 10, 30, 0), + day=date(2026, 1, 15), + score=Decimal("99.5"), + status=Status.ACTIVE, + tags={"python", "sdk"}, + ) + result = _normalize_result(record) + parsed = json.loads(result.text_result_for_llm) + assert parsed == { + "id": "12345678-1234-5678-1234-567812345678", + "created": "2026-01-15T10:30:00", + "day": "2026-01-15", + "score": "99.5", + "status": "active", + "tags": parsed["tags"], + } + assert set(parsed["tags"]) == {"python", "sdk"} + assert result.result_type == "success" + def test_raises_for_unserializable_value(self): # Functions cannot be JSON serialized with pytest.raises(TypeError, match="Failed to serialize"): @@ -550,3 +590,47 @@ def test_tool_references_round_trip_from_wire(self): } ) assert wire.tool_references == ["alpha", "beta"] + + +class TestToolResultToExternalToolTextResultForLlm: + def test_forwards_binary_results_and_session_log(self): + tool_result = ToolResult( + text_result_for_llm="screenshot captured", + binary_results_for_llm=[ + ToolBinaryResult( + data="base64data", + mime_type="image/png", + type="image", + description="screenshot.png", + ) + ], + session_log="tool execution details", + tool_telemetry={"duration_ms": 42}, + ) + + rpc_result = tool_result_to_external_tool_text_result_for_llm(tool_result) + + assert rpc_result.text_result_for_llm == "screenshot captured" + assert rpc_result.session_log == "tool execution details" + assert rpc_result.tool_telemetry == {"duration_ms": 42} + assert rpc_result.binary_results_for_llm is not None + assert len(rpc_result.binary_results_for_llm) == 1 + assert rpc_result.binary_results_for_llm[0].data == "base64data" + assert rpc_result.binary_results_for_llm[0].mime_type == "image/png" + assert rpc_result.binary_results_for_llm[0].type.value == "image" + assert rpc_result.binary_results_for_llm[0].description == "screenshot.png" + + def test_omits_binary_results_when_none(self): + tool_result = ToolResult(text_result_for_llm="done") + rpc_result = tool_result_to_external_tool_text_result_for_llm(tool_result) + assert rpc_result.binary_results_for_llm is None + assert rpc_result.session_log is None + + def test_forwards_tool_references(self): + tool_result = ToolResult( + text_result_for_llm="found tools", + result_type="success", + tool_references=["get_weather", "check_status"], + ) + rpc_result = tool_result_to_external_tool_text_result_for_llm(tool_result) + assert rpc_result.tool_references == ["get_weather", "check_status"] diff --git a/rust/README.md b/rust/README.md index 254a2b216..29fe67355 100644 --- a/rust/README.md +++ b/rust/README.md @@ -4,7 +4,7 @@ A Rust SDK for programmatic access to the GitHub Copilot CLI. See [github/copilot-sdk](https://github.com/github/copilot-sdk) for the equivalent SDKs in TypeScript, Python, Go, .NET, and Java. The Rust SDK seeks parity with those SDKs; see [Differences From Other SDKs](#differences-from-other-sdks) below for the small set of intentional divergences. -**Releases:** [github.com/github/copilot-sdk/releases?q=rust%2F](https://github.com/github/copilot-sdk/releases?q=rust%2F) — per-version release notes for the Rust crate. +**Releases:** [github.com/github/copilot-sdk/releases](https://github.com/github/copilot-sdk/releases) — combined release notes for all SDK languages. ## Prerequisites @@ -31,6 +31,12 @@ client.stop().await.ok(); # } ``` +When targeting MCP tools configured through `mcp_servers`, remember the runtime +tool name is `-`. For `available_tools` and +`excluded_tools`, prefer `ToolSet::new().add_mcp("-")` +or the raw `mcp:-` form. For `custom_agents[].tools` +and `default_agent.excluded_tools`, use `-` directly. + ## Architecture ```text @@ -70,6 +76,20 @@ let pong = client.ping("hello").await?; client.stop().await?; ``` +After `Client::start` succeeds, inspect its startup cost without parsing logs: + +```rust,ignore +let timings = client.startup_timings().expect("started by Client::start"); +println!( + "startup={}ms transport={}ms handshake={}ms", + timings.total_ms, timings.transport_setup_ms, timings.handshake_ms +); +``` + +Transport-specific phases are optional. For example, `port_wait_ms` is present +only for TCP and `process_spawn_ms` is absent for external and in-process +transports. + **`ClientOptions`:** | Field | Type | Description | @@ -88,6 +108,8 @@ With the default `CliProgram::Resolve`, `Client::start()` resolves the CLI in th Created via `Client::create_session` or `Client::resume_session`. Owns an internal event loop that dispatches CLI callbacks to the focused handler traits you install on `SessionConfig`, and broadcasts session events through `subscribe()`. +`SessionConfig::working_directory` sets the session working directory. When unset, the runtime uses its process working directory. + ```rust,ignore use github_copilot_sdk::MessageOptions; @@ -210,6 +232,10 @@ impl PermissionHandler for MyPermissions { _rid: RequestId, data: PermissionRequestData, ) -> PermissionResult { + if data.managed_approval_required == Some(true) { + return PermissionResult::no_result(); + } + if data.extra.get("tool").and_then(|v| v.as_str()) == Some("view") { PermissionResult::approve_once() } else { @@ -230,7 +256,7 @@ let config = SessionConfig::default() .with_user_input_handler(h); ``` -The built-in `ApproveAllHandler` and `DenyAllHandler` implement `PermissionHandler` for the common cases. To observe streamed session events (assistant messages, tool calls, etc.), call `session.subscribe()` — see [Streaming](#streaming) below. +The built-in `ApproveAllHandler` and `DenyAllHandler` implement `PermissionHandler` for the common cases. When `enable_managed_settings` is true, `ApproveAllHandler` logs an error and returns a user-not-available decision; custom handlers can inspect `managed_approval_required` when implementing a human-facing confirmation flow. To observe streamed session events (assistant messages, tool calls, etc.), call `session.subscribe()` — see [Streaming](#streaming) below. ### SessionConfig @@ -294,7 +320,7 @@ let session = client .await?; ``` -**Hook events:** `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `UserPromptSubmitted`, `SessionStart`, `SessionEnd`, `ErrorOccurred`. Each carries typed input/output structs. `PostToolUse` only fires on success; override `on_post_tool_use_failure` to observe failed tool calls. Return `HookOutput::None` for events you don't handle. +**Hook events:** `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `UserPromptSubmitted`, `UserPromptTransformed`, `SessionStart`, `SessionEnd`, `ErrorOccurred`. Each carries typed input/output structs. `PostToolUse` only fires on success; override `on_post_tool_use_failure` to observe failed tool calls. Return `HookOutput::None` for events you don't handle. ### System Message Transforms @@ -410,6 +436,8 @@ Reach for the `ToolHandler` trait directly when you need shared state across mul Set a permission policy directly on `SessionConfig` with the chainable builders. They install a synthesized `PermissionHandler` so only permission requests are intercepted; every other event flows through unchanged. +When `enable_managed_settings` is true, the approve-all policy logs an error and returns a user-not-available decision. Custom handlers can inspect `managed_approval_required` for human-facing confirmation logic. + ```rust,ignore let session = client .create_session( @@ -571,6 +599,8 @@ config.infinite_sessions = Some(infinite); The CLI emits `session.compaction_start` / `session.compaction_complete` events around each compaction. The session id remains stable across compactions; resume with `Client::resume_session` to pick up a prior conversation. Workspace state lives under `~/.copilot/session-state/{sessionId}` by default — override with `workspace_path` to relocate. +`enable_session_store` on `SessionConfig` enables the cross-session store for search and retrieval across sessions. When unset in the default client mode, the runtime default applies (enabled). In `Empty` mode, defaults to disabled. + ### Memory Configure the runtime memory feature for a session: @@ -935,3 +965,22 @@ github-copilot-sdk = { version = "0.1", default-features = false } # Derive JSON Schema for tool parameters (adds to default bundled-cli). github-copilot-sdk = { version = "0.1", features = ["derive"] } ``` + +## Development + +Tests require a supported [Node.js version](../nodejs/README.md#prerequisites). From the repository root: + +```bash +cd nodejs +npm ci +``` + +```bash +cd test/harness +npm ci +``` + +```bash +cd rust +cargo test --features test-support +``` diff --git a/rust/RELEASING.md b/rust/RELEASING.md index de0252de8..06e362f54 100644 --- a/rust/RELEASING.md +++ b/rust/RELEASING.md @@ -1,8 +1,7 @@ # Releasing `github-copilot-sdk` -The Rust crate ships through the same unified `publish.yml` workflow -as the Node, .NET, and Python SDKs. There is no Rust-specific release -workflow. +The Rust crate ships through the unified `publish.yml` workflow +alongside the other SDKs. There is no Rust-specific release workflow. ## TL;DR @@ -16,9 +15,11 @@ workflow. prerelease version requirement to install it. - `unstable` — skipped for Rust (Cargo doesn't have a clean equivalent of npm's `unstable` dist-tag). -4. The workflow publishes all four SDKs at the shared computed - version, tags `rust/vX.Y.Z`, and creates a Rust-scoped GitHub - Release with auto-generated notes since the previous Rust tag. +4. For `latest` and `prerelease`, the workflow publishes all SDKs at + the shared computed version, tags `rust/vX.Y.Z` for source + traceability, and creates one combined `vX.Y.Z` GitHub Release. + The `unstable` channel publishes only the Node.js SDK and does not + create a GitHub Release. ## Version, tag, and release notes @@ -26,14 +27,11 @@ workflow. as a placeholder. CI overrides it at publish time with the version computed by `publish.yml` (or an explicit `version` workflow input). - **Tag:** `rust/vX.Y.Z` (matches the `go/vX.Y.Z` style used elsewhere - in this repo). The historical `rust-v0.1.0` tag from the - release-plz era stays valid as a starting point for auto-generated - release notes. -- **Release notes:** auto-generated by `gh release --generate-notes` - from PR titles between the previous Rust tag and the new one. - Write descriptive PR titles for any change that touches the Rust - surface; that's the only place those changes will be visible to - Rust users. + in this repo). The tag identifies the source used for that crate + version. +- **Release notes:** generated for the combined `vX.Y.Z` GitHub + Release. Write descriptive PR titles for changes that touch the Rust + surface so they are represented accurately in the shared notes. ## Cargo prerelease semantics @@ -59,8 +57,8 @@ cargo yank --version X.Y.Z github-copilot-sdk Yanking does *not* delete the version — existing `Cargo.lock` files keep working — but it stops new resolutions from picking it. Follow -up with a patch release that fixes the bug, and add a note to the -yanked version's GitHub Release explaining why. +up with a patch release that fixes the bug, and update the combined +GitHub Release notes to explain why. Reverse with `cargo yank --undo --version X.Y.Z github-copilot-sdk` if the yank was a mistake. @@ -90,6 +88,5 @@ git push origin rust/vX.Y.Z perl -i -pe 's/^version = ".*"$/version = "0.0.0-dev"/' Cargo.toml ``` -Manual publishes skip the auto-generated GitHub Release. Run -`gh release create rust/vX.Y.Z --generate-notes` after pushing the -tag. +Manual publishes skip the combined GitHub Release. Create or update the +matching `vX.Y.Z` release after pushing the tag. diff --git a/rust/examples/manual_tool_resume.rs b/rust/examples/manual_tool_resume.rs index e513cf921..ad8ad5a04 100644 --- a/rust/examples/manual_tool_resume.rs +++ b/rust/examples/manual_tool_resume.rs @@ -113,8 +113,10 @@ async fn main() -> Result<(), Box> { .rpc() .permissions() .handle_pending_permission_request(PermissionDecisionRequest { + decision_context: None, request_id: permission.request_id, result: PermissionDecision::ApproveOnce(PermissionDecisionApproveOnce { + approved_interactively: None, kind: PermissionDecisionApproveOnceKind::ApproveOnce, }), }) diff --git a/rust/src/generated/api_types.rs b/rust/src/generated/api_types.rs index bd32d490c..caf9457a8 100644 --- a/rust/src/generated/api_types.rs +++ b/rust/src/generated/api_types.rs @@ -24,6 +24,8 @@ pub mod rpc_methods { pub const CONNECT: &str = "connect"; /// `models.list` pub const MODELS_LIST: &str = "models.list"; + /// `models.getBuiltInCatalog` + pub const MODELS_GETBUILTINCATALOG: &str = "models.getBuiltInCatalog"; /// `tools.list` pub const TOOLS_LIST: &str = "tools.list"; /// `account.getQuota` @@ -54,6 +56,14 @@ pub mod rpc_methods { pub const MCP_CONFIG_RELOAD: &str = "mcp.config.reload"; /// `mcp.discover` pub const MCP_DISCOVER: &str = "mcp.discover"; + /// `extensions.discover` + pub const EXTENSIONS_DISCOVER: &str = "extensions.discover"; + /// `extensions.enable` + pub const EXTENSIONS_ENABLE: &str = "extensions.enable"; + /// `extensions.disable` + pub const EXTENSIONS_DISABLE: &str = "extensions.disable"; + /// `registerExtensionLaunchProvider` + pub const REGISTEREXTENSIONLAUNCHPROVIDER: &str = "registerExtensionLaunchProvider"; /// `plugins.list` pub const PLUGINS_LIST: &str = "plugins.list"; /// `plugins.install` @@ -100,6 +110,8 @@ pub mod rpc_methods { pub const USER_SETTINGS_GET: &str = "user.settings.get"; /// `user.settings.set` pub const USER_SETTINGS_SET: &str = "user.settings.set"; + /// `managedSettings.read` + pub const MANAGEDSETTINGS_READ: &str = "managedSettings.read"; /// `runtime.shutdown` pub const RUNTIME_SHUTDOWN: &str = "runtime.shutdown"; /// `sessionFs.setProvider` @@ -118,6 +130,10 @@ pub mod rpc_methods { pub const SESSIONS_CONNECT: &str = "sessions.connect"; /// `sessions.list` pub const SESSIONS_LIST: &str = "sessions.list"; + /// `sessions.getMetadata` + pub const SESSIONS_GETMETADATA: &str = "sessions.getMetadata"; + /// `sessions.listNonEmptySessionIds` + pub const SESSIONS_LISTNONEMPTYSESSIONIDS: &str = "sessions.listNonEmptySessionIds"; /// `sessions.findByTaskId` pub const SESSIONS_FINDBYTASKID: &str = "sessions.findByTaskId"; /// `sessions.findByPrefix` @@ -136,6 +152,8 @@ pub mod rpc_methods { pub const SESSIONS_CLOSE: &str = "sessions.close"; /// `sessions.bulkDelete` pub const SESSIONS_BULKDELETE: &str = "sessions.bulkDelete"; + /// `sessions.delete` + pub const SESSIONS_DELETE: &str = "sessions.delete"; /// `sessions.pruneOld` pub const SESSIONS_PRUNEOLD: &str = "sessions.pruneOld"; /// `sessions.save` @@ -175,8 +193,14 @@ pub mod rpc_methods { pub const SESSION_SEND: &str = "session.send"; /// `session.sendMessages` pub const SESSION_SENDMESSAGES: &str = "session.sendMessages"; + /// `session.sendSystemNotification` + pub const SESSION_SENDSYSTEMNOTIFICATION: &str = "session.sendSystemNotification"; /// `session.abort` pub const SESSION_ABORT: &str = "session.abort"; + /// `session.interruptMainTurn` + pub const SESSION_INTERRUPTMAINTURN: &str = "session.interruptMainTurn"; + /// `session.cancelAllBackgroundAgents` + pub const SESSION_CANCELALLBACKGROUNDAGENTS: &str = "session.cancelAllBackgroundAgents"; /// `session.shutdown` pub const SESSION_SHUTDOWN: &str = "session.shutdown"; /// `session.gitHubAuth.getStatus` @@ -197,8 +221,16 @@ pub mod rpc_methods { pub const SESSION_CANVAS_ACTION_INVOKE: &str = "session.canvas.action.invoke"; /// `session.factory.run` pub const SESSION_FACTORY_RUN: &str = "session.factory.run"; + /// `session.factory.resume` + pub const SESSION_FACTORY_RESUME: &str = "session.factory.resume"; /// `session.factory.getRun` pub const SESSION_FACTORY_GETRUN: &str = "session.factory.getRun"; + /// `session.factory.listRuns` + pub const SESSION_FACTORY_LISTRUNS: &str = "session.factory.listRuns"; + /// `session.factory.getRunDetail` + pub const SESSION_FACTORY_GETRUNDETAIL: &str = "session.factory.getRunDetail"; + /// `session.factory.getRunProgress` + pub const SESSION_FACTORY_GETRUNPROGRESS: &str = "session.factory.getRunProgress"; /// `session.factory.cancel` pub const SESSION_FACTORY_CANCEL: &str = "session.factory.cancel"; /// `session.factory.log` @@ -240,6 +272,10 @@ pub mod rpc_methods { "session.plan.readSqlTodosWithDependencies"; /// `session.workspaces.getWorkspace` pub const SESSION_WORKSPACES_GETWORKSPACE: &str = "session.workspaces.getWorkspace"; + /// `session.workspaces.updateMetadata` + pub const SESSION_WORKSPACES_UPDATEMETADATA: &str = "session.workspaces.updateMetadata"; + /// `session.workspaces.ensure` + pub const SESSION_WORKSPACES_ENSURE: &str = "session.workspaces.ensure"; /// `session.workspaces.listFiles` pub const SESSION_WORKSPACES_LISTFILES: &str = "session.workspaces.listFiles"; /// `session.workspaces.readFile` @@ -250,6 +286,22 @@ pub mod rpc_methods { pub const SESSION_WORKSPACES_LISTCHECKPOINTS: &str = "session.workspaces.listCheckpoints"; /// `session.workspaces.readCheckpoint` pub const SESSION_WORKSPACES_READCHECKPOINT: &str = "session.workspaces.readCheckpoint"; + /// `session.workspaces.addSummary` + pub const SESSION_WORKSPACES_ADDSUMMARY: &str = "session.workspaces.addSummary"; + /// `session.workspaces.truncateSummaries` + pub const SESSION_WORKSPACES_TRUNCATESUMMARIES: &str = "session.workspaces.truncateSummaries"; + /// `session.workspaces.readAutopilotObjective` + pub const SESSION_WORKSPACES_READAUTOPILOTOBJECTIVE: &str = + "session.workspaces.readAutopilotObjective"; + /// `session.workspaces.writeAutopilotObjective` + pub const SESSION_WORKSPACES_WRITEAUTOPILOTOBJECTIVE: &str = + "session.workspaces.writeAutopilotObjective"; + /// `session.workspaces.deleteAutopilotObjective` + pub const SESSION_WORKSPACES_DELETEAUTOPILOTOBJECTIVE: &str = + "session.workspaces.deleteAutopilotObjective"; + /// `session.workspaces.autopilotObjectiveExists` + pub const SESSION_WORKSPACES_AUTOPILOTOBJECTIVEEXISTS: &str = + "session.workspaces.autopilotObjectiveExists"; /// `session.workspaces.saveLargePaste` pub const SESSION_WORKSPACES_SAVELARGEPASTE: &str = "session.workspaces.saveLargePaste"; /// `session.workspaces.diff` @@ -265,6 +317,8 @@ pub mod rpc_methods { pub const SESSION_FLEET_START: &str = "session.fleet.start"; /// `session.agent.list` pub const SESSION_AGENT_LIST: &str = "session.agent.list"; + /// `session.agent.setPrompt` + pub const SESSION_AGENT_SETPROMPT: &str = "session.agent.setPrompt"; /// `session.agent.getCurrent` pub const SESSION_AGENT_GETCURRENT: &str = "session.agent.getCurrent"; /// `session.agent.select` @@ -345,8 +399,13 @@ pub mod rpc_methods { /// `session.mcp.oauth.handlePendingRequest` pub const SESSION_MCP_OAUTH_HANDLEPENDINGREQUEST: &str = "session.mcp.oauth.handlePendingRequest"; + /// `session.mcp.oauth.authenticationStateChanged` + pub const SESSION_MCP_OAUTH_AUTHENTICATIONSTATECHANGED: &str = + "session.mcp.oauth.authenticationStateChanged"; /// `session.mcp.oauth.login` pub const SESSION_MCP_OAUTH_LOGIN: &str = "session.mcp.oauth.login"; + /// `session.mcp.oauth.respond` + pub const SESSION_MCP_OAUTH_RESPOND: &str = "session.mcp.oauth.respond"; /// `session.mcp.headers.handlePendingHeadersRefreshRequest` pub const SESSION_MCP_HEADERS_HANDLEPENDINGHEADERSREFRESHREQUEST: &str = "session.mcp.headers.handlePendingHeadersRefreshRequest"; @@ -518,6 +577,8 @@ pub mod rpc_methods { pub const SESSION_SETTINGS_SNAPSHOT: &str = "session.settings.snapshot"; /// `session.settings.evaluatePredicate` pub const SESSION_SETTINGS_EVALUATEPREDICATE: &str = "session.settings.evaluatePredicate"; + /// `session.contentExclusion.checkPaths` + pub const SESSION_CONTENTEXCLUSION_CHECKPATHS: &str = "session.contentExclusion.checkPaths"; /// `session.shell.exec` pub const SESSION_SHELL_EXEC: &str = "session.shell.exec"; /// `session.shell.kill` @@ -530,6 +591,12 @@ pub mod rpc_methods { pub const SESSION_HISTORY_COMPACT: &str = "session.history.compact"; /// `session.history.truncate` pub const SESSION_HISTORY_TRUNCATE: &str = "session.history.truncate"; + /// `session.history.listRewindPoints` + pub const SESSION_HISTORY_LISTREWINDPOINTS: &str = "session.history.listRewindPoints"; + /// `session.history.previewRewind` + pub const SESSION_HISTORY_PREVIEWREWIND: &str = "session.history.previewRewind"; + /// `session.history.rewind` + pub const SESSION_HISTORY_REWIND: &str = "session.history.rewind"; /// `session.history.cancelBackgroundCompaction` pub const SESSION_HISTORY_CANCELBACKGROUNDCOMPACTION: &str = "session.history.cancelBackgroundCompaction"; @@ -537,12 +604,45 @@ pub mod rpc_methods { pub const SESSION_HISTORY_ABORTMANUALCOMPACTION: &str = "session.history.abortManualCompaction"; /// `session.history.summarizeForHandoff` pub const SESSION_HISTORY_SUMMARIZEFORHANDOFF: &str = "session.history.summarizeForHandoff"; + /// `session.history.clearContext` + pub const SESSION_HISTORY_CLEARCONTEXT: &str = "session.history.clearContext"; /// `session.queue.pendingItems` pub const SESSION_QUEUE_PENDINGITEMS: &str = "session.queue.pendingItems"; + /// `session.queue.snapshot` + pub const SESSION_QUEUE_SNAPSHOT: &str = "session.queue.snapshot"; + /// `session.queue.moveItem` + pub const SESSION_QUEUE_MOVEITEM: &str = "session.queue.moveItem"; + /// `session.queue.insertAt` + pub const SESSION_QUEUE_INSERTAT: &str = "session.queue.insertAt"; + /// `session.queue.removeAt` + pub const SESSION_QUEUE_REMOVEAT: &str = "session.queue.removeAt"; + /// `session.queue.updateText` + pub const SESSION_QUEUE_UPDATETEXT: &str = "session.queue.updateText"; + /// `session.queue.duplicateAt` + pub const SESSION_QUEUE_DUPLICATEAT: &str = "session.queue.duplicateAt"; + /// `session.queue.setDrainPaused` + pub const SESSION_QUEUE_SETDRAINPAUSED: &str = "session.queue.setDrainPaused"; + /// `session.queue.sendNow` + pub const SESSION_QUEUE_SENDNOW: &str = "session.queue.sendNow"; + /// `session.queue.hasPending` + pub const SESSION_QUEUE_HASPENDING: &str = "session.queue.hasPending"; + /// `session.queue.beginDeferredIdleDrain` + pub const SESSION_QUEUE_BEGINDEFERREDIDLEDRAIN: &str = "session.queue.beginDeferredIdleDrain"; + /// `session.queue.finishDeferredIdleDrain` + pub const SESSION_QUEUE_FINISHDEFERREDIDLEDRAIN: &str = "session.queue.finishDeferredIdleDrain"; + /// `session.queue.deferSessionIdle` + pub const SESSION_QUEUE_DEFERSESSIONIDLE: &str = "session.queue.deferSessionIdle"; /// `session.queue.removeMostRecent` pub const SESSION_QUEUE_REMOVEMOSTRECENT: &str = "session.queue.removeMostRecent"; /// `session.queue.clear` pub const SESSION_QUEUE_CLEAR: &str = "session.queue.clear"; + /// `session.queue.consumeSystemNotifications` + pub const SESSION_QUEUE_CONSUMESYSTEMNOTIFICATIONS: &str = + "session.queue.consumeSystemNotifications"; + /// `session.queue.enqueueResumePending` + pub const SESSION_QUEUE_ENQUEUERESUMEPENDING: &str = "session.queue.enqueueResumePending"; + /// `session.queue.process` + pub const SESSION_QUEUE_PROCESS: &str = "session.queue.process"; /// `session.eventLog.read` pub const SESSION_EVENTLOG_READ: &str = "session.eventLog.read"; /// `session.eventLog.tail` @@ -553,6 +653,8 @@ pub mod rpc_methods { pub const SESSION_EVENTLOG_RELEASEINTEREST: &str = "session.eventLog.releaseInterest"; /// `session.usage.getMetrics` pub const SESSION_USAGE_GETMETRICS: &str = "session.usage.getMetrics"; + /// `session.limitPrediction.predict` + pub const SESSION_LIMITPREDICTION_PREDICT: &str = "session.limitPrediction.predict"; /// `session.remote.enable` pub const SESSION_REMOTE_ENABLE: &str = "session.remote.enable"; /// `session.remote.disable` @@ -565,6 +667,20 @@ pub mod rpc_methods { pub const SESSION_VISIBILITY_SET: &str = "session.visibility.set"; /// `session.schedule.list` pub const SESSION_SCHEDULE_LIST: &str = "session.schedule.list"; + /// `session.schedule.hydrate` + pub const SESSION_SCHEDULE_HYDRATE: &str = "session.schedule.hydrate"; + /// `session.schedule.hasSelfPaced` + pub const SESSION_SCHEDULE_HASSELFPACED: &str = "session.schedule.hasSelfPaced"; + /// `session.schedule.add` + pub const SESSION_SCHEDULE_ADD: &str = "session.schedule.add"; + /// `session.schedule.addCron` + pub const SESSION_SCHEDULE_ADDCRON: &str = "session.schedule.addCron"; + /// `session.schedule.addAt` + pub const SESSION_SCHEDULE_ADDAT: &str = "session.schedule.addAt"; + /// `session.schedule.addSelfPaced` + pub const SESSION_SCHEDULE_ADDSELFPACED: &str = "session.schedule.addSelfPaced"; + /// `session.schedule.rearmSelfPaced` + pub const SESSION_SCHEDULE_REARMSELFPACED: &str = "session.schedule.rearmSelfPaced"; /// `session.schedule.stop` pub const SESSION_SCHEDULE_STOP: &str = "session.schedule.stop"; /// `providerToken.getToken` @@ -595,6 +711,8 @@ pub mod rpc_methods { pub const SESSIONFS_RENAME: &str = "sessionFs.rename"; /// `sessionFs.sqliteQuery` pub const SESSIONFS_SQLITEQUERY: &str = "sessionFs.sqliteQuery"; + /// `sessionFs.sqliteTransaction` + pub const SESSIONFS_SQLITETRANSACTION: &str = "sessionFs.sqliteTransaction"; /// `sessionFs.sqliteExists` pub const SESSIONFS_SQLITEEXISTS: &str = "sessionFs.sqliteExists"; /// `canvas.open` @@ -838,7 +956,7 @@ pub struct AgentDiscoveryPathList { pub paths: Vec, } -/// Custom agent metadata, including identifiers, display details, source, tools, model, MCP servers, skills, and file path. +/// Agent metadata, including identifiers, display details, source, tools, model, MCP servers, skills, and file path. /// ///
/// @@ -865,14 +983,17 @@ pub struct AgentInfo { ///
#[serde(skip_serializing_if = "Option::is_none")] pub mcp_servers: Option>, - /// Preferred model id for this agent. When omitted, inherits the outer agent's model. + /// Authored preferred model id for this agent. Runtime model selection may choose a different model; omitted means no authored preference. #[serde(skip_serializing_if = "Option::is_none")] pub model: Option, - /// Unique identifier of the custom agent + /// Name of the agent. Use `id` as the stable selection identifier. pub name: String, /// Absolute local file path of the agent definition. Only set for file-based agents loaded from disk; remote agents do not have a path. #[serde(skip_serializing_if = "Option::is_none")] pub path: Option, + /// Authored base prompt for the agent. Runtime prompt assembly may add dynamic context at invocation time. Omitted from `session.agent.list` unless `includePrompt` is true. + #[serde(skip_serializing_if = "Option::is_none")] + pub prompt: Option, /// Skill names preloaded into this agent's context. Omitted means none. #[serde(skip_serializing_if = "Option::is_none")] pub skills: Option>, @@ -902,7 +1023,7 @@ pub struct AgentGetCurrentResult { pub agent: AgentInfo, } -/// Custom agents available to the session. +/// Agents available to the session. /// ///
/// @@ -913,10 +1034,29 @@ pub struct AgentGetCurrentResult { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AgentList { - /// Available custom agents + /// Available agents pub agents: Vec, } +/// Controls whether built-in agents and authored prompt text are included. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AgentListRequest { + /// When true, request the session's configured built-in agents alongside custom agents. Listing applies feature, context, inclusion, exclusion, and user-disabled-agent policy, but does not evaluate transient invocation requirements such as model availability. Built-in metadata may be omitted when the session cannot project it, such as a relay session. + #[serde(skip_serializing_if = "Option::is_none")] + pub include_built_in_agents: Option, + /// When true, request authored base prompt text on each AgentInfo. Prompt text may be omitted when unavailable, such as for agents projected through a relay session. + #[serde(skip_serializing_if = "Option::is_none")] + pub include_prompt: Option, +} + /// Full registry entry for the spawned child. Lets the controller call `handleLiveTargetSelected(entry)` directly without re-reading the registry (avoids a TOCTOU window). /// ///
@@ -1183,6 +1323,23 @@ pub struct AgentSelectResult { pub agent: AgentInfo, } +/// An in-memory authored prompt override for an available agent. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AgentSetPromptRequest { + /// Stable effective agent id. Plugin namespace separators are normalized. + pub id: String, + /// Replacement authored prompt. Empty text is valid. + pub prompt: String, +} + /// Optional project paths to include when enumerating agent discovery directories. /// ///
@@ -1253,6 +1410,8 @@ pub struct AllowAllPermissionState { pub struct CopilotUserResponseEndpoints { #[serde(skip_serializing_if = "Option::is_none")] pub api: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub exp: Option, #[serde(rename = "origin-tracker", skip_serializing_if = "Option::is_none")] pub origin_tracker: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -2092,6 +2251,36 @@ pub struct AttachmentSelection { pub r#type: AttachmentSelectionType, } +/// A well-known model in the runtime's built-in catalog. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BuiltInModelCatalogEntry { + /// Well-known runtime model ID suitable for `ProviderConfig.modelId` or `ProviderModelConfig.modelId`. This is not necessarily the provider-facing deployment or model name and does not indicate CAPI entitlement or provider availability. + pub id: String, +} + +/// The running runtime's complete catalog of well-known built-in model IDs, including supported models and additional IDs with built-in metadata. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BuiltInModelCatalog { + /// Built-in model entries. + pub models: Vec, +} + /// Cancellation result for a user-requested shell command. /// ///
@@ -2853,7 +3042,7 @@ pub struct ConnectRemoteSessionParams { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct ConnectRequest { - /// Opt this connection in to GitHub telemetry forwarding for its lifetime. When set, the runtime forwards every internal telemetry event it emits — across all sessions, plus sessionless events — to this connection over the `gitHubTelemetry.event` notification, in addition to the runtime's normal GitHub/CTS emission (dual-write). Intended for first-party hosts that re-emit the events into their own telemetry stores. Both unrestricted and restricted events are forwarded, each tagged with a `restricted` discriminator; a backstop drops restricted events when restricted telemetry is disabled. + /// Opt this connection in to GitHub telemetry forwarding for its lifetime. When set, the runtime forwards every internal telemetry event it emits — across all sessions, plus sessionless events — to this connection over the `gitHubTelemetry.event` notification. Regular events are also written to the runtime's normal GitHub/CTS path (dual-write); host-only compatibility events are forward-only and intentionally skip that path. Intended for first-party hosts that re-emit the events into their own telemetry stores. Both unrestricted and restricted events are forwarded, each tagged with a `restricted` discriminator; a backstop drops restricted events when restricted telemetry is disabled — using the process-global gate for ordinary events and an explicit session-scoped decision for host-only events. #[serde(skip_serializing_if = "Option::is_none")] pub enable_git_hub_telemetry_forwarding: Option, /// Connection token; required when the server was started with COPILOT_CONNECTION_TOKEN @@ -2880,6 +3069,55 @@ pub(crate) struct ConnectResult { pub version: String, } +/// Local file system absolute paths within the session working directory to check against its content-exclusion policy. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ContentExclusionCheckPathsRequest { + /// Local file system absolute paths within the session working directory to check. Results are returned in the same order, including duplicates. + pub paths: Vec, +} + +/// Content-exclusion decision for one requested path. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ContentExclusionPathCheck { + /// Whether the session's complete content-exclusion policy excludes the path. + pub excluded: bool, + /// The path supplied by the caller. + pub path: String, +} + +/// Batch content-exclusion result. Callers must fail closed when policy evaluation is unavailable. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ContentExclusionCheckPathsResult { + /// Whether the session's policy service was available for the complete batch. When false, checks is empty and callers must treat every requested path as excluded. + pub available: bool, + /// Per-path decisions in request order. Empty when available is false. + pub checks: Vec, +} + /// A single large message currently in context. /// ///
@@ -3135,6 +3373,94 @@ pub struct DebugCollectLogsResult { pub skipped_entries: Option>, } +/// Installed plugin that contributes a discovered extension. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DiscoveredExtensionPlugin { + /// Installed plugin name + pub name: String, +} + +/// Discovered extension metadata and persistent enablement state. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DiscoveredExtension { + /// Whether this extension's persistent per-ID preference is enabled + pub enabled: bool, + /// Source-qualified ID accepted by both server and session extension enablement methods + pub id: String, + /// Human-readable extension name + pub name: String, + /// Absolute path to the extension entry module, suitable for revealing it in a file manager + pub path: String, + /// Containing plugin metadata for plugin-contributed extensions + #[serde(skip_serializing_if = "Option::is_none")] + pub plugin: Option, + /// Discovery source + pub source: DiscoveredExtensionSource, +} + +/// Extensions discovered from persisted Copilot home state and their effective loading mode. Launch-scoped additional plugins are not included. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DiscoveredExtensions { + /// Discovered user and enabled installed-plugin extensions from persisted Copilot home state + pub extensions: Vec, + /// Effective extension loading mode. Defaults to load_and_augment when unset. + pub mode: DiscoveredExtensionMode, +} + +/// Source-qualified extension identifiers to persistently disable for future sessions. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DiscoveredExtensionsDisableRequest { + /// Source-qualified user or plugin extension IDs to disable + pub ids: Vec, +} + +/// Source-qualified extension identifiers to persistently enable for future sessions. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DiscoveredExtensionsEnableRequest { + /// Source-qualified user or plugin extension IDs to enable + pub ids: Vec, +} + /// MCP server discovered by `mcp.discover`, with config source, optional plugin source, transport type, and enabled state. /// ///
@@ -3231,19 +3557,28 @@ pub struct EnvAuthInfo { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct EventLogReadRequest { + /// Optional non-empty list of subagent identifiers. When provided, only events owned by one of these agents are returned; ownership recognizes the event envelope's agentId plus legacy data.agentId and data.parentToolCallId markers. This filter takes precedence over agentScope. + #[serde(skip_serializing_if = "Option::is_none")] + pub agent_ids: Option>, /// Agent-scope filter: 'primary' returns only main-agent events plus events whose type starts with 'subagent.' (matching the typed-subscription default behavior); 'all' returns events from all agents (matching wildcard-subscription behavior). Default is 'all' to preserve wildcard semantics for catch-up callers. #[serde(skip_serializing_if = "Option::is_none")] pub agent_scope: Option, /// Opaque cursor returned by a previous read. Omit on the first call to start from the beginning of the session's persisted history. #[serde(skip_serializing_if = "Option::is_none")] pub cursor: Option, + /// Direction to page through the session's persisted event history. 'forward' (default) pages from the cursor toward newer events (or from the start of history when no cursor is given). 'backward' enables tail-first reads: with no cursor it returns the NEWEST `max` events, and the returned cursor pages toward OLDER events on subsequent backward reads. Events within a returned batch are always in chronological (oldest-to-newest) order, even for a backward read. Backward reads cover PERSISTED history only; ephemeral events are never returned by a backward read. `direction` selects the INITIAL read only: the returned cursor is self-describing, so a continuation read pages in the cursor's own direction regardless of the `direction` passed alongside it — a forward cursor always pages forward and a backward cursor always pages backward. Pass the direction that matches the cursor to avoid confusion. + #[serde(skip_serializing_if = "Option::is_none")] + pub direction: Option, + /// When false, skip ephemeral events entirely and return only durable (persisted) events. History-backfill callers that discard ephemerals anyway should set this so the read is bounded by the durable log length instead of racing the ephemeral ring on a busy session. Defaults to true (ephemerals are interleaved with durable events in creation order). Ignored by backward reads, which always cover persisted history only. + #[serde(skip_serializing_if = "Option::is_none")] + pub include_ephemeral: Option, /// Maximum number of events to return in this batch (1–1000, default 200). #[serde(skip_serializing_if = "Option::is_none")] pub max: Option, /// Either '*' to receive all event types, or a non-empty list of event types to receive #[serde(skip_serializing_if = "Option::is_none")] pub types: Option, - /// Milliseconds to wait for new events when the cursor is at the tail of history. 0 (default) returns immediately even if no events are available. Capped at 30000ms. Ephemeral events that arrive during the wait are delivered in this batch but are NOT replayable on a subsequent read (use a non-zero waitMs in your next call to capture future ephemerals as they happen). + /// Milliseconds to wait for new events when the cursor is at the tail of history. 0 (default) returns immediately even if no events are available. Capped at 30000ms. Ephemeral events that arrive during the wait are delivered in this batch but are NOT replayable on a subsequent read (use a non-zero waitMs in your next call to capture future ephemerals as they happen). This applies to forward reads only: a backward read always returns immediately and ignores `waitMs`, because backward paging covers persisted history only while new events append at the tail (the opposite end from a backward page), so no blocking or ephemeral delivery can occur. #[serde(skip_serializing_if = "Option::is_none")] pub wait_ms: Option, } @@ -3289,13 +3624,13 @@ pub struct EventLogTailResult { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct EventsReadResult { - /// Opaque cursor for the next read. Pass back unchanged in the next read.cursor to continue from where this read left off. Always present, even when no events were returned. + /// Opaque cursor for the next read. Pass back unchanged in the next read.cursor to continue from where this read left off. Always present, even when no events were returned. For a backward read this cursor pages toward OLDER events; keep passing `direction: backward` with it (the cursor is also self-describing, so backward paging continues correctly). pub cursor: String, - /// Cursor status: 'ok' means the cursor was applied successfully; 'expired' means the cursor referred to an event that no longer exists in history (e.g. truncated or compacted away) and the read started from the beginning of the remaining history. + /// Cursor status: 'ok' means the cursor was applied successfully; 'expired' means the cursor referred to an event that no longer exists in history (e.g. truncated or compacted away) and the read fell back to a boundary of the remaining history. For a forward read the fallback starts from the beginning of the remaining history; for a backward read it falls back to the tail (the newest window). Because the fallback page is a fresh boundary snapshot rather than a continuation of the requested cursor, it may overlap events the consumer has already rendered — a backward fallback to the tail in particular can repeat the newest window. On 'expired', consumers should reset or rebase their local pagination state (or deduplicate by event id) before continuing from the returned cursor rather than blindly appending/prepending the fallback page. pub cursor_status: EventsCursorStatus, - /// Events are delivered in two batches per read: persisted events first (in append order), then ephemeral events (in seq order). When `waitMs > 0` and the catch-up batches were empty, post-wait events follow the same two-batch ordering. Persisted and ephemeral events do not interleave within a single read. + /// Session events for this batch, merged into a single stream in creation order: durable (persisted) events and ephemeral events interleave exactly as they were emitted. Set `includeEphemeral: false` to receive only durable events. Ephemeral events are never replayable once pruned from the in-memory ring, so a consumer that needs them should keep reading with a non-zero `waitMs`. For a backward (tail-first) read, the returned window contains persisted events only, still in chronological (oldest-to-newest) append order. pub events: Vec, - /// True when the read returned `max` events and more events are available immediately. When false, the next read with a non-zero `waitMs` will block until a new event arrives or the wait expires. + /// True when more events are available in the read's direction. For a forward read, true means the batch returned `max` events and more are available immediately. For a backward read, true means older persisted events remain before the returned window. pub has_more: bool, } @@ -3375,6 +3710,62 @@ pub struct ExtensionContextPushInput { pub r#type: ExtensionContextPushInputType, } +/// Opaque integrator-owned process launch profile for one extension entrypoint. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ExtensionLaunchProfile { + /// Opaque integrator-defined arguments passed to the executable. The runtime does not append the extension entrypoint. + pub args: Vec, + /// Opaque integrator-defined environment variables. Runtime-owned COPILOT_SDK_PATH, SESSION_ID, and COPILOT_EXTENSION_PARENT_PID values take precedence. + pub env: HashMap, + /// Executable used to launch the extension entrypoint. + pub executable: String, +} + +/// A discovered extension entrypoint that the registered integrator may classify and resolve to an opaque launch profile. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ExtensionLaunchProviderResolveRequest { + /// Source-qualified extension identifier. + pub id: String, + /// Absolute path to the discovered extension entrypoint. + pub module_path: String, + /// Human-readable extension name. + pub name: String, + /// Discovery source for the extension entrypoint. + pub source: ExtensionSource, +} + +/// The launch profile for a supported entrypoint. Omit launch when the provider does not support the entrypoint. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ExtensionLaunchProviderResolveResult { + /// Opaque launch profile, omitted when this provider does not support the entrypoint. + #[serde(skip_serializing_if = "Option::is_none")] + pub launch: Option, +} + /// Extensions discovered for the session, with their current status. /// ///
@@ -3702,12 +4093,21 @@ pub struct FactoryAckResult {} #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct FactoryAgentOptions { + /// Optional custom agent name for the subagent. This field is accepted but not yet honored. + #[serde(skip_serializing_if = "Option::is_none")] + pub agent: Option, + /// Optional context tier for the subagent. This field is accepted but not yet honored. + #[serde(skip_serializing_if = "Option::is_none")] + pub context_tier: Option, /// Optional label distinguishing otherwise identical memoized agent calls. #[serde(skip_serializing_if = "Option::is_none")] pub label: Option, /// Optional model identifier for the subagent. #[serde(skip_serializing_if = "Option::is_none")] pub model: Option, + /// Optional reasoning effort for the subagent. This field is accepted but not yet honored. + #[serde(skip_serializing_if = "Option::is_none")] + pub reasoning_effort: Option, /// Optional JSON Schema for structured agent output. #[serde(skip_serializing_if = "Option::is_none")] pub schema: Option, @@ -3724,6 +4124,8 @@ pub struct FactoryAgentOptions { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct FactoryAgentRequest { + /// Opaque token identifying the current factory execution attempt. + pub execution_token: String, /// Factory run identifier that owns the subagent. pub factory_run_id: String, /// Subagent execution options. @@ -3748,7 +4150,7 @@ pub struct FactoryAgentResult { pub result: Option, } -/// Parameters for cancelling a factory run. +/// Prompt-safe durable identity and live status for a direct factory agent. /// ///
/// @@ -3758,12 +4160,28 @@ pub struct FactoryAgentResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FactoryCancelRequest { - /// Factory run identifier. +pub struct FactoryAgentSummary { + pub active_ms: i64, + #[serde(skip_serializing_if = "Option::is_none")] + pub activity: Option, + pub agent_id: String, + pub agent_type: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub completed_at: Option, + pub label: String, + pub phase_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub requested_model: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub resolved_model: Option, pub run_id: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub started_at: Option, + pub status: String, + pub tool_call_id: String, } -/// Parameters sent to the owning extension to execute a factory closure. +/// Parameters for cancelling a factory run. /// ///
/// @@ -3773,18 +4191,12 @@ pub struct FactoryCancelRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FactoryExecuteRequest { - /// Target session identifier - pub session_id: SessionId, - /// Registered factory name. - pub name: String, +pub struct FactoryCancelRequest { /// Factory run identifier. pub run_id: String, - /// Factory input value. - pub args: serde_json::Value, } -/// Result returned by an extension factory closure. +/// Current factory phase identity. /// ///
/// @@ -3794,12 +4206,12 @@ pub struct FactoryExecuteRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FactoryExecuteResult { - /// Factory result value. - pub result: serde_json::Value, +pub struct FactoryCurrentPhase { + pub id: String, + pub ordinal: Option, } -/// Parameters for retrieving a factory run. +/// Declared or approved factory resource ceilings. /// ///
/// @@ -3809,12 +4221,18 @@ pub struct FactoryExecuteResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FactoryGetRunRequest { - /// Factory run identifier. - pub run_id: String, +pub struct FactoryDeclaredLimits { + #[serde(skip_serializing_if = "Option::is_none")] + pub max_ai_credits: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub max_concurrent_subagents: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub max_total_subagents: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub timeout_seconds: Option, } -/// Parameters for reading a factory journal entry. +/// Parameters sent to the owning extension to execute a factory closure. /// ///
/// @@ -3824,14 +4242,20 @@ pub struct FactoryGetRunRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FactoryJournalGetRequest { - /// Namespaced journal key. - pub key: String, +pub struct FactoryExecuteRequest { + /// Target session identifier + pub session_id: SessionId, + /// Registered factory name. + pub name: String, /// Factory run identifier. pub run_id: String, + /// Opaque token identifying this factory execution attempt. + pub execution_token: String, + /// Factory input value. + pub args: serde_json::Value, } -/// Result of reading a factory journal entry. +/// Result returned by an extension factory closure. /// ///
/// @@ -3841,15 +4265,13 @@ pub struct FactoryJournalGetRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FactoryJournalGetResult { - /// Whether the journal contained the requested key. - pub hit: bool, - /// Cached JSON result. The hit field distinguishes a cached JSON null from a miss. +pub struct FactoryExecuteResult { + /// Factory result value. #[serde(skip_serializing_if = "Option::is_none")] - pub result_json: Option, + pub result: Option, } -/// Parameters for storing a factory journal entry. +/// Parameters for paging factory progress. /// ///
/// @@ -3859,16 +4281,24 @@ pub struct FactoryJournalGetResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FactoryJournalPutRequest { - /// Namespaced journal key. - pub key: String, - /// JSON result to memoize. - pub result_json: serde_json::Value, +pub struct FactoryGetRunProgressRequest { + /// Exclusive forward cursor. + #[serde(skip_serializing_if = "Option::is_none")] + pub after_seq: Option, + /// Exclusive backward cursor. + #[serde(skip_serializing_if = "Option::is_none")] + pub before_seq: Option, + /// Maximum records to return. Defaults to 200 and is capped at 500. + #[serde(skip_serializing_if = "Option::is_none")] + pub limit: Option, + /// Optional phase identifier used to scope records and cursors. + #[serde(skip_serializing_if = "Option::is_none")] + pub phase_id: Option, /// Factory run identifier. pub run_id: String, } -/// One ordered factory progress line. +/// Parameters for retrieving a factory run. /// ///
/// @@ -3878,16 +4308,12 @@ pub struct FactoryJournalPutRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FactoryLogLine { - /// Progress line kind. - pub kind: FactoryLogLineKind, - /// Monotonic sequence number within the factory run. - pub seq: i64, - /// Progress text. - pub text: String, +pub struct FactoryGetRunRequest { + /// Factory run identifier. + pub run_id: String, } -/// Parameters for recording factory progress. +/// Parameters for reading a factory journal entry. /// ///
/// @@ -3897,14 +4323,16 @@ pub struct FactoryLogLine { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FactoryLogRequest { - /// Ordered progress lines to append. - pub lines: Vec, +pub struct FactoryJournalGetRequest { + /// Opaque token identifying the current factory execution attempt. + pub execution_token: String, + /// Namespaced journal key. + pub key: String, /// Factory run identifier. pub run_id: String, } -/// Wire-only per-invocation factory resource ceiling overrides. +/// Result of reading a factory journal entry. /// ///
/// @@ -3914,19 +4342,15 @@ pub struct FactoryLogRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FactoryRunLimits { - /// Maximum number of factory subagents that may run concurrently. - #[serde(skip_serializing_if = "Option::is_none")] - pub max_concurrent_subagents: Option, - /// Maximum total number of factory subagents that may be admitted. - #[serde(skip_serializing_if = "Option::is_none")] - pub max_total_subagents: Option, - /// Factory active-run timeout in milliseconds. +pub struct FactoryJournalGetResult { + /// Whether the journal contained the requested key. + pub hit: bool, + /// Cached JSON result. The hit field distinguishes a cached JSON null from a miss. #[serde(skip_serializing_if = "Option::is_none")] - pub timeout: Option, + pub result_json: Option, } -/// Options controlling factory invocation. +/// Parameters for storing a factory journal entry. /// ///
/// @@ -3936,16 +4360,18 @@ pub struct FactoryRunLimits { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct RunOptions { - /// Per-invocation resource ceiling overrides. - #[serde(skip_serializing_if = "Option::is_none")] - pub limits: Option, - /// Run identifier whose journal and progress should seed this resumed run. - #[serde(skip_serializing_if = "Option::is_none")] - pub resume_from_run_id: Option, +pub struct FactoryJournalPutRequest { + /// Opaque token identifying the current factory execution attempt. + pub execution_token: String, + /// Namespaced journal key. + pub key: String, + /// JSON result to memoize. + pub result_json: serde_json::Value, + /// Factory run identifier. + pub run_id: String, } -/// Parameters for invoking a registered factory. +/// Parameters for paging factory runs. /// ///
/// @@ -3955,17 +4381,19 @@ pub struct RunOptions { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FactoryRunRequest { - /// Factory input value. - pub args: serde_json::Value, - /// Registered factory name. - pub name: String, - /// Factory invocation options. +pub struct FactoryListRunsRequest { + /// Exclusive forward cursor. #[serde(skip_serializing_if = "Option::is_none")] - pub options: Option, + pub after_seq: Option, + /// Exclusive backward cursor. + #[serde(skip_serializing_if = "Option::is_none")] + pub before_seq: Option, + /// Maximum terminal runs to return. Defaults to 200 and is capped at 500. + #[serde(skip_serializing_if = "Option::is_none")] + pub limit: Option, } -/// Complete current or terminal factory run envelope. +/// Durable factory resource consumption. /// ///
/// @@ -3975,29 +4403,13 @@ pub struct FactoryRunRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FactoryRunResult { - /// Error message for an errored run. - #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, - /// Machine-readable failure details for an errored run. - #[serde(skip_serializing_if = "Option::is_none")] - pub failure: Option, - /// Reason for a halted or cancelled run. - #[serde(skip_serializing_if = "Option::is_none")] - pub reason: Option, - /// Completed factory result. - #[serde(skip_serializing_if = "Option::is_none")] - pub result: Option, - /// Factory run identifier. - pub run_id: String, - /// Partial journal and progress snapshot for a halted, cancelled, or errored run. - #[serde(skip_serializing_if = "Option::is_none")] - pub snapshot: Option, - /// Current or terminal factory run status. - pub status: FactoryRunStatus, +pub struct FactoryRunConsumed { + pub active_ms: i64, + pub nano_aiu: i64, + pub subagents: i64, } -/// Optional user prompt to combine with the fleet orchestration instructions. +/// Prompt-safe terminal factory outcome. /// ///
/// @@ -4007,13 +4419,18 @@ pub struct FactoryRunResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FleetStartRequest { - /// Optional user prompt to combine with fleet instructions +pub struct FactoryRunTerminal { #[serde(skip_serializing_if = "Option::is_none")] - pub prompt: Option, + pub error: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub failure: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub reason: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub result_preview: Option, } -/// Indicates whether fleet mode was successfully activated. +/// Durable factory run summary with read-time live overlays. /// ///
/// @@ -4023,12 +4440,29 @@ pub struct FleetStartRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FleetStartResult { - /// Whether fleet mode was successfully activated - pub started: bool, +pub struct FactoryRunSummary { + pub active_segment_started_at: Option, + pub approved: Option, + pub completed_at: Option, + pub consumed: FactoryRunConsumed, + pub created_at: i64, + pub current_phase: Option, + pub declared_limits: FactoryDeclaredLimits, + pub declared_phase_count: i64, + pub description: String, + pub factory_name: String, + pub live_agent_count: i64, + pub observed_at: i64, + pub revision: i64, + pub run_id: String, + pub started_at: Option, + pub status: FactoryRunStatus, + pub terminal: Option, + pub total_spawned_agent_count: i64, + pub updated_at: i64, } -/// Folder path to add to trusted folders. +/// A page of factory runs in durable creation order. /// ///
/// @@ -4038,12 +4472,23 @@ pub struct FleetStartResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FolderTrustAddParams { - /// Folder path to mark as trusted - pub path: String, +pub struct FactoryListRunsResult { + /// Whether terminal runs newer than this page exist. + #[serde(skip_serializing_if = "Option::is_none")] + pub has_more_newer: Option, + /// Newest terminal-run cursor in this page, or null when the terminal window is empty. + #[serde(skip_serializing_if = "Option::is_none")] + pub newest_seq: Option, + /// Oldest terminal-run cursor in this page, or null when the terminal window is empty. + #[serde(skip_serializing_if = "Option::is_none")] + pub oldest_seq: Option, + /// Number of terminal runs older than this page. + #[serde(skip_serializing_if = "Option::is_none")] + pub omitted_older: Option, + pub runs: Vec, } -/// Folder path to check for trust. +/// One ordered factory progress line. /// ///
/// @@ -4053,12 +4498,16 @@ pub struct FolderTrustAddParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FolderTrustCheckParams { - /// Folder path to check - pub path: String, +pub struct FactoryLogLine { + /// Progress line kind. + pub kind: FactoryLogLineKind, + /// Monotonic sequence number within the factory run. + pub seq: i64, + /// Progress text. + pub text: String, } -/// Folder trust check result. +/// Parameters for recording factory progress. /// ///
/// @@ -4068,12 +4517,16 @@ pub struct FolderTrustCheckParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct FolderTrustCheckResult { - /// Whether the folder is trusted - pub trusted: bool, +pub struct FactoryLogRequest { + /// Opaque token identifying the current factory execution attempt. + pub execution_token: String, + /// Ordered progress lines to append. + pub lines: Vec, + /// Factory run identifier. + pub run_id: String, } -/// Authentication-info variant for GitHub CLI credentials, carrying host, login, and the `gh auth token` value. +/// Durable lifecycle and timing for one factory phase. /// ///
/// @@ -4083,21 +4536,26 @@ pub struct FolderTrustCheckResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct GhCliAuthInfo { - /// Snapshot of the authenticated user's Copilot subscription info, if known. Mirrors the GitHub API `/copilot_internal/v2/token` user response shape — the runtime trusts this verbatim and does not re-fetch when set. +pub struct FactoryPhaseObservation { + pub accumulated_active_ms: i64, #[serde(skip_serializing_if = "Option::is_none")] - pub copilot_user: Option, - /// Authentication host. - pub host: String, - /// User login as reported by `gh auth status`. - pub login: String, - /// The token returned by `gh auth token`. Treat as a secret. - pub token: String, - /// Authentication via the `gh` CLI's saved credentials. - pub r#type: GhCliAuthInfoType, + pub completed_at: Option, + pub current_active_ms: i64, + #[serde(skip_serializing_if = "Option::is_none")] + pub detail: Option, + pub entry_count: i64, + pub id: String, + pub last_entered_run_attempt: i64, + pub live_agent_count: i64, + pub ordinal: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub started_at: Option, + pub status: FactoryPhaseStatus, + pub title: String, + pub total_agent_count: i64, } -/// Client environment metadata describing the process that produced a telemetry event. +/// One durable factory progress record. /// ///
/// @@ -4107,40 +4565,22 @@ pub struct GhCliAuthInfo { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct GitHubTelemetryClientInfo { - /// Copilot CLI version string. - #[serde(rename = "cli_version")] - pub cli_version: String, - /// Name of the client application. - #[serde(rename = "client_name", skip_serializing_if = "Option::is_none")] - pub client_name: Option, - /// Type of client. - #[serde(rename = "client_type", skip_serializing_if = "Option::is_none")] - pub client_type: Option, - /// Copilot subscription plan, when known. - #[serde(rename = "copilot_plan", skip_serializing_if = "Option::is_none")] - pub copilot_plan: Option, - /// Stable machine identifier for the device. - #[serde(rename = "dev_device_id", skip_serializing_if = "Option::is_none")] - pub dev_device_id: Option, - /// Whether the user is a GitHub/Microsoft staff member. - #[serde(rename = "is_staff", skip_serializing_if = "Option::is_none")] - pub is_staff: Option, - /// Node.js runtime version string. - #[serde(rename = "node_version")] - pub node_version: String, - /// Operating system architecture (e.g. arm64, x64). - #[serde(rename = "os_arch")] - pub os_arch: String, - /// Operating system platform (e.g. darwin, linux, win32). - #[serde(rename = "os_platform")] - pub os_platform: String, - /// Operating system version string. - #[serde(rename = "os_version")] - pub os_version: String, +pub struct FactoryProgressLine { + /// Resume attempt that emitted this record. + pub attempt: i64, + /// Progress record kind. + pub kind: FactoryLogLineKind, + /// Phase active when the record was emitted, or null before any phase. + pub phase_id: Option, + /// Epoch milliseconds when the record was persisted. + pub recorded_at: i64, + /// Global monotonic sequence number within the run. + pub seq: i64, + /// Prompt-safe progress text. + pub text: String, } -/// A single telemetry event in the runtime's native GitHub-shaped telemetry format, forwarded verbatim to opted-in hosts. The `restricted` flag on the enclosing GitHubTelemetryNotification distinguishes standard from restricted events; the payload shape is identical for both. +/// A bidirectional page of factory progress. /// ///
/// @@ -4150,43 +4590,17 @@ pub struct GitHubTelemetryClientInfo { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct GitHubTelemetryEvent { - /// Client environment metadata. - #[serde(skip_serializing_if = "Option::is_none")] - pub client: Option, - /// Copilot tracking ID for user-level attribution. - #[serde( - rename = "copilot_tracking_id", - skip_serializing_if = "Option::is_none" - )] - pub copilot_tracking_id: Option, - /// Timestamp when the event was created (ISO 8601 format). - #[serde(rename = "created_at", skip_serializing_if = "Option::is_none")] - pub created_at: Option, - /// Experiment assignment context. - #[serde( - rename = "exp_assignment_context", - skip_serializing_if = "Option::is_none" - )] - pub exp_assignment_context: Option, - /// Feature flags enabled for this session, as a map from flag to value. - #[serde(skip_serializing_if = "Option::is_none")] - pub features: Option>, - /// Event type/kind (e.g. get_completion_with_tools_turn, tool_call_executed). - pub kind: String, - /// Numeric metrics as a map from key to value. - pub metrics: HashMap, - /// Reference to the model call that produced this event. - #[serde(rename = "model_call_id", skip_serializing_if = "Option::is_none")] - pub model_call_id: Option, - /// String-valued properties as a map from key to value. - pub properties: HashMap, - /// Session identifier the event belongs to. - #[serde(rename = "session_id", skip_serializing_if = "Option::is_none")] - pub session_id: Option, +pub struct FactoryProgressPage { + pub has_more_newer: bool, + pub has_more_older: bool, + pub newest_seq: Option, + pub oldest_seq: Option, + pub records: Vec, + /// Run revision reflected by this page. + pub revision: i64, } -/// Payload for a `gitHubTelemetry.event` notification: a single GitHub telemetry event the runtime forwards to a host connection that opted into telemetry forwarding during the `server.connect` handshake. +/// Wire-only per-invocation factory resource ceiling overrides. /// ///
/// @@ -4196,17 +4610,22 @@ pub struct GitHubTelemetryEvent { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct GitHubTelemetryNotification { - /// The telemetry event, in the runtime's native GitHub-shaped telemetry format. - pub event: GitHubTelemetryEvent, - /// Whether this is a restricted telemetry event (cli.restricted_telemetry). Hosts must route restricted events to first-party Microsoft stores only. - pub restricted: bool, - /// Session the telemetry event belongs to, when it is session-scoped. Omitted for sessionless events (for example, `server.sendTelemetry` calls with no session id), which are still forwarded to opted-in connections. +pub struct FactoryRunLimits { + /// Maximum AI credits consumed by factory subagents and their descendants. The post-paid ceiling is soft: parallel turns can settle beyond it before the run stops. #[serde(skip_serializing_if = "Option::is_none")] - pub session_id: Option, + pub max_ai_credits: Option, + /// Maximum number of factory subagents that may run concurrently. + #[serde(skip_serializing_if = "Option::is_none")] + pub max_concurrent_subagents: Option, + /// Maximum total number of factory subagents that may be admitted. + #[serde(skip_serializing_if = "Option::is_none")] + pub max_total_subagents: Option, + /// Maximum accumulated active-execution time in seconds. Active execution includes the entire extension body, subprocess waits, queued-agent waits, and sleeps; time between resumed attempts is not counted. + #[serde(skip_serializing_if = "Option::is_none")] + pub timeout_seconds: Option, } -/// Pending external tool call request ID, with the tool result or an error describing why it failed. +/// Parameters for resuming a factory run from its persisted identity. /// ///
/// @@ -4216,18 +4635,15 @@ pub struct GitHubTelemetryNotification { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct HandlePendingToolCallRequest { - /// Error message if the tool call failed - #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, - /// Request ID of the pending tool call - pub request_id: RequestId, - /// Tool call result (string or expanded result object) +pub struct FactoryResumeRequest { + /// Optional per-invocation resource ceiling overrides. #[serde(skip_serializing_if = "Option::is_none")] - pub result: Option, + pub limits: Option, + /// Factory run identifier. + pub run_id: String, } -/// Indicates whether the external tool call result was handled successfully. +/// Complete current or terminal factory run envelope. /// ///
/// @@ -4237,12 +4653,29 @@ pub struct HandlePendingToolCallRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct HandlePendingToolCallResult { - /// Whether the tool call result was handled successfully - pub success: bool, +pub struct FactoryRunResult { + /// Error message for an errored run. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + /// Machine-readable failure details for an errored run. + #[serde(skip_serializing_if = "Option::is_none")] + pub failure: Option, + /// Reason for a halted or cancelled run. + #[serde(skip_serializing_if = "Option::is_none")] + pub reason: Option, + /// Completed factory result. + #[serde(skip_serializing_if = "Option::is_none")] + pub result: Option, + /// Factory run identifier. + pub run_id: String, + /// Partial journal and progress snapshot for a halted, cancelled, or errored run. + #[serde(skip_serializing_if = "Option::is_none")] + pub snapshot: Option, + /// Current or terminal factory run status. + pub status: FactoryRunStatus, } -/// Indicates whether an in-progress manual compaction was aborted. +/// Resolved persisted factory identity and resumed run envelope. /// ///
/// @@ -4252,12 +4685,14 @@ pub struct HandlePendingToolCallResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct HistoryAbortManualCompactionResult { - /// Whether an in-progress manual compaction was aborted. False when no manual compaction was running, when its abort controller was already aborted, or when the session is remote. - pub aborted: bool, +pub struct FactoryResumeResult { + /// Persisted factory name resolved for the resumed run. + pub factory_name: String, + /// Terminal resumed run envelope. + pub run: FactoryRunResult, } -/// Indicates whether an in-progress background compaction was cancelled. +/// Full factory run observability detail. /// ///
/// @@ -4267,12 +4702,32 @@ pub struct HistoryAbortManualCompactionResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct HistoryCancelBackgroundCompactionResult { - /// Whether an in-progress background compaction was cancelled. False when no compaction was running, when the session is remote, or when the underlying processor was unavailable. - pub cancelled: bool, +pub struct FactoryRunDetail { + pub active_segment_started_at: Option, + pub agents: Vec, + pub approved: Option, + pub completed_at: Option, + pub consumed: FactoryRunConsumed, + pub created_at: i64, + pub current_phase: Option, + pub declared_limits: FactoryDeclaredLimits, + pub declared_phase_count: i64, + pub description: String, + pub factory_name: String, + pub live_agent_count: i64, + pub observed_at: i64, + pub phases: Vec, + pub progress: FactoryProgressPage, + pub revision: i64, + pub run_id: String, + pub started_at: Option, + pub status: FactoryRunStatus, + pub terminal: Option, + pub total_spawned_agent_count: i64, + pub updated_at: i64, } -/// Post-compaction context window usage breakdown +/// Options controlling factory invocation. /// ///
/// @@ -4282,25 +4737,16 @@ pub struct HistoryCancelBackgroundCompactionResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct HistoryCompactContextWindow { - /// Token count from non-system messages (user, assistant, tool) - #[serde(skip_serializing_if = "Option::is_none")] - pub conversation_tokens: Option, - /// Current total tokens in the context window (system + conversation + tool definitions) - pub current_tokens: i64, - /// Current number of messages in the conversation - pub messages_length: i64, - /// Token count from system message(s) +pub struct RunOptions { + /// Per-invocation resource ceiling overrides. #[serde(skip_serializing_if = "Option::is_none")] - pub system_tokens: Option, - /// Maximum token count for the model's context window - pub token_limit: i64, - /// Token count from tool definitions + pub limits: Option, + /// Run identifier whose journal and progress should seed this resumed run. #[serde(skip_serializing_if = "Option::is_none")] - pub tool_definitions_tokens: Option, + pub resume_from_run_id: Option, } -/// Optional compaction parameters. +/// Parameters for invoking a registered factory. /// ///
/// @@ -4310,13 +4756,17 @@ pub struct HistoryCompactContextWindow { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct HistoryCompactRequest { - /// Optional user-provided instructions to focus the compaction summary +pub struct FactoryRunRequest { + /// Factory input value. + pub args: serde_json::Value, + /// Registered factory name. + pub name: String, + /// Factory invocation options. #[serde(skip_serializing_if = "Option::is_none")] - pub custom_instructions: Option, + pub options: Option, } -/// Compaction outcome with the number of tokens and messages removed, summary text, and the resulting context window breakdown. +/// Optional user prompt to combine with the fleet orchestration instructions. /// ///
/// @@ -4326,22 +4776,13 @@ pub struct HistoryCompactRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct HistoryCompactResult { - /// Post-compaction context window usage breakdown - #[serde(skip_serializing_if = "Option::is_none")] - pub context_window: Option, - /// Number of messages removed during compaction - pub messages_removed: i64, - /// Whether compaction completed successfully - pub success: bool, - /// Summary text produced by compaction. Omitted when compaction did not produce a summary (e.g. failure path). +pub struct FleetStartRequest { + /// Optional user prompt to combine with fleet instructions #[serde(skip_serializing_if = "Option::is_none")] - pub summary_content: Option, - /// Number of tokens freed by compaction - pub tokens_removed: i64, + pub prompt: Option, } -/// Markdown summary of the conversation context (empty when not available). +/// Indicates whether fleet mode was successfully activated. /// ///
/// @@ -4351,12 +4792,12 @@ pub struct HistoryCompactResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct HistorySummarizeForHandoffResult { - /// Markdown summary of the conversation context produced by an LLM. Empty string when there are no messages or when the session does not support local summarization. - pub summary: String, +pub struct FleetStartResult { + /// Whether fleet mode was successfully activated + pub started: bool, } -/// Identifier of the event to truncate to; this event and all later events are removed. +/// Folder path to add to trusted folders. /// ///
/// @@ -4366,12 +4807,12 @@ pub struct HistorySummarizeForHandoffResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct HistoryTruncateRequest { - /// Event ID to truncate to. This event and all events after it are removed from the session. - pub event_id: String, +pub struct FolderTrustAddParams { + /// Folder path to mark as trusted + pub path: String, } -/// Number of events that were removed by the truncation. +/// Folder path to check for trust. /// ///
/// @@ -4381,12 +4822,12 @@ pub struct HistoryTruncateRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct HistoryTruncateResult { - /// Number of events that were removed - pub events_removed: i64, +pub struct FolderTrustCheckParams { + /// Folder path to check + pub path: String, } -/// Authentication-info variant for GitHub-internal HMAC auth, carrying the public GitHub host and HMAC secret. +/// Folder trust check result. /// ///
/// @@ -4396,37 +4837,36 @@ pub struct HistoryTruncateResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct HMACAuthInfo { - /// Snapshot of the authenticated user's Copilot subscription info, if known. Mirrors the GitHub API `/copilot_internal/v2/token` user response shape — the runtime trusts this verbatim and does not re-fetch when set. - #[serde(skip_serializing_if = "Option::is_none")] - pub copilot_user: Option, - /// HMAC secret used to sign requests. - pub hmac: String, - /// Authentication host. HMAC auth always targets the public GitHub host. - pub host: HMACAuthInfoHost, - /// HMAC-based authentication used by GitHub-internal services. - pub r#type: HMACAuthInfoType, -} - -/// Runtime-owned wire payload for a server-to-client hook callback invocation. -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub(crate) struct HookInvokeRequest { - #[doc(hidden)] - pub(crate) hook_type: HookType, - pub input: serde_json::Value, - pub session_id: SessionId, +pub struct FolderTrustCheckResult { + /// Whether the folder is trusted + pub trusted: bool, } -/// Optional output returned by an SDK callback hook. +/// Authentication-info variant for GitHub CLI credentials, carrying host, login, and the `gh auth token` value. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub(crate) struct HookInvokeResponse { +pub struct GhCliAuthInfo { + /// Snapshot of the authenticated user's Copilot subscription info, if known. Mirrors the GitHub API `/copilot_internal/v2/token` user response shape — the runtime trusts this verbatim and does not re-fetch when set. #[serde(skip_serializing_if = "Option::is_none")] - pub output: Option, + pub copilot_user: Option, + /// Authentication host. + pub host: String, + /// User login as reported by `gh auth status`. + pub login: String, + /// The token returned by `gh auth token`. Treat as a secret. + pub token: String, + /// Authentication via the `gh` CLI's saved credentials. + pub r#type: GhCliAuthInfoType, } -/// Installed plugin record from global state, with marketplace, version, install time, enabled state, cache path, and source. +/// Client environment metadata describing the process that produced a telemetry event. /// ///
/// @@ -4436,28 +4876,40 @@ pub(crate) struct HookInvokeResponse { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct InstalledPlugin { - /// Path where the plugin is cached locally - #[serde(rename = "cache_path", skip_serializing_if = "Option::is_none")] - pub cache_path: Option, - /// Whether the plugin is currently enabled - pub enabled: bool, - /// Installation timestamp - #[serde(rename = "installed_at")] - pub installed_at: String, - /// Marketplace the plugin came from (empty string for direct repo installs) - pub marketplace: String, - /// Plugin name - pub name: String, - /// Source for direct repo installs (when marketplace is empty) - #[serde(skip_serializing_if = "Option::is_none")] - pub source: Option, - /// Version installed (if available) - #[serde(skip_serializing_if = "Option::is_none")] - pub version: Option, +pub struct GitHubTelemetryClientInfo { + /// Copilot CLI version string. + #[serde(rename = "cli_version")] + pub cli_version: String, + /// Name of the client application. + #[serde(rename = "client_name", skip_serializing_if = "Option::is_none")] + pub client_name: Option, + /// Type of client. + #[serde(rename = "client_type", skip_serializing_if = "Option::is_none")] + pub client_type: Option, + /// Copilot subscription plan, when known. + #[serde(rename = "copilot_plan", skip_serializing_if = "Option::is_none")] + pub copilot_plan: Option, + /// Stable machine identifier for the device. + #[serde(rename = "dev_device_id", skip_serializing_if = "Option::is_none")] + pub dev_device_id: Option, + /// Whether the user is a GitHub/Microsoft staff member. + #[serde(rename = "is_staff", skip_serializing_if = "Option::is_none")] + pub is_staff: Option, + /// Node.js runtime version string. + #[serde(rename = "node_version")] + pub node_version: String, + /// Operating system architecture (e.g. arm64, x64). + #[serde(rename = "os_arch")] + pub os_arch: String, + /// Operating system platform (e.g. darwin, linux, win32). + #[serde(rename = "os_platform")] + pub os_platform: String, + /// Operating system version string. + #[serde(rename = "os_version")] + pub os_version: String, } -/// Information about an installed plugin tracked in global state. +/// A single telemetry event in the runtime's native GitHub-shaped telemetry format, forwarded verbatim to opted-in hosts. The `restricted` flag on the enclosing GitHubTelemetryNotification distinguishes standard from restricted events; the payload shape is identical for both. /// ///
/// @@ -4467,22 +4919,43 @@ pub struct InstalledPlugin { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct InstalledPluginInfo { - /// Opaque, stable hash identifying a direct (non-marketplace) install source. Present only for direct repo / URL / local installs; absent for marketplace plugins. Same source yields the same id; distinct sources never collide. +pub struct GitHubTelemetryEvent { + /// Client environment metadata. #[serde(skip_serializing_if = "Option::is_none")] - pub direct_source_id: Option, - /// Whether the plugin is currently enabled for new sessions - pub enabled: bool, - /// Marketplace the plugin came from. Empty string ("") for direct repo / URL / local installs. - pub marketplace: String, - /// Plugin name - pub name: String, - /// Installed version (when reported by the plugin manifest) + pub client: Option, + /// Copilot tracking ID for user-level attribution. + #[serde( + rename = "copilot_tracking_id", + skip_serializing_if = "Option::is_none" + )] + pub copilot_tracking_id: Option, + /// Timestamp when the event was created (ISO 8601 format). + #[serde(rename = "created_at", skip_serializing_if = "Option::is_none")] + pub created_at: Option, + /// Experiment assignment context. + #[serde( + rename = "exp_assignment_context", + skip_serializing_if = "Option::is_none" + )] + pub exp_assignment_context: Option, + /// Feature flags enabled for this session, as a map from flag to value. #[serde(skip_serializing_if = "Option::is_none")] - pub version: Option, + pub features: Option>, + /// Event type/kind (e.g. get_completion_with_tools_turn, tool_call_executed). + pub kind: String, + /// Numeric metrics as a map from key to value. + pub metrics: HashMap, + /// Reference to the model call that produced this event. + #[serde(rename = "model_call_id", skip_serializing_if = "Option::is_none")] + pub model_call_id: Option, + /// String-valued properties as a map from key to value. + pub properties: HashMap, + /// Session identifier the event belongs to. + #[serde(rename = "session_id", skip_serializing_if = "Option::is_none")] + pub session_id: Option, } -/// Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref, and optional subpath. +/// Payload for a `gitHubTelemetry.event` notification: a single GitHub telemetry event the runtime forwards to a host connection that opted into telemetry forwarding during the `server.connect` handshake. /// ///
/// @@ -4492,17 +4965,17 @@ pub struct InstalledPluginInfo { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct InstalledPluginSourceGitHub { - #[serde(skip_serializing_if = "Option::is_none")] - pub path: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub r#ref: Option, - pub repo: String, - /// Constant value. Always "github". - pub source: InstalledPluginSourceGitHubSource, +pub struct GitHubTelemetryNotification { + /// The telemetry event, in the runtime's native GitHub-shaped telemetry format. + pub event: GitHubTelemetryEvent, + /// Whether this is a restricted telemetry event (cli.restricted_telemetry). Hosts must route restricted events to first-party Microsoft stores only. + pub restricted: bool, + /// Session the telemetry event belongs to, when it is session-scoped. Omitted for sessionless events (for example, `server.sendTelemetry` calls with no session id), which are still forwarded to opted-in connections. + #[serde(skip_serializing_if = "Option::is_none")] + pub session_id: Option, } -/// Source descriptor for a direct local plugin install, with a local filesystem path. +/// Pending external tool call request ID, with the tool result or an error describing why it failed. /// ///
/// @@ -4512,13 +4985,18 @@ pub struct InstalledPluginSourceGitHub { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct InstalledPluginSourceLocal { - pub path: String, - /// Constant value. Always "local". - pub source: InstalledPluginSourceLocalSource, +pub struct HandlePendingToolCallRequest { + /// Error message if the tool call failed + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + /// Request ID of the pending tool call + pub request_id: RequestId, + /// Tool call result (string or expanded result object) + #[serde(skip_serializing_if = "Option::is_none")] + pub result: Option, } -/// Source descriptor for a direct URL plugin install, with URL, optional ref, and optional subpath. +/// Indicates whether the external tool call result was handled successfully. /// ///
/// @@ -4528,17 +5006,12 @@ pub struct InstalledPluginSourceLocal { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct InstalledPluginSourceUrl { - #[serde(skip_serializing_if = "Option::is_none")] - pub path: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub r#ref: Option, - /// Constant value. Always "url". - pub source: InstalledPluginSourceUrlSource, - pub url: String, +pub struct HandlePendingToolCallResult { + /// Whether the tool call result was handled successfully + pub success: bool, } -/// Canonical file or directory where custom instructions can be discovered or created, with location, kind, preference, and project path. +/// Indicates whether an in-progress manual compaction was aborted. /// ///
/// @@ -4548,21 +5021,12 @@ pub struct InstalledPluginSourceUrl { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct InstructionDiscoveryPath { - /// Whether the target is a single file or a directory of instruction files - pub kind: InstructionDiscoveryPathKind, - /// Which tier this target belongs to - pub location: InstructionDiscoveryPathLocation, - /// Absolute path of the file or directory (may not exist on disk yet) - pub path: String, - /// Whether this is the canonical target to create new instructions in its tier. At most one entry per tier is preferred. - pub preferred_for_creation: bool, - /// The input project path this target was derived from (only for repository targets) - #[serde(skip_serializing_if = "Option::is_none")] - pub project_path: Option, +pub struct HistoryAbortManualCompactionResult { + /// Whether an in-progress manual compaction was aborted. False when no manual compaction was running, when its abort controller was already aborted, or when the session is remote. + pub aborted: bool, } -/// Canonical files and directories where custom instructions can be created so the runtime will recognize them. +/// Indicates whether an in-progress background compaction was cancelled. /// ///
/// @@ -4572,12 +5036,12 @@ pub struct InstructionDiscoveryPath { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct InstructionDiscoveryPathList { - /// Canonical instruction create/discovery files and directories, in priority order - pub paths: Vec, +pub struct HistoryCancelBackgroundCompactionResult { + /// Whether an in-progress background compaction was cancelled. False when no compaction was running, when the session is remote, or when the underlying processor was unavailable. + pub cancelled: bool, } -/// Optional project paths to include in instruction discovery. +/// Parameters for clearing the conversation and seeding the window that replaces it. /// ///
/// @@ -4587,16 +5051,12 @@ pub struct InstructionDiscoveryPathList { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct InstructionsDiscoverRequest { - /// When true, omit the host's instruction sources (user/home-level files and plugin rules), leaving only repository and working-directory sources. For multitenant deployments. - #[serde(skip_serializing_if = "Option::is_none")] - pub exclude_host_instructions: Option, - /// Optional list of project directory paths to scan for repository/working-directory instruction sources. When omitted or empty, only user-level and plugin instruction sources are returned (no project scan). - #[serde(skip_serializing_if = "Option::is_none")] - pub project_paths: Option>, +pub struct HistoryClearContextRequest { + /// First user message of the fresh context window. Required: a cleared window holding only system and developer messages is not a conversation a model can answer, so every clear seeds the window it creates. Delivered by the enclosing turn driver once the agentic loop exits, which is why the call must be made from inside a tool handler. + pub prompt: String, } -/// Optional project paths to include when enumerating instruction discovery targets. +/// What a successful clear removed. A clear that could not be applied rejects instead of reporting a count. /// ///
/// @@ -4606,16 +5066,12 @@ pub struct InstructionsDiscoverRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct InstructionsGetDiscoveryPathsRequest { - /// When true, omit the host's user-level instruction targets, leaving only repository targets. For multitenant deployments (mirrors `discover`'s `excludeHostInstructions`). - #[serde(skip_serializing_if = "Option::is_none")] - pub exclude_host_instructions: Option, - /// Optional list of project directory paths. When omitted or empty, only the user-level targets are returned. - #[serde(skip_serializing_if = "Option::is_none")] - pub project_paths: Option>, +pub struct HistoryClearContextResult { + /// Number of non-system, non-developer messages that were removed from the conversation. Zero only when the window already held no conversation. + pub messages_cleared: i64, } -/// Loaded instruction source for a session, including path, content, category, location, applicability, and optional description. +/// Post-compaction context window usage breakdown /// ///
/// @@ -4625,34 +5081,25 @@ pub struct InstructionsGetDiscoveryPathsRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct InstructionSource { - /// Glob pattern(s) from frontmatter — when set, this instruction applies only to matching files - #[serde(skip_serializing_if = "Option::is_none")] - pub apply_to: Option>, - /// Raw content of the instruction file - pub content: String, - /// When true, this source starts disabled and must be toggled on by the user +pub struct HistoryCompactContextWindow { + /// Token count from non-system messages (user, assistant, tool) #[serde(skip_serializing_if = "Option::is_none")] - pub default_disabled: Option, - /// Short description (body after frontmatter) for use in instruction tables + pub conversation_tokens: Option, + /// Current total tokens in the context window (system + conversation + tool definitions) + pub current_tokens: i64, + /// Current number of messages in the conversation + pub messages_length: i64, + /// Token count from system message(s) #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, - /// Unique identifier for this source (used for toggling) - pub id: String, - /// Human-readable label - pub label: String, - /// Where this source lives — used for UI grouping - pub location: InstructionSourceLocation, - /// The project path this source was discovered from. Only set by sessionless discovery for repository/working-directory sources, where it disambiguates same-named files (e.g. .github/copilot-instructions.md) across multiple workspace roots. The session-scoped getSources leaves it unset. + pub system_tokens: Option, + /// Maximum token count for the model's context window + pub token_limit: i64, + /// Token count from tool definitions #[serde(skip_serializing_if = "Option::is_none")] - pub project_path: Option, - /// File path relative to repo or absolute for home - pub source_path: String, - /// Category of instruction source — used for merge logic - pub r#type: InstructionSourceType, + pub tool_definitions_tokens: Option, } -/// Instruction sources loaded for the session, in merge order. +/// Optional compaction parameters. /// ///
/// @@ -4662,78 +5109,44 @@ pub struct InstructionSource { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct InstructionsGetSourcesResult { - /// Instruction sources for the session - pub sources: Vec, -} - -/// A request body chunk or cancellation signal. -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct LlmInferenceHttpRequestChunkRequest { - /// Identity of the agent invocation (one agentic loop) this body chunk belongs to, matching the `agentInvocationId` semantics on httpRequestStart. Carried per chunk so a persistent transport can attribute successive turns correctly: when a WebSocket connection is reused across turns, the httpRequestStart identity reflects only the turn that opened the connection, so each later turn stamps its own invocation id here. Absent when the runtime has no invocation context for the request, or on the plain-HTTP transport where every request has its own httpRequestStart. - #[serde(skip_serializing_if = "Option::is_none")] - pub agent_invocation_id: Option, - /// When true, `data` is base64-encoded bytes. When absent or false, `data` is UTF-8 text. - #[serde(skip_serializing_if = "Option::is_none")] - pub binary: Option, - /// When true, the runtime is cancelling the in-flight request (e.g. upstream consumer aborted). `data` is ignored. Implies end-of-request. +pub struct HistoryCompactRequest { + /// Optional user-provided instructions to focus the compaction summary #[serde(skip_serializing_if = "Option::is_none")] - pub cancel: Option, - /// Optional human-readable reason for the cancellation, propagated for logging. + pub custom_instructions: Option, + /// Context window token limit this compaction is targeting, recorded as the `tokenLimit` on the persisted `session.compaction_start` / `session.compaction_complete` events. Set it when the compaction targets a window other than the compacting model's own, e.g. switching to a model with a smaller context window: the compaction still runs on the current model, so the limit that motivated it would otherwise be lost. When absent, the events record the compacting model's own resolved limit. Attribution metadata only - it does not change how much the compaction removes. #[serde(skip_serializing_if = "Option::is_none")] - pub cancel_reason: Option, - /// Body byte range. UTF-8 text when `binary` is absent or false; base64-encoded bytes when `binary` is true. May be empty. - pub data: String, - /// When true, this is the final body chunk for the request. The SDK may rely on having received an end-marked chunk before treating the request body as complete. + pub token_limit: Option, + /// What initiated this compaction request, recorded as the `trigger` on the persisted `session.compaction_start` / `session.compaction_complete` events. When absent, the compaction is persisted without trigger attribution (initiator unknown). #[serde(skip_serializing_if = "Option::is_none")] - pub end: Option, - /// Matches the requestId from the originating httpRequestStart frame. - pub request_id: RequestId, + pub trigger: Option, } -/// Acknowledgement. The SDK is free to ignore the ack and treat chunk delivery as fire-and-forget. -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct LlmInferenceHttpRequestChunkResult {} - -/// The head of an outbound model-layer HTTP request. +/// Compaction outcome with the number of tokens and messages removed, summary text, and the resulting context window breakdown. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct LlmInferenceHttpRequestStartRequest { - /// Stable identity of the agent trajectory that issued this request. Present when the request originates from an agent turn; absent for requests outside any agent context. This is the same identity used by lifecycle and bridged session events and remains constant across turns and retries. - #[serde(skip_serializing_if = "Option::is_none")] - pub agent_id: Option, - /// Identity of the agent invocation (one agentic loop) that issued this request. It remains fixed across physical retries within the invocation and is distinct from the stable trajectory `agentId`. A caller-supplied invocation id always takes precedence (this covers auxiliary calls that have no model call id). Otherwise, first-party CAPI requests fall back to the runtime's agent task id — the same value the runtime emits as the `X-Agent-Task-Id` header — while custom-provider requests fall back to the model call id. - #[serde(skip_serializing_if = "Option::is_none")] - pub agent_invocation_id: Option, - pub headers: HashMap>, - /// Coarse classification of the interaction that produced this request. Open string for forward-compatibility; known values include `conversation-agent`, `conversation-subagent`, `conversation-sampling`, `conversation-background`, `conversation-compaction`, and `conversation-user`. Absent when the runtime did not classify the request. Comes from the runtime's per-request agent context independently of transport; on the CAPI transport the runtime derives the upstream `X-Interaction-Type` header from this same context. - #[serde(skip_serializing_if = "Option::is_none")] - pub interaction_type: Option, - /// HTTP method, e.g. GET, POST. - pub method: String, - /// Stable identity of the immediate parent trajectory. Present for child trajectories such as subagents and conversation-sampling requests; absent for root-agent and non-agent requests. - #[serde(skip_serializing_if = "Option::is_none")] - pub parent_agent_id: Option, - /// Opaque runtime-minted id, unique per in-flight request. The SDK uses this to correlate httpRequestChunk frames and to address its httpResponseStart / httpResponseChunk replies back to the runtime. - pub request_id: RequestId, - /// Id of the runtime session that triggered this request, when one is in scope. Absent for requests issued outside any session (e.g. startup model-catalog or capability resolution). This is a payload field — not a dispatch key — because the client-global API is registered process-wide rather than per session. +pub struct HistoryCompactResult { + /// Post-compaction context window usage breakdown #[serde(skip_serializing_if = "Option::is_none")] - pub session_id: Option, - /// Transport the runtime would otherwise use for this request. `http` (the default when absent) covers plain HTTP and SSE responses; `websocket` indicates a full-duplex message channel where each body chunk maps to one WebSocket message and the `binary` flag distinguishes text from binary frames. The SDK consumer uses this to decide whether to service the request with an HTTP client or a WebSocket client. It is the one piece of request metadata the consumer cannot reliably infer from the URL or headers alone. + pub context_window: Option, + /// Number of messages removed during compaction + pub messages_removed: i64, + /// Whether compaction completed successfully + pub success: bool, + /// Summary text produced by compaction. Omitted when compaction did not produce a summary (e.g. failure path). #[serde(skip_serializing_if = "Option::is_none")] - pub transport: Option, - /// Absolute request URL. - pub url: String, + pub summary_content: Option, + /// Number of tokens freed by compaction + pub tokens_removed: i64, } -/// Acknowledgement. Returning successfully simply means the SDK accepted the start frame; it does not imply the request will succeed. -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct LlmInferenceHttpRequestStartResult {} - -/// Set to terminate the response with a transport-level failure. Implies end-of-stream; any further chunks for this requestId are ignored. +/// A root user turn that the session can rewind to. /// ///
/// @@ -4743,15 +5156,28 @@ pub struct LlmInferenceHttpRequestStartResult {} ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct LlmInferenceHttpResponseChunkError { - /// Optional machine-readable error code. - #[serde(skip_serializing_if = "Option::is_none")] - pub code: Option, - /// Human-readable failure description. - pub message: String, +pub struct HistoryRewindPoint { + /// Whether at least one file in this turn or a later turn can be restored. + pub can_restore_files: bool, + /// ID of the user.message event that begins the discarded suffix. + pub event_id: String, + /// Number of unique files in this turn and all later turns that have captured changes. + pub file_count: i64, + /// Whether this turn was an automatically injected autopilot continuation. + pub is_autopilot_continuation: bool, + /// Lines added by this turn's captured file changes. + pub lines_added: i64, + /// Lines removed by this turn's captured file changes. + pub lines_removed: i64, + /// ISO timestamp of the user turn. + pub timestamp: String, + /// Whether this turn itself captured any file changes. + pub turn_changed_files: bool, + /// User-visible message text for the turn. + pub user_message: String, } -/// A response body chunk or terminal error. +/// Rewind points and file-change-tracking availability for the session. /// ///
/// @@ -4761,23 +5187,17 @@ pub struct LlmInferenceHttpResponseChunkError { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct LlmInferenceHttpResponseChunkRequest { - /// When true, `data` is base64-encoded bytes. When absent or false, `data` is UTF-8 text. - #[serde(skip_serializing_if = "Option::is_none")] - pub binary: Option, - /// Body byte range. UTF-8 text when `binary` is absent or false; base64-encoded bytes when `binary` is true. May be empty (e.g. when the response body is empty: send a single chunk with empty data and end=true). - pub data: String, - /// When true, this is the final body chunk for the response. The runtime treats the response body as complete after receiving an end-marked chunk. +pub struct HistoryListRewindPointsResult { + /// Whether this session captured file changes from its first turn. + pub file_change_tracking_enabled: bool, + /// Root user turns in chronological order. Empty when `unavailableReason` is set. + pub points: Vec, + /// Why the listed points could not be produced, when applicable; the points list is empty whenever it is set. `unsupported-remote-session` is permanent for the session and comes with `fileChangeTrackingEnabled: false`. `session-busy` is transient and only ever reported by a session that *is* tracking (`fileChangeTrackingEnabled: true`), because the file-change captures cannot be read while work that may still mutate them is in flight; the same request succeeds once the session settles, so a client that wants points should retry rather than treat it as a failure. It is never `file-change-tracking-disabled`: an untracked local session still lists conversation-only points and reports that through `fileChangeTrackingEnabled: false`. #[serde(skip_serializing_if = "Option::is_none")] - pub end: Option, - /// Set to terminate the response with a transport-level failure. Implies end-of-stream; any further chunks for this requestId are ignored. - #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, - /// Matches the requestId from the originating httpRequestStart frame. - pub request_id: RequestId, + pub unavailable_reason: Option, } -/// Whether the chunk was accepted. +/// Event boundary to preview for conversation-and-files rewind. /// ///
/// @@ -4787,12 +5207,12 @@ pub struct LlmInferenceHttpResponseChunkRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct LlmInferenceHttpResponseChunkResult { - /// True when the chunk was matched to a pending request; false when unknown. - pub accepted: bool, +pub struct HistoryPreviewRewindRequest { + /// ID of the user.message event that begins the discarded suffix. + pub event_id: String, } -/// Response head. +/// A file that a conversation-and-files rewind would restore. /// ///
/// @@ -4802,18 +5222,18 @@ pub struct LlmInferenceHttpResponseChunkResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct LlmInferenceHttpResponseStartRequest { - pub headers: HashMap>, - /// Matches the requestId from the originating httpRequestStart frame. - pub request_id: RequestId, - /// HTTP status code. - pub status: i64, - /// Optional HTTP status reason phrase. - #[serde(skip_serializing_if = "Option::is_none")] - pub status_text: Option, +pub struct HistoryRewindFilePreview { + /// Aggregate change made across the discarded turns. + pub change_type: HistoryRewindChangeType, + /// Lines added across the discarded turns. + pub lines_added: i64, + /// Lines removed across the discarded turns. + pub lines_removed: i64, + /// Absolute path of the captured file. + pub path: String, } -/// Whether the start frame was accepted. +/// Files and aggregate changes for a prospective rewind. /// ///
/// @@ -4823,12 +5243,19 @@ pub struct LlmInferenceHttpResponseStartRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct LlmInferenceHttpResponseStartResult { - /// True when the response start was matched to a pending request; false when unknown. - pub accepted: bool, +pub struct HistoryPreviewRewindResult { + /// Whether file restore is available for this session. This is authoritative: switch on it and read `reason` only when it is false. + pub available: bool, + /// Number of unique files in the preview. + pub file_count: i64, + /// Files ordered by path. + pub files: Vec, + /// Why file restore is unavailable, when applicable. Populated only when `available` is false and never set when `available` is true. + #[serde(skip_serializing_if = "Option::is_none")] + pub reason: Option, } -/// Indicates whether the calling client was registered as the LLM inference provider. +/// Boundary and mode for rewinding session history. /// ///
/// @@ -4838,12 +5265,14 @@ pub struct LlmInferenceHttpResponseStartResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct LlmInferenceSetProviderResult { - /// Whether the provider was set successfully - pub success: bool, +pub struct HistoryRewindRequest { + /// ID of the user.message event that begins the discarded suffix. + pub event_id: String, + /// Whether to rewind only conversation history or also restore captured files. + pub mode: HistoryRewindMode, } -/// Pre-resolved working-directory context for session startup. +/// A captured file that rewind intentionally left unchanged. /// ///
/// @@ -4853,24 +5282,14 @@ pub struct LlmInferenceSetProviderResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionContext { - /// Active git branch - #[serde(skip_serializing_if = "Option::is_none")] - pub branch: Option, - /// Most recent working directory for this session - pub cwd: String, - /// Git repository root, if the cwd was inside a git repo - #[serde(skip_serializing_if = "Option::is_none")] - pub git_root: Option, - /// Repository host type - #[serde(skip_serializing_if = "Option::is_none")] - pub host_type: Option, - /// Repository slug in `owner/name` form, when known - #[serde(skip_serializing_if = "Option::is_none")] - pub repository: Option, +pub struct HistorySkippedFileRestore { + /// Absolute path of the skipped file. + pub path: String, + /// Reason the file was not restored. + pub reason: HistoryFileRestoreSkipReason, } -/// Persisted local session metadata, including identifiers, timestamps, summary/name, client, context, detached state, and task ID. +/// Structured outcome of a rewind request. /// ///
/// @@ -4880,36 +5299,22 @@ pub struct SessionContext { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct LocalSessionMetadataValue { - /// Runtime client name that created/last resumed this session - #[serde(skip_serializing_if = "Option::is_none")] - pub client_name: Option, - /// Pre-resolved working-directory context for session startup. - #[serde(skip_serializing_if = "Option::is_none")] - pub context: Option, - /// True for detached maintenance sessions that should be hidden from normal resume lists. - #[serde(skip_serializing_if = "Option::is_none")] - pub is_detached: Option, - /// Always false for local sessions. - pub is_remote: bool, - /// GitHub task ID, when this local session is bound to one. Only present for local sessions exported to remote control. - #[serde(skip_serializing_if = "Option::is_none")] - pub mc_task_id: Option, - /// Last-modified time of the session's persisted state, as ISO 8601 - pub modified_time: String, - /// Optional human-friendly name set via /rename +pub struct HistoryRewindResult { + /// Failure detail. Set only for the failure and partial-failure outcomes (`files-rolled-back`, `rollback-incomplete`, `truncation-failed`, `checkpoint-cleanup-failed`, `snapshot-prune-failed`); omitted for `success` and for the unavailable outcomes (`session-busy`, `file-change-tracking-disabled`, `unsupported-remote-session`). #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, - /// Stable session identifier - pub session_id: SessionId, - /// Session creation time as an ISO 8601 timestamp - pub start_time: String, - /// Short summary of the session, when one has been derived + pub error: Option, + /// Number of persisted events removed by conversation truncation. Present only when truncation succeeded (outcomes `success`, `checkpoint-cleanup-failed`, and `snapshot-prune-failed`); omitted for every unavailable outcome (`session-busy`, `file-change-tracking-disabled`, `unsupported-remote-session`) and for `truncation-failed`, `files-rolled-back`, and `rollback-incomplete`. #[serde(skip_serializing_if = "Option::is_none")] - pub summary: Option, + pub events_removed: Option, + /// Overall rewind outcome. This discriminates the result: it governs which of the remaining fields are populated, so consumers must switch on it before reading `eventsRemoved`, `restoredFiles`, `skippedFiles`, or `error`. See each field for the outcomes that populate it. + pub outcome: HistoryRewindOutcome, + /// Absolute paths restored to their captured preimages. Always empty for conversation-only rewinds and for the unavailable outcomes (`session-busy`, `file-change-tracking-disabled`, `unsupported-remote-session`); only conversation-and-files outcomes that reached the file-restore stage populate it. + pub restored_files: Vec, + /// Captured files intentionally left unchanged. Always empty for conversation-only rewinds and for the unavailable outcomes (`session-busy`, `file-change-tracking-disabled`, `unsupported-remote-session`); only conversation-and-files outcomes that reached the file-restore stage populate it. + pub skipped_files: Vec, } -/// Message text, optional severity level, persistence flag, optional follow-up URL, and optional tip. +/// Markdown summary of the conversation context (empty when not available). /// ///
/// @@ -4919,27 +5324,12 @@ pub struct LocalSessionMetadataValue { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct LogRequest { - /// When true, the message is transient and not persisted to the session event log on disk - #[serde(skip_serializing_if = "Option::is_none")] - pub ephemeral: Option, - /// Log severity level. Determines how the message is displayed in the timeline. Defaults to "info". - #[serde(skip_serializing_if = "Option::is_none")] - pub level: Option, - /// Human-readable message - pub message: String, - /// Optional actionable tip displayed alongside the message. Only honored on `level: "info"`. - #[serde(skip_serializing_if = "Option::is_none")] - pub tip: Option, - /// Domain category for this log entry (e.g., "mcp", "subscription", "policy", "model"). Maps to `infoType`/`warningType`/`errorType` on the emitted event. Defaults to "notification". - #[serde(skip_serializing_if = "Option::is_none")] - pub r#type: Option, - /// Optional URL the user can open in their browser for more details - #[serde(skip_serializing_if = "Option::is_none")] - pub url: Option, +pub struct HistorySummarizeForHandoffResult { + /// Markdown summary of the conversation context produced by an LLM. Empty string when there are no messages or when the session does not support local summarization. + pub summary: String, } -/// Identifier of the session event that was emitted for the log message. +/// Identifier of the event to truncate to; this event and all later events are removed. /// ///
/// @@ -4949,12 +5339,12 @@ pub struct LogRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct LogResult { - /// The unique identifier of the emitted session event +pub struct HistoryTruncateRequest { + /// Event ID to truncate to. This event and all events after it are removed from the session. pub event_id: String, } -/// Parameters for (re)loading the merged LSP configuration set. +/// Number of events that were removed by the truncation. /// ///
/// @@ -4964,19 +5354,18 @@ pub struct LogResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct LspInitializeRequest { - /// Force re-initialization even when LSP configs were already loaded for the working directory. - #[serde(skip_serializing_if = "Option::is_none")] - pub force: Option, - /// Git root used as the boundary when traversing for project-level LSP configs (supports monorepos). +pub struct HistoryTruncateResult { + /// Failure detail when checkpointCleanupFailed is true. #[serde(skip_serializing_if = "Option::is_none")] - pub git_root: Option, - /// Working directory used to load project-level LSP configs. Defaults to the session working directory when omitted. + pub checkpoint_cleanup_error: Option, + /// True when conversation truncation succeeded but post-truncation workspace checkpoint cleanup failed. History is already truncated; callers may still prune snapshots but should report a checkpoint-cleanup rather than a truncation failure. #[serde(skip_serializing_if = "Option::is_none")] - pub working_directory: Option, + pub checkpoint_cleanup_failed: Option, + /// Number of events that were removed + pub events_removed: i64, } -/// Result of registering a new marketplace. +/// Authentication-info variant for GitHub-internal HMAC auth, carrying the public GitHub host and HMAC secret. /// ///
/// @@ -4986,30 +5375,37 @@ pub struct LspInitializeRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct MarketplaceAddResult { - /// Final name of the marketplace as resolved from its manifest - pub name: String, +pub struct HMACAuthInfo { + /// Snapshot of the authenticated user's Copilot subscription info, if known. Mirrors the GitHub API `/copilot_internal/v2/token` user response shape — the runtime trusts this verbatim and does not re-fetch when set. + #[serde(skip_serializing_if = "Option::is_none")] + pub copilot_user: Option, + /// HMAC secret used to sign requests. + pub hmac: String, + /// Authentication host. HMAC auth always targets the public GitHub host. + pub host: HMACAuthInfoHost, + /// HMAC-based authentication used by GitHub-internal services. + pub r#type: HMACAuthInfoType, } -/// Plugin entry advertised by a marketplace. -/// -///
-/// -/// **Experimental.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
+/// Runtime-owned wire payload for a server-to-client hook callback invocation. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct MarketplacePluginInfo { - /// Short description from the marketplace catalog, when present +pub(crate) struct HookInvokeRequest { + #[doc(hidden)] + pub(crate) hook_type: HookType, + pub input: serde_json::Value, + pub session_id: SessionId, +} + +/// Optional output returned by an SDK callback hook. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct HookInvokeResponse { #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, - /// Plugin name as listed in the marketplace catalog - pub name: String, + pub output: Option, } -/// Plugins advertised by the marketplace. +/// Installed plugin record from global state, with marketplace, version, install time, enabled state, cache path, and source. /// ///
/// @@ -5019,12 +5415,31 @@ pub struct MarketplacePluginInfo { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct MarketplaceBrowseResult { - /// Plugins advertised by the marketplace - pub plugins: Vec, +pub struct InstalledPlugin { + /// Path where the plugin is cached locally + #[serde(rename = "cache_path", skip_serializing_if = "Option::is_none")] + pub cache_path: Option, + /// Whether the plugin is currently enabled + pub enabled: bool, + /// Installation timestamp + #[serde(rename = "installed_at")] + pub installed_at: String, + /// Marketplace the plugin came from (empty string for direct repo installs) + pub marketplace: String, + /// Plugin name + pub name: String, + /// Source for direct repo installs (when marketplace is empty) + #[serde(skip_serializing_if = "Option::is_none")] + pub source: Option, + /// Per-plugin source fingerprint (a SHA-256 hash of the plugin's catalog source spec plus its resolved source subtree — NOT a Git commit SHA) captured at marketplace install/update time. Auto-update compares it against the freshly recomputed fingerprint to detect a content change that does not bump the version. Absent for pre-existing installs and for direct (non-marketplace) installs. + #[serde(rename = "source_sha", skip_serializing_if = "Option::is_none")] + pub source_sha: Option, + /// Version installed (if available) + #[serde(skip_serializing_if = "Option::is_none")] + pub version: Option, } -/// Registered marketplace summary. +/// Information about an installed plugin tracked in global state. /// ///
/// @@ -5034,17 +5449,22 @@ pub struct MarketplaceBrowseResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct MarketplaceInfo { - /// True when this is a default marketplace shipped with the runtime. Defaults are not removable. +pub struct InstalledPluginInfo { + /// Opaque, stable hash identifying a direct (non-marketplace) install source. Present only for direct repo / URL / local installs; absent for marketplace plugins. Same source yields the same id; distinct sources never collide. #[serde(skip_serializing_if = "Option::is_none")] - pub is_default: Option, - /// Marketplace name (matches the @marketplace suffix in plugin specs) + pub direct_source_id: Option, + /// Whether the plugin is currently enabled for new sessions + pub enabled: bool, + /// Marketplace the plugin came from. Empty string ("") for direct repo / URL / local installs. + pub marketplace: String, + /// Plugin name pub name: String, - /// Human-readable description of where the marketplace data is fetched from (e.g. "GitHub: owner/repo"). - pub source: String, + /// Installed version (when reported by the plugin manifest) + #[serde(skip_serializing_if = "Option::is_none")] + pub version: Option, } -/// All registered marketplaces, including built-in defaults. +/// Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref or full commit SHA, and optional subpath. /// ///
/// @@ -5054,12 +5474,20 @@ pub struct MarketplaceInfo { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct MarketplaceListResult { - /// Registered marketplaces - pub marketplaces: Vec, +pub struct InstalledPluginSourceGitHub { + #[serde(skip_serializing_if = "Option::is_none")] + pub path: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub r#ref: Option, + pub repo: String, + /// Optional full 40-character hexadecimal commit SHA. + #[serde(skip_serializing_if = "Option::is_none")] + pub sha: Option, + /// Constant value. Always "github". + pub source: InstalledPluginSourceGitHubSource, } -/// Per-marketplace refresh result, including marketplace name, success flag, and optional failure error. +/// Source descriptor for a direct local plugin install, with a local filesystem path. /// ///
/// @@ -5069,17 +5497,13 @@ pub struct MarketplaceListResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct MarketplaceRefreshEntry { - /// Error message (failure only) - #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, - /// Marketplace name that was refreshed - pub name: String, - /// Whether the refresh succeeded - pub success: bool, +pub struct InstalledPluginSourceLocal { + pub path: String, + /// Constant value. Always "local". + pub source: InstalledPluginSourceLocalSource, } -/// Result of refreshing one or more marketplace catalogs. +/// Source descriptor for a direct URL plugin install, with URL, optional ref or full commit SHA, and optional subpath. /// ///
/// @@ -5089,12 +5513,20 @@ pub struct MarketplaceRefreshEntry { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct MarketplaceRefreshResult { - /// Per-marketplace refresh results in deterministic order. - pub results: Vec, +pub struct InstalledPluginSourceUrl { + #[serde(skip_serializing_if = "Option::is_none")] + pub path: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub r#ref: Option, + /// Optional full 40-character hexadecimal commit SHA. + #[serde(skip_serializing_if = "Option::is_none")] + pub sha: Option, + /// Constant value. Always "url". + pub source: InstalledPluginSourceUrlSource, + pub url: String, } -/// Outcome of the remove attempt, including dependent-plugin info when applicable. +/// Canonical file or directory where custom instructions can be discovered or created, with location, kind, preference, and project path. /// ///
/// @@ -5104,15 +5536,21 @@ pub struct MarketplaceRefreshResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct MarketplaceRemoveResult { - /// Names of installed plugins that prevented removal. Populated only when `removed=false`. +pub struct InstructionDiscoveryPath { + /// Whether the target is a single file or a directory of instruction files + pub kind: InstructionDiscoveryPathKind, + /// Which tier this target belongs to + pub location: InstructionDiscoveryPathLocation, + /// Absolute path of the file or directory (may not exist on disk yet) + pub path: String, + /// Whether this is the canonical target to create new instructions in its tier. At most one entry per tier is preferred. + pub preferred_for_creation: bool, + /// The input project path this target was derived from (only for repository targets) #[serde(skip_serializing_if = "Option::is_none")] - pub dependent_plugins: Option>, - /// True when the marketplace was actually removed. False when removal was skipped because the marketplace has dependent plugins and `force` was not set. - pub removed: bool, + pub project_path: Option, } -/// MCP server allowed by policy, with server name and optional PII-free explanatory note. +/// Canonical files and directories where custom instructions can be created so the runtime will recognize them. /// ///
/// @@ -5122,15 +5560,12 @@ pub struct MarketplaceRemoveResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpAllowedServer { - /// Allowed server name - pub name: String, - /// PII-free note explaining why the server was allowed - #[serde(skip_serializing_if = "Option::is_none")] - pub redacted_note: Option, +pub struct InstructionDiscoveryPathList { + /// Canonical instruction create/discovery files and directories, in priority order + pub paths: Vec, } -/// MCP server, tool name, and arguments to invoke from an MCP App view. +/// Optional project paths to include in instruction discovery. /// ///
/// @@ -5140,19 +5575,16 @@ pub struct McpAllowedServer { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpAppsCallToolRequest { - /// Tool arguments +pub struct InstructionsDiscoverRequest { + /// When true, omit the host's instruction sources (user/home-level files and plugin rules), leaving only repository and working-directory sources. For multitenant deployments. #[serde(skip_serializing_if = "Option::is_none")] - pub arguments: Option>, - /// **Required.** Server whose ui:// view issued the request. Per SEP-1865 ('callable by the app from this server only'), the call is rejected when this differs from `serverName`, and rejected outright when missing. - pub origin_server_name: String, - /// MCP server hosting the tool - pub server_name: String, - /// MCP tool name - pub tool_name: String, + pub exclude_host_instructions: Option, + /// Optional list of project directory paths to scan for repository/working-directory instruction sources. When omitted or empty, only user-level and plugin instruction sources are returned (no project scan). + #[serde(skip_serializing_if = "Option::is_none")] + pub project_paths: Option>, } -/// Capability negotiation snapshot +/// Optional project paths to include when enumerating instruction discovery targets. /// ///
/// @@ -5162,16 +5594,16 @@ pub struct McpAppsCallToolRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpAppsDiagnoseCapability { - /// Whether the runtime advertises `extensions.io.modelcontextprotocol/ui` to MCP servers - pub advertised: bool, - /// Whether the MCP_APPS feature flag (or COPILOT_MCP_APPS env override) is on - pub feature_flag_enabled: bool, - /// Whether the session has the `mcp-apps` capability - pub session_has_mcp_apps: bool, +pub struct InstructionsGetDiscoveryPathsRequest { + /// When true, omit the host's user-level instruction targets, leaving only repository targets. For multitenant deployments (mirrors `discover`'s `excludeHostInstructions`). + #[serde(skip_serializing_if = "Option::is_none")] + pub exclude_host_instructions: Option, + /// Optional list of project directory paths. When omitted or empty, only the user-level targets are returned. + #[serde(skip_serializing_if = "Option::is_none")] + pub project_paths: Option>, } -/// MCP server to diagnose MCP Apps wiring for. +/// Loaded instruction source for a session, including path, content, category, location, applicability, and optional description. /// ///
/// @@ -5181,12 +5613,34 @@ pub struct McpAppsDiagnoseCapability { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpAppsDiagnoseRequest { - /// MCP server to probe - pub server_name: String, +pub struct InstructionSource { + /// Glob pattern(s) from frontmatter — when set, this instruction applies only to matching files + #[serde(skip_serializing_if = "Option::is_none")] + pub apply_to: Option>, + /// Raw content of the instruction file + pub content: String, + /// When true, this source starts disabled and must be toggled on by the user + #[serde(skip_serializing_if = "Option::is_none")] + pub default_disabled: Option, + /// Short description (body after frontmatter) for use in instruction tables + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + /// Unique identifier for this source (used for toggling) + pub id: String, + /// Human-readable label + pub label: String, + /// Where this source lives — used for UI grouping + pub location: InstructionSourceLocation, + /// The project path this source was discovered from. Only set by sessionless discovery for repository, working-directory, and project-scoped plugin sources, where it disambiguates sources across multiple workspace roots. The session-scoped getSources leaves it unset. + #[serde(skip_serializing_if = "Option::is_none")] + pub project_path: Option, + /// File path relative to repo or absolute for home + pub source_path: String, + /// Category of instruction source — used for merge logic + pub r#type: InstructionSourceType, } -/// What the server returned for this session +/// Instruction sources loaded for the session, in merge order. /// ///
/// @@ -5196,18 +5650,12 @@ pub struct McpAppsDiagnoseRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpAppsDiagnoseServer { - /// Whether the named server is currently connected - pub connected: bool, - /// Up to 5 tool names with `_meta.ui` for quick inspection - pub sample_tool_names: Vec, - /// Total tools returned by the server's tools/list - pub tool_count: f64, - /// Tools whose `_meta.ui` is populated (resourceUri and/or visibility set) - pub tools_with_ui_meta: f64, +pub struct InstructionsGetSourcesResult { + /// Instruction sources for the session + pub sources: Vec, } -/// Diagnostic snapshot of MCP Apps wiring for the named server. +/// Parameters for interrupting the main agent turn. /// ///
/// @@ -5217,14 +5665,13 @@ pub struct McpAppsDiagnoseServer { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpAppsDiagnoseResult { - /// Capability negotiation snapshot - pub capability: McpAppsDiagnoseCapability, - /// What the server returned for this session - pub server: McpAppsDiagnoseServer, +pub struct InterruptMainTurnRequest { + /// When true, the user's queued prompts are preserved and run as the next turn once the interrupted turn unwinds; when false (the default), the queue is cleared like a plain abort. + #[serde(skip_serializing_if = "Option::is_none")] + pub flush_queued: Option, } -/// Current host context +/// Result of interrupting the main agent turn. /// ///
/// @@ -5234,31 +5681,78 @@ pub struct McpAppsDiagnoseResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpAppsHostContextDetails { - /// Display modes the host supports +pub struct InterruptMainTurnResult { + /// Whether an in-flight main agent turn was interrupted. False when the main loop was not processing. + pub interrupted: bool, +} + +/// A request body chunk or cancellation signal. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct LlmInferenceHttpRequestChunkRequest { + /// Identity of the agent invocation (one agentic loop) this body chunk belongs to, matching the `agentInvocationId` semantics on httpRequestStart. Carried per chunk so a persistent transport can attribute successive turns correctly: when a WebSocket connection is reused across turns, the httpRequestStart identity reflects only the turn that opened the connection, so each later turn stamps its own invocation id here. Absent when the runtime has no invocation context for the request, or on the plain-HTTP transport where every request has its own httpRequestStart. #[serde(skip_serializing_if = "Option::is_none")] - pub available_display_modes: Option>, - /// Current display mode (SEP-1865) + pub agent_invocation_id: Option, + /// When true, `data` is base64-encoded bytes. When absent or false, `data` is UTF-8 text. #[serde(skip_serializing_if = "Option::is_none")] - pub display_mode: Option, - /// BCP-47 locale, e.g. 'en-US' + pub binary: Option, + /// When true, the runtime is cancelling the in-flight request (e.g. upstream consumer aborted). `data` is ignored. Implies end-of-request. #[serde(skip_serializing_if = "Option::is_none")] - pub locale: Option, - /// Platform type for responsive design + pub cancel: Option, + /// Optional human-readable reason for the cancellation, propagated for logging. #[serde(skip_serializing_if = "Option::is_none")] - pub platform: Option, - /// UI theme preference per SEP-1865 + pub cancel_reason: Option, + /// Body byte range. UTF-8 text when `binary` is absent or false; base64-encoded bytes when `binary` is true. May be empty. + pub data: String, + /// When true, this is the final body chunk for the request. The SDK may rely on having received an end-marked chunk before treating the request body as complete. #[serde(skip_serializing_if = "Option::is_none")] - pub theme: Option, - /// IANA timezone, e.g. 'America/New_York' + pub end: Option, + /// Matches the requestId from the originating httpRequestStart frame. + pub request_id: RequestId, +} + +/// Acknowledgement. The SDK is free to ignore the ack and treat chunk delivery as fire-and-forget. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct LlmInferenceHttpRequestChunkResult {} + +/// The head of an outbound model-layer HTTP request. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct LlmInferenceHttpRequestStartRequest { + /// Stable identity of the agent trajectory that issued this request. Present when the request originates from an agent turn; absent for requests outside any agent context. This is the same identity used by lifecycle and bridged session events and remains constant across turns and retries. #[serde(skip_serializing_if = "Option::is_none")] - pub time_zone: Option, - /// Host application identifier + pub agent_id: Option, + /// Identity of the agent invocation (one agentic loop) that issued this request. It remains fixed across physical retries within the invocation and is distinct from the stable trajectory `agentId`. A caller-supplied invocation id always takes precedence (this covers auxiliary calls that have no model call id). Otherwise, first-party CAPI requests fall back to the runtime's agent task id — the same value the runtime emits as the `X-Agent-Task-Id` header — while custom-provider requests fall back to the model call id. #[serde(skip_serializing_if = "Option::is_none")] - pub user_agent: Option, + pub agent_invocation_id: Option, + pub headers: HashMap>, + /// Coarse classification of the interaction that produced this request. Open string for forward-compatibility; known values include `conversation-agent`, `conversation-subagent`, `conversation-sampling`, `conversation-background`, `conversation-compaction`, and `conversation-user`. Absent when the runtime did not classify the request. Comes from the runtime's per-request agent context independently of transport; on the CAPI transport the runtime derives the upstream `X-Interaction-Type` header from this same context. + #[serde(skip_serializing_if = "Option::is_none")] + pub interaction_type: Option, + /// HTTP method, e.g. GET, POST. + pub method: String, + /// Stable identity of the immediate parent trajectory. Present for child trajectories such as subagents and conversation-sampling requests; absent for root-agent and non-agent requests. + #[serde(skip_serializing_if = "Option::is_none")] + pub parent_agent_id: Option, + /// Opaque runtime-minted id, unique per in-flight request. The SDK uses this to correlate httpRequestChunk frames and to address its httpResponseStart / httpResponseChunk replies back to the runtime. + pub request_id: RequestId, + /// Id of the runtime session that triggered this request, when one is in scope. Absent for requests issued outside any session (e.g. startup model-catalog or capability resolution). This is a payload field — not a dispatch key — because the client-global API is registered process-wide rather than per session. + #[serde(skip_serializing_if = "Option::is_none")] + pub session_id: Option, + /// Transport the runtime would otherwise use for this request. `http` (the default when absent) covers plain HTTP and SSE responses; `websocket` indicates a full-duplex message channel where each body chunk maps to one WebSocket message and the `binary` flag distinguishes text from binary frames. The SDK consumer uses this to decide whether to service the request with an HTTP client or a WebSocket client. It is the one piece of request metadata the consumer cannot reliably infer from the URL or headers alone. + #[serde(skip_serializing_if = "Option::is_none")] + pub transport: Option, + /// Absolute request URL. + pub url: String, } -/// Current host context advertised to MCP App guests. +/// Acknowledgement. Returning successfully simply means the SDK accepted the start frame; it does not imply the request will succeed. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct LlmInferenceHttpRequestStartResult {} + +/// Set to terminate the response with a transport-level failure. Implies end-of-stream; any further chunks for this requestId are ignored. /// ///
/// @@ -5268,12 +5762,15 @@ pub struct McpAppsHostContextDetails { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpAppsHostContext { - /// Current host context - pub context: McpAppsHostContextDetails, +pub struct LlmInferenceHttpResponseChunkError { + /// Optional machine-readable error code. + #[serde(skip_serializing_if = "Option::is_none")] + pub code: Option, + /// Human-readable failure description. + pub message: String, } -/// MCP server to list app-callable tools for. +/// A response body chunk or terminal error. /// ///
/// @@ -5283,14 +5780,23 @@ pub struct McpAppsHostContext { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpAppsListToolsRequest { - /// **Required.** Server whose ui:// view issued the request. Per SEP-1865 ('callable by the app from this server only'), the call is rejected when this differs from `serverName`, and rejected outright when missing. - pub origin_server_name: String, - /// MCP server hosting the app - pub server_name: String, +pub struct LlmInferenceHttpResponseChunkRequest { + /// When true, `data` is base64-encoded bytes. When absent or false, `data` is UTF-8 text. + #[serde(skip_serializing_if = "Option::is_none")] + pub binary: Option, + /// Body byte range. UTF-8 text when `binary` is absent or false; base64-encoded bytes when `binary` is true. May be empty (e.g. when the response body is empty: send a single chunk with empty data and end=true). + pub data: String, + /// When true, this is the final body chunk for the response. The runtime treats the response body as complete after receiving an end-marked chunk. + #[serde(skip_serializing_if = "Option::is_none")] + pub end: Option, + /// Set to terminate the response with a transport-level failure. Implies end-of-stream; any further chunks for this requestId are ignored. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + /// Matches the requestId from the originating httpRequestStart frame. + pub request_id: RequestId, } -/// App-callable tools from the named MCP server. +/// Whether the chunk was accepted. /// ///
/// @@ -5300,12 +5806,12 @@ pub struct McpAppsListToolsRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpAppsListToolsResult { - /// App-callable tools from the server - pub tools: Vec>, +pub struct LlmInferenceHttpResponseChunkResult { + /// True when the chunk was matched to a pending request; false when unknown. + pub accepted: bool, } -/// MCP server and resource URI to fetch. +/// Response head. /// ///
/// @@ -5315,14 +5821,18 @@ pub struct McpAppsListToolsResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpAppsReadResourceRequest { - /// Name of the MCP server hosting the resource - pub server_name: String, - /// Resource URI (typically ui://...) - pub uri: String, +pub struct LlmInferenceHttpResponseStartRequest { + pub headers: HashMap>, + /// Matches the requestId from the originating httpRequestStart frame. + pub request_id: RequestId, + /// HTTP status code. + pub status: i64, + /// Optional HTTP status reason phrase. + #[serde(skip_serializing_if = "Option::is_none")] + pub status_text: Option, } -/// MCP Apps resource content with URI, optional MIME type, text or base64 blob, and resource metadata. +/// Whether the start frame was accepted. /// ///
/// @@ -5332,24 +5842,12 @@ pub struct McpAppsReadResourceRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpAppsResourceContent { - /// Resource-level metadata (CSP, permissions, etc.) - #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] - pub meta: Option>, - /// Base64-encoded binary content - #[serde(skip_serializing_if = "Option::is_none")] - pub blob: Option, - /// MIME type of the content - #[serde(skip_serializing_if = "Option::is_none")] - pub mime_type: Option, - /// Text content (e.g. HTML) - #[serde(skip_serializing_if = "Option::is_none")] - pub text: Option, - /// The resource URI (typically ui://...) - pub uri: String, +pub struct LlmInferenceHttpResponseStartResult { + /// True when the response start was matched to a pending request; false when unknown. + pub accepted: bool, } -/// Resource contents returned by the MCP server. +/// Indicates whether the calling client was registered as the LLM inference provider. /// ///
/// @@ -5359,12 +5857,12 @@ pub struct McpAppsResourceContent { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpAppsReadResourceResult { - /// Resource contents returned by the server - pub contents: Vec, +pub struct LlmInferenceSetProviderResult { + /// Whether the provider was set successfully + pub success: bool, } -/// Host context advertised to MCP App guests +/// Pre-resolved working-directory context for session startup. /// ///
/// @@ -5374,31 +5872,24 @@ pub struct McpAppsReadResourceResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpAppsSetHostContextDetails { - /// Display modes the host supports - #[serde(skip_serializing_if = "Option::is_none")] - pub available_display_modes: Option>, - /// Current display mode (SEP-1865) - #[serde(skip_serializing_if = "Option::is_none")] - pub display_mode: Option, - /// BCP-47 locale, e.g. 'en-US' - #[serde(skip_serializing_if = "Option::is_none")] - pub locale: Option, - /// Platform type for responsive design +pub struct SessionContext { + /// Active git branch #[serde(skip_serializing_if = "Option::is_none")] - pub platform: Option, - /// UI theme preference per SEP-1865 + pub branch: Option, + /// Most recent working directory for this session + pub cwd: String, + /// Git repository root, if the cwd was inside a git repo #[serde(skip_serializing_if = "Option::is_none")] - pub theme: Option, - /// IANA timezone, e.g. 'America/New_York' + pub git_root: Option, + /// Repository host type #[serde(skip_serializing_if = "Option::is_none")] - pub time_zone: Option, - /// Host application identifier + pub host_type: Option, + /// Repository slug in `owner/name` form, when known #[serde(skip_serializing_if = "Option::is_none")] - pub user_agent: Option, + pub repository: Option, } -/// Host context to advertise to MCP App guests. +/// Persisted local session metadata, including identifiers, timestamps, summary/name, client, context, detached state, and task ID. /// ///
/// @@ -5408,12 +5899,36 @@ pub struct McpAppsSetHostContextDetails { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpAppsSetHostContextRequest { - /// Host context advertised to MCP App guests - pub context: McpAppsSetHostContextDetails, +pub struct LocalSessionMetadataValue { + /// Runtime client name that created/last resumed this session + #[serde(skip_serializing_if = "Option::is_none")] + pub client_name: Option, + /// Pre-resolved working-directory context for session startup. + #[serde(skip_serializing_if = "Option::is_none")] + pub context: Option, + /// True for detached maintenance sessions that should be hidden from normal resume lists. + #[serde(skip_serializing_if = "Option::is_none")] + pub is_detached: Option, + /// Always false for local sessions. + pub is_remote: bool, + /// GitHub task ID, when this local session is bound to one. Only present for local sessions exported to remote control. + #[serde(skip_serializing_if = "Option::is_none")] + pub mc_task_id: Option, + /// Last-modified time of the session's persisted state, as ISO 8601 + pub modified_time: String, + /// Optional human-friendly name set via /rename + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + /// Stable session identifier + pub session_id: SessionId, + /// Session creation time as an ISO 8601 timestamp + pub start_time: String, + /// Short summary of the session, when one has been derived + #[serde(skip_serializing_if = "Option::is_none")] + pub summary: Option, } -/// The requestId previously passed to executeSampling that should be cancelled. +/// Message text, optional severity level, persistence flag, optional follow-up URL, and optional tip. /// ///
/// @@ -5423,12 +5938,27 @@ pub struct McpAppsSetHostContextRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpCancelSamplingExecutionParams { - /// The requestId previously passed to executeSampling that should be cancelled - pub request_id: RequestId, +pub struct LogRequest { + /// When true, the message is transient and not persisted to the session event log on disk + #[serde(skip_serializing_if = "Option::is_none")] + pub ephemeral: Option, + /// Log severity level. Determines how the message is displayed in the timeline. Defaults to "info". + #[serde(skip_serializing_if = "Option::is_none")] + pub level: Option, + /// Human-readable message + pub message: String, + /// Optional actionable tip displayed alongside the message. Only honored on `level: "info"`. + #[serde(skip_serializing_if = "Option::is_none")] + pub tip: Option, + /// Domain category for this log entry (e.g., "mcp", "subscription", "policy", "model"). Maps to `infoType`/`warningType`/`errorType` on the emitted event. Defaults to "notification". + #[serde(skip_serializing_if = "Option::is_none")] + pub r#type: Option, + /// Optional URL the user can open in their browser for more details + #[serde(skip_serializing_if = "Option::is_none")] + pub url: Option, } -/// Indicates whether an in-flight sampling execution with the given requestId was found and cancelled. +/// Identifier of the session event that was emitted for the log message. /// ///
/// @@ -5438,12 +5968,12 @@ pub struct McpCancelSamplingExecutionParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpCancelSamplingExecutionResult { - /// True if an in-flight execution with the given requestId was found and signalled to cancel. False when no such execution is in flight (already completed, never started, or cancelled by another caller). - pub cancelled: bool, +pub struct LogResult { + /// The unique identifier of the emitted session event + pub event_id: String, } -/// MCP server name and configuration to add to user configuration. +/// Parameters for (re)loading the merged LSP configuration set. /// ///
/// @@ -5453,14 +5983,19 @@ pub struct McpCancelSamplingExecutionResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpConfigAddRequest { - /// MCP server configuration (stdio process or remote HTTP/SSE) - pub config: serde_json::Value, - /// Unique name for the MCP server - pub name: String, +pub struct LspInitializeRequest { + /// Force re-initialization even when LSP configs were already loaded for the working directory. + #[serde(skip_serializing_if = "Option::is_none")] + pub force: Option, + /// Git root used as the boundary when traversing for project-level LSP configs (supports monorepos). + #[serde(skip_serializing_if = "Option::is_none")] + pub git_root: Option, + /// Working directory used to load project-level LSP configs. Defaults to the session working directory when omitted. + #[serde(skip_serializing_if = "Option::is_none")] + pub working_directory: Option, } -/// MCP server names to disable for new sessions. +/// Validated device-managed settings discovered before a session exists. /// ///
/// @@ -5470,12 +6005,16 @@ pub struct McpConfigAddRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpConfigDisableRequest { - /// Names of MCP servers to disable. Each server is added to the persisted disabled list so new sessions skip it. Already-disabled names are ignored. Active sessions keep their current connections until they end. - pub names: Vec, +pub struct ManagedSettingsReadResult { + /// Discovery or validation error text when managed settings could not be read safely. + #[serde(skip_serializing_if = "Option::is_none")] + pub error_message: Option, + /// Validated, canonical managed-settings JSON. Omitted when no managed settings were discovered or when discovered settings failed validation. + #[serde(skip_serializing_if = "Option::is_none")] + pub settings_json: Option, } -/// MCP server names to enable for new sessions. +/// Result of registering a new marketplace. /// ///
/// @@ -5485,12 +6024,12 @@ pub struct McpConfigDisableRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpConfigEnableRequest { - /// Names of MCP servers to enable. Each server is removed from the persisted disabled list so new sessions spawn it. Unknown or already-enabled names are ignored. - pub names: Vec, +pub struct MarketplaceAddResult { + /// Final name of the marketplace as resolved from its manifest + pub name: String, } -/// User-configured MCP servers, keyed by server name. +/// Plugin entry advertised by a marketplace. /// ///
/// @@ -5500,12 +6039,15 @@ pub struct McpConfigEnableRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpConfigList { - /// All MCP servers from user config, keyed by name - pub servers: HashMap, +pub struct MarketplacePluginInfo { + /// Short description from the marketplace catalog, when present + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + /// Plugin name as listed in the marketplace catalog + pub name: String, } -/// MCP server name to remove from user configuration. +/// Plugins advertised by the marketplace. /// ///
/// @@ -5515,12 +6057,12 @@ pub struct McpConfigList { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpConfigRemoveRequest { - /// Name of the MCP server to remove - pub name: String, +pub struct MarketplaceBrowseResult { + /// Plugins advertised by the marketplace + pub plugins: Vec, } -/// MCP server name and replacement configuration to write to user configuration. +/// Registered marketplace summary. /// ///
/// @@ -5530,14 +6072,17 @@ pub struct McpConfigRemoveRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpConfigUpdateRequest { - /// MCP server configuration (stdio process or remote HTTP/SSE) - pub config: serde_json::Value, - /// Name of the MCP server to update +pub struct MarketplaceInfo { + /// True when this is a default marketplace shipped with the runtime. Defaults are not removable. + #[serde(skip_serializing_if = "Option::is_none")] + pub is_default: Option, + /// Marketplace name (matches the @marketplace suffix in plugin specs) pub name: String, + /// Human-readable description of where the marketplace data is fetched from (e.g. "GitHub: owner/repo"). + pub source: String, } -/// Opaque auth info used to configure GitHub MCP. +/// All registered marketplaces, including built-in defaults. /// ///
/// @@ -5547,13 +6092,12 @@ pub struct McpConfigUpdateRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub(crate) struct McpConfigureGitHubRequest { - /// Opaque runtime auth info for GitHub MCP configuration. Marked internal: an in-process runtime shape (configureGitHubMcp is a no-op over the wire). - #[doc(hidden)] - pub(crate) auth_info: serde_json::Value, +pub struct MarketplaceListResult { + /// Registered marketplaces + pub marketplaces: Vec, } -/// Result of configuring GitHub MCP. +/// Per-marketplace refresh result, including marketplace name, success flag, and optional failure error. /// ///
/// @@ -5563,12 +6107,17 @@ pub(crate) struct McpConfigureGitHubRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpConfigureGitHubResult { - /// Whether GitHub MCP configuration changed. - pub changed: bool, +pub struct MarketplaceRefreshEntry { + /// Error message (failure only) + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + /// Marketplace name that was refreshed + pub name: String, + /// Whether the refresh succeeded + pub success: bool, } -/// Name of the MCP server to disable for the session. +/// Result of refreshing one or more marketplace catalogs. /// ///
/// @@ -5578,12 +6127,12 @@ pub struct McpConfigureGitHubResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpDisableRequest { - /// Name of the MCP server to disable - pub server_name: String, +pub struct MarketplaceRefreshResult { + /// Per-marketplace refresh results in deterministic order. + pub results: Vec, } -/// Optional working directory used as context for MCP server discovery. +/// Outcome of the remove attempt, including dependent-plugin info when applicable. /// ///
/// @@ -5593,13 +6142,15 @@ pub struct McpDisableRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpDiscoverRequest { - /// Working directory used as context for discovery (e.g., plugin resolution) +pub struct MarketplaceRemoveResult { + /// Names of installed plugins that prevented removal. Populated only when `removed=false`. #[serde(skip_serializing_if = "Option::is_none")] - pub working_directory: Option, + pub dependent_plugins: Option>, + /// True when the marketplace was actually removed. False when removal was skipped because the marketplace has dependent plugins and `force` was not set. + pub removed: bool, } -/// MCP servers discovered from user, workspace, plugin, and built-in sources. +/// MCP server allowed by policy, with server name and optional PII-free explanatory note. /// ///
/// @@ -5609,12 +6160,15 @@ pub struct McpDiscoverRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpDiscoverResult { - /// MCP servers discovered from all sources - pub servers: Vec, +pub struct McpAllowedServer { + /// Allowed server name + pub name: String, + /// PII-free note explaining why the server was allowed + #[serde(skip_serializing_if = "Option::is_none")] + pub redacted_note: Option, } -/// Name of the MCP server to enable for the session. +/// MCP server, tool name, and arguments to invoke from an MCP App view. /// ///
/// @@ -5624,12 +6178,19 @@ pub struct McpDiscoverResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpEnableRequest { - /// Name of the MCP server to enable +pub struct McpAppsCallToolRequest { + /// Tool arguments + #[serde(skip_serializing_if = "Option::is_none")] + pub arguments: Option>, + /// **Required.** Server whose ui:// view issued the request. Per SEP-1865 ('callable by the app from this server only'), the call is rejected when this differs from `serverName`, and rejected outright when missing. + pub origin_server_name: String, + /// MCP server hosting the tool pub server_name: String, + /// MCP tool name + pub tool_name: String, } -/// Raw MCP CreateMessageRequest params, as received in the `sampling.requested` event. Treated as opaque at the schema layer; the runtime converts the embedded MCP messages into the OpenAI chat-completion shape internally. +/// Capability negotiation snapshot /// ///
/// @@ -5639,9 +6200,16 @@ pub struct McpEnableRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpExecuteSamplingRequest {} +pub struct McpAppsDiagnoseCapability { + /// Whether the runtime advertises `extensions.io.modelcontextprotocol/ui` to MCP servers + pub advertised: bool, + /// Whether the MCP_APPS feature flag (or COPILOT_MCP_APPS env override) is on + pub feature_flag_enabled: bool, + /// Whether the session has the `mcp-apps` capability + pub session_has_mcp_apps: bool, +} -/// Identifiers and raw MCP CreateMessageRequest params used to run a sampling inference. +/// MCP server to diagnose MCP Apps wiring for. /// ///
/// @@ -5651,18 +6219,12 @@ pub struct McpExecuteSamplingRequest {} ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpExecuteSamplingParams { - /// The original MCP JSON-RPC request ID (string or number). Used by the runtime to correlate the inference with the originating MCP request for telemetry; this is distinct from `requestId` (which is the schema-level cancellation handle). - pub mcp_request_id: serde_json::Value, - /// Raw MCP CreateMessageRequest params, as received in the `sampling.requested` event. Treated as opaque at the schema layer; the runtime converts the embedded MCP messages into the OpenAI chat-completion shape internally. - pub request: McpExecuteSamplingRequest, - /// Caller-provided unique identifier for this sampling execution. Use this same ID with cancelSamplingExecution to cancel the in-flight call. Must be unique within the session for the lifetime of the call. - pub request_id: RequestId, - /// Name of the MCP server that initiated the sampling request +pub struct McpAppsDiagnoseRequest { + /// MCP server to probe pub server_name: String, } -/// MCP server filtered by policy, with name, reason, optional redacted reason, and enterprise login. +/// What the server returned for this session /// ///
/// @@ -5672,34 +6234,35 @@ pub struct McpExecuteSamplingParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpFilteredServer { - /// Enterprise login associated with an allowlist policy - #[serde(skip_serializing_if = "Option::is_none")] - pub enterprise_name: Option, - /// Filtered server name - pub name: String, - /// Human-readable filter reason - pub reason: String, - /// PII-free filter reason - #[serde(skip_serializing_if = "Option::is_none")] - pub redacted_reason: Option, -} - -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct McpHeadersHandlePendingHeadersRefreshRequestHeaders { - /// Headers to overlay onto the MCP request. Dynamic headers override static config headers but do not replace SDK-managed request headers. - pub headers: HashMap, - pub kind: McpHeadersHandlePendingHeadersRefreshRequestHeadersKind, +pub struct McpAppsDiagnoseServer { + /// Whether the named server is currently connected + pub connected: bool, + /// Up to 5 tool names with `_meta.ui` for quick inspection + pub sample_tool_names: Vec, + /// Total tools returned by the server's tools/list + pub tool_count: f64, + /// Tools whose `_meta.ui` is populated (resourceUri and/or visibility set) + pub tools_with_ui_meta: f64, } +/// Diagnostic snapshot of MCP Apps wiring for the named server. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpHeadersHandlePendingHeadersRefreshRequestNone { - pub kind: McpHeadersHandlePendingHeadersRefreshRequestNoneKind, +pub struct McpAppsDiagnoseResult { + /// Capability negotiation snapshot + pub capability: McpAppsDiagnoseCapability, + /// What the server returned for this session + pub server: McpAppsDiagnoseServer, } -/// MCP headers refresh request id and the host response. +/// Current host context /// ///
/// @@ -5707,16 +6270,33 @@ pub struct McpHeadersHandlePendingHeadersRefreshRequestNone { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpHeadersHandlePendingHeadersRefreshRequestRequest { - /// Headers refresh request identifier from mcp.headers_refresh_required - pub request_id: RequestId, - /// Host response: supply dynamic headers or decline this refresh. - pub result: McpHeadersHandlePendingHeadersRefreshRequest, +pub struct McpAppsHostContextDetails { + /// Display modes the host supports + #[serde(skip_serializing_if = "Option::is_none")] + pub available_display_modes: Option>, + /// Current display mode (SEP-1865) + #[serde(skip_serializing_if = "Option::is_none")] + pub display_mode: Option, + /// BCP-47 locale, e.g. 'en-US' + #[serde(skip_serializing_if = "Option::is_none")] + pub locale: Option, + /// Platform type for responsive design + #[serde(skip_serializing_if = "Option::is_none")] + pub platform: Option, + /// UI theme preference per SEP-1865 + #[serde(skip_serializing_if = "Option::is_none")] + pub theme: Option, + /// IANA timezone, e.g. 'America/New_York' + #[serde(skip_serializing_if = "Option::is_none")] + pub time_zone: Option, + /// Host application identifier + #[serde(skip_serializing_if = "Option::is_none")] + pub user_agent: Option, } -/// Indicates whether the pending MCP headers refresh response was accepted. +/// Current host context advertised to MCP App guests. /// ///
/// @@ -5726,12 +6306,12 @@ pub struct McpHeadersHandlePendingHeadersRefreshRequestRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpHeadersHandlePendingHeadersRefreshRequestResult { - /// Whether the response was accepted. False if the request was unknown, timed out, or already resolved. - pub success: bool, +pub struct McpAppsHostContext { + /// Current host context + pub context: McpAppsHostContextDetails, } -/// Recorded MCP server connection failure. +/// MCP server to list app-callable tools for. /// ///
/// @@ -5741,14 +6321,14 @@ pub struct McpHeadersHandlePendingHeadersRefreshRequestResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpServerFailureInfo { - /// Failure message produced when the MCP server connection failed. - pub message: String, - /// epoch-ms timestamp at which the failure was recorded. - pub timestamp: i64, +pub struct McpAppsListToolsRequest { + /// **Required.** Server whose ui:// view issued the request. Per SEP-1865 ('callable by the app from this server only'), the call is rejected when this differs from `serverName`, and rejected outright when missing. + pub origin_server_name: String, + /// MCP server hosting the app + pub server_name: String, } -/// Recorded MCP server pending-auth state. +/// App-callable tools from the named MCP server. /// ///
/// @@ -5758,12 +6338,12 @@ pub struct McpServerFailureInfo { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpServerNeedsAuthInfo { - /// epoch-ms timestamp at which the server signalled it needs authentication. - pub timestamp: i64, +pub struct McpAppsListToolsResult { + /// App-callable tools from the server + pub tools: Vec>, } -/// Host-level state, omitted when no MCP host is initialized. +/// MCP server and resource URI to fetch. /// ///
/// @@ -5773,24 +6353,14 @@ pub struct McpServerNeedsAuthInfo { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpHostState { - /// Names of currently-connected MCP clients. - pub clients: Vec, - /// Configured servers that are explicitly disabled. - pub disabled_servers: Vec, - /// Map of server name to recorded connection failure. - pub failed_servers: HashMap, - /// Configured servers filtered out by enterprise allowlist policy. - pub filtered_servers: Vec, - /// Whether third-party MCP servers are policy-enabled for this session. - pub mcp3p_enabled: bool, - /// Map of server name to recorded pending-auth state. - pub needs_auth_servers: HashMap, - /// Names of servers with in-flight connection attempts. - pub pending_connections: Vec, +pub struct McpAppsReadResourceRequest { + /// Name of the MCP server hosting the resource + pub server_name: String, + /// Resource URI (typically ui://...) + pub uri: String, } -/// Server name to check running status for. +/// MCP Apps resource content with URI, optional MIME type, text or base64 blob, and resource metadata. /// ///
/// @@ -5800,12 +6370,24 @@ pub struct McpHostState { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpIsServerRunningRequest { - /// Name of the MCP server to check - pub server_name: String, +pub struct McpAppsResourceContent { + /// Resource-level metadata (CSP, permissions, etc.) + #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] + pub meta: Option>, + /// Base64-encoded binary content + #[serde(skip_serializing_if = "Option::is_none")] + pub blob: Option, + /// MIME type of the content + #[serde(skip_serializing_if = "Option::is_none")] + pub mime_type: Option, + /// Text content (e.g. HTML) + #[serde(skip_serializing_if = "Option::is_none")] + pub text: Option, + /// The resource URI (typically ui://...) + pub uri: String, } -/// Whether the named MCP server is running. +/// Resource contents returned by the MCP server. /// ///
/// @@ -5815,12 +6397,12 @@ pub struct McpIsServerRunningRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpIsServerRunningResult { - /// True if the server has an active client and transport. - pub running: bool, +pub struct McpAppsReadResourceResult { + /// Resource contents returned by the server + pub contents: Vec, } -/// Server name whose tool list should be returned. +/// Host context advertised to MCP App guests /// ///
/// @@ -5830,12 +6412,31 @@ pub struct McpIsServerRunningResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpListToolsRequest { - /// Name of the connected MCP server whose tools to list. - pub server_name: String, +pub struct McpAppsSetHostContextDetails { + /// Display modes the host supports + #[serde(skip_serializing_if = "Option::is_none")] + pub available_display_modes: Option>, + /// Current display mode (SEP-1865) + #[serde(skip_serializing_if = "Option::is_none")] + pub display_mode: Option, + /// BCP-47 locale, e.g. 'en-US' + #[serde(skip_serializing_if = "Option::is_none")] + pub locale: Option, + /// Platform type for responsive design + #[serde(skip_serializing_if = "Option::is_none")] + pub platform: Option, + /// UI theme preference per SEP-1865 + #[serde(skip_serializing_if = "Option::is_none")] + pub theme: Option, + /// IANA timezone, e.g. 'America/New_York' + #[serde(skip_serializing_if = "Option::is_none")] + pub time_zone: Option, + /// Host application identifier + #[serde(skip_serializing_if = "Option::is_none")] + pub user_agent: Option, } -/// Normalized MCP Apps discovery metadata from a tool's `_meta.ui` block. +/// Host context to advertise to MCP App guests. /// ///
/// @@ -5845,16 +6446,12 @@ pub struct McpListToolsRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpToolUi { - /// URI of the tool's MCP App resource, typically a `ui://` resource identifier. Use `session.mcp.resources.read` to fetch its HTML and resource metadata. - #[serde(skip_serializing_if = "Option::is_none")] - pub resource_uri: Option, - /// Tool visibility advertised by the server. When absent, MCP Apps defaults apply. - #[serde(skip_serializing_if = "Option::is_none")] - pub visibility: Option>, +pub struct McpAppsSetHostContextRequest { + /// Host context advertised to MCP App guests + pub context: McpAppsSetHostContextDetails, } -/// MCP tool metadata with tool name, optional description, and normalized MCP Apps discovery metadata. +/// The requestId previously passed to executeSampling that should be cancelled. /// ///
/// @@ -5864,18 +6461,12 @@ pub struct McpToolUi { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpTools { - /// Tool description, when provided. - #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, - /// Tool name. - pub name: String, - /// Normalized MCP Apps discovery metadata. An empty object indicates that a valid `_meta.ui` block was present without recognized fields. - #[serde(skip_serializing_if = "Option::is_none")] - pub ui: Option, +pub struct McpCancelSamplingExecutionParams { + /// The requestId previously passed to executeSampling that should be cancelled + pub request_id: RequestId, } -/// Tools exposed by the connected MCP server. Throws when the server is not connected. +/// Indicates whether an in-flight sampling execution with the given requestId was found and cancelled. /// ///
/// @@ -5885,32 +6476,29 @@ pub struct McpTools { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpListToolsResult { - /// Tools exposed by the server. - pub tools: Vec, -} - -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct McpOauthPendingRequestResponseToken { - /// Access token acquired by the SDK host - pub access_token: String, - /// Token lifetime in seconds, if known. - #[serde(skip_serializing_if = "Option::is_none")] - pub expires_in: Option, - pub kind: McpOauthPendingRequestResponseTokenKind, - /// OAuth token type. Defaults to Bearer when omitted. - #[serde(skip_serializing_if = "Option::is_none")] - pub token_type: Option, +pub struct McpCancelSamplingExecutionResult { + /// True if an in-flight execution with the given requestId was found and signalled to cancel. False when no such execution is in flight (already completed, never started, or cancelled by another caller). + pub cancelled: bool, } +/// MCP server name and configuration to add to user configuration. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpOauthPendingRequestResponseCancelled { - pub kind: McpOauthPendingRequestResponseCancelledKind, +pub struct McpConfigAddRequest { + /// MCP server configuration (stdio process or remote HTTP/SSE) + pub config: serde_json::Value, + /// Unique name for the MCP server + pub name: String, } -/// Pending MCP OAuth request ID and host-provided token or cancellation response. +/// MCP server names to disable for new sessions. /// ///
/// @@ -5918,16 +6506,14 @@ pub struct McpOauthPendingRequestResponseCancelled { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpOauthHandlePendingRequest { - /// OAuth request identifier from the mcp.oauth_required event - pub request_id: RequestId, - /// Host response to the pending OAuth request. - pub result: McpOauthPendingRequestResponse, +pub struct McpConfigDisableRequest { + /// Names of MCP servers to disable. Each server is added to the persisted disabled list so new sessions skip it. Already-disabled names are ignored. Active sessions keep their current connections until they end. + pub names: Vec, } -/// Indicates whether the pending MCP OAuth response was accepted. +/// MCP server names to enable for new sessions. /// ///
/// @@ -5937,12 +6523,12 @@ pub struct McpOauthHandlePendingRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpOauthHandlePendingResult { - /// Whether the response was accepted. False if the request was unknown, timed out, or already resolved. - pub success: bool, +pub struct McpConfigEnableRequest { + /// Names of MCP servers to enable. Each server is removed from the persisted disabled list so new sessions spawn it. Unknown or already-enabled names are ignored. + pub names: Vec, } -/// Remote MCP server name and optional overrides controlling reauthentication, OAuth client display name, callback success-page copy, and static OAuth client selection. +/// User-configured MCP servers, keyed by server name. /// ///
/// @@ -5952,33 +6538,12 @@ pub struct McpOauthHandlePendingResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpOauthLoginRequest { - /// Optional override for the body text shown on the OAuth loopback callback success page. When omitted, the runtime applies a neutral fallback; callers driving interactive auth should pass surface-specific copy telling the user where to return. - #[serde(skip_serializing_if = "Option::is_none")] - pub callback_success_message: Option, - /// Optional OAuth client ID override for this login. When set, the runtime uses this pre-registered static client instead of dynamic client registration. - #[serde(skip_serializing_if = "Option::is_none")] - pub client_id: Option, - /// Optional override for the OAuth client display name shown on the consent screen. Applies to newly registered dynamic clients only — existing registrations keep the name they were created with. When omitted, the runtime applies a neutral fallback; callers driving interactive auth should pass their own surface-specific label so the consent screen matches the product the user sees. - #[serde(skip_serializing_if = "Option::is_none")] - pub client_name: Option, - /// Optional OAuth client secret override for this login. The runtime treats this as an ephemeral host-owned secret, uses it for this authentication attempt and does not persist it. - #[serde(skip_serializing_if = "Option::is_none")] - pub client_secret: Option, - /// When true, clears any cached OAuth token for the server and runs a full new authorization. Use when the user explicitly wants to switch accounts or believes their session is stuck. - #[serde(skip_serializing_if = "Option::is_none")] - pub force_reauth: Option, - /// Optional OAuth grant type override for this login. Defaults to the server configuration, or authorization_code when no grant type is specified. - #[serde(skip_serializing_if = "Option::is_none")] - pub grant_type: Option, - /// Optional override indicating whether the static OAuth client is public. When false, the runtime treats it as confidential and uses the per-login clientSecret if provided, otherwise retrieving the client secret from the MCP OAuth secret store. - #[serde(skip_serializing_if = "Option::is_none")] - pub public_client: Option, - /// Name of the remote MCP server to authenticate - pub server_name: String, +pub struct McpConfigList { + /// All MCP servers from user config, keyed by name + pub servers: HashMap, } -/// OAuth authorization URL the caller should open, or empty when cached tokens already authenticated the server. +/// MCP server name to remove from user configuration. /// ///
/// @@ -5988,13 +6553,12 @@ pub struct McpOauthLoginRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpOauthLoginResult { - /// URL the caller should open in a browser to complete OAuth. Omitted when cached tokens were still valid and no browser interaction was needed — the server is already reconnected in that case. When present, the runtime starts the callback listener before returning and continues the flow in the background; completion is signaled via session.mcp_server_status_changed. - #[serde(skip_serializing_if = "Option::is_none")] - pub authorization_url: Option, +pub struct McpConfigRemoveRequest { + /// Name of the MCP server to remove + pub name: String, } -/// Registration parameters for an external MCP client. +/// MCP server name and replacement configuration to write to user configuration. /// ///
/// @@ -6004,21 +6568,14 @@ pub struct McpOauthLoginResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub(crate) struct McpRegisterExternalClientRequest { - /// In-process MCP Client instance. Marked internal: cannot be serialized across the JSON-RPC boundary. - #[doc(hidden)] - pub(crate) client: serde_json::Value, - /// In-process server config (MCPServerConfig) paired with the in-process client/transport. Marked internal alongside its companions. - #[doc(hidden)] - pub(crate) config: serde_json::Value, - /// Logical server name for the external client - pub server_name: String, - /// In-process MCP Transport instance. Marked internal: cannot be serialized across the JSON-RPC boundary. - #[doc(hidden)] - pub(crate) transport: serde_json::Value, +pub struct McpConfigUpdateRequest { + /// MCP server configuration (stdio process or remote HTTP/SSE) + pub config: serde_json::Value, + /// Name of the MCP server to update + pub name: String, } -/// Opaque MCP reload configuration. +/// Opaque auth info used to configure GitHub MCP. /// ///
/// @@ -6028,13 +6585,13 @@ pub(crate) struct McpRegisterExternalClientRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub(crate) struct McpReloadWithConfigRequest { - /// Opaque runtime MCP reload configuration. Marked internal: an in-process runtime shape (reloadMcpServers throws over the wire). +pub(crate) struct McpConfigureGitHubRequest { + /// Opaque runtime auth info for GitHub MCP configuration. Marked internal: an in-process runtime shape (configureGitHubMcp is a no-op over the wire). #[doc(hidden)] - pub(crate) config: serde_json::Value, + pub(crate) auth_info: serde_json::Value, } -/// Indicates whether the auto-managed `github` MCP server was removed (false when nothing to remove). +/// Result of configuring GitHub MCP. /// ///
/// @@ -6044,12 +6601,12 @@ pub(crate) struct McpReloadWithConfigRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpRemoveGitHubResult { - /// True when the auto-managed `github` MCP server was removed; false when no removal happened (e.g. user has explicitly configured a `github` server, or the server was not registered). - pub removed: bool, +pub struct McpConfigureGitHubResult { + /// Whether GitHub MCP configuration changed. + pub changed: bool, } -/// Standard MCP resource annotations plus preserved non-standard annotation fields. +/// Name of the MCP server to disable for the session. /// ///
/// @@ -6059,22 +6616,12 @@ pub struct McpRemoveGitHubResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpResourceAnnotations { - /// Server-provided non-standard annotation fields preserved from the MCP response - #[serde(skip_serializing_if = "Option::is_none")] - pub additional_properties: Option>, - /// Intended audience roles for this resource - #[serde(skip_serializing_if = "Option::is_none")] - pub audience: Option>, - /// Last-modified timestamp hint - #[serde(skip_serializing_if = "Option::is_none")] - pub last_modified: Option, - /// Priority hint for model/client use - #[serde(skip_serializing_if = "Option::is_none")] - pub priority: Option, +pub struct McpDisableRequest { + /// Name of the MCP server to disable + pub server_name: String, } -/// A resource icon descriptor plus preserved non-standard icon fields. +/// Optional working directory used as context for MCP server discovery. /// ///
/// @@ -6084,24 +6631,13 @@ pub struct McpResourceAnnotations { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpResourceIcon { - /// Server-provided non-standard icon fields preserved from the MCP response - #[serde(skip_serializing_if = "Option::is_none")] - pub additional_properties: Option>, - /// Icon MIME type, when known - #[serde(skip_serializing_if = "Option::is_none")] - pub mime_type: Option, - /// Icon sizes hint - #[serde(skip_serializing_if = "Option::is_none")] - pub sizes: Option, - /// Icon URI - pub src: String, - /// Theme hint for this icon +pub struct McpDiscoverRequest { + /// Working directory used as context for discovery (e.g., plugin resolution) #[serde(skip_serializing_if = "Option::is_none")] - pub theme: Option, + pub working_directory: Option, } -/// An MCP resource descriptor (spec `Resource`): URI, name, and optional title, description, MIME type, size, icons, annotations, and metadata. Server-provided fields outside the standard descriptor shape are exposed under `additionalProperties`. +/// MCP servers discovered from user, workspace, plugin, and built-in sources. /// ///
/// @@ -6111,38 +6647,12 @@ pub struct McpResourceIcon { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpResource { - /// Resource-level metadata - #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] - pub meta: Option>, - /// Server-provided non-standard descriptor fields preserved from the MCP response - #[serde(skip_serializing_if = "Option::is_none")] - pub additional_properties: Option>, - /// Model/client annotations associated with this resource - #[serde(skip_serializing_if = "Option::is_none")] - pub annotations: Option, - /// Optional description of what this resource represents - #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, - /// Icons associated with this resource - #[serde(skip_serializing_if = "Option::is_none")] - pub icons: Option>, - /// MIME type of the resource, if known - #[serde(skip_serializing_if = "Option::is_none")] - pub mime_type: Option, - /// The programmatic name of the resource - pub name: String, - /// Resource size in bytes, when known - #[serde(skip_serializing_if = "Option::is_none")] - pub size: Option, - /// Optional human-readable display title - #[serde(skip_serializing_if = "Option::is_none")] - pub title: Option, - /// The resource URI (e.g. ui://... or file:///...) - pub uri: String, +pub struct McpDiscoverResult { + /// MCP servers discovered from all sources + pub servers: Vec, } -/// MCP resource content with URI, optional MIME type, text or base64 blob, and resource metadata. +/// Name of the MCP server to enable for the session. /// ///
/// @@ -6152,24 +6662,12 @@ pub struct McpResource { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpResourceContent { - /// Resource-level metadata (CSP, permissions, etc.) - #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] - pub meta: Option>, - /// Base64-encoded binary content - #[serde(skip_serializing_if = "Option::is_none")] - pub blob: Option, - /// MIME type of the content - #[serde(skip_serializing_if = "Option::is_none")] - pub mime_type: Option, - /// Text content (e.g. HTML) - #[serde(skip_serializing_if = "Option::is_none")] - pub text: Option, - /// The resource URI - pub uri: String, +pub struct McpEnableRequest { + /// Name of the MCP server to enable + pub server_name: String, } -/// MCP server whose resources to enumerate. +/// Raw MCP CreateMessageRequest params, as received in the `sampling.requested` event. Treated as opaque at the schema layer; the runtime converts the embedded MCP messages into the OpenAI chat-completion shape internally. /// ///
/// @@ -6179,15 +6677,9 @@ pub struct McpResourceContent { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpResourcesListRequest { - /// Opaque MCP pagination cursor from a prior `nextCursor` value - #[serde(skip_serializing_if = "Option::is_none")] - pub cursor: Option, - /// Name of the MCP server whose resources to enumerate - pub server_name: String, -} +pub struct McpExecuteSamplingRequest {} -/// One page of resources advertised by the named MCP server. +/// Identifiers and raw MCP CreateMessageRequest params used to run a sampling inference. /// ///
/// @@ -6197,15 +6689,18 @@ pub struct McpResourcesListRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpResourcesListResult { - /// Opaque cursor for the next page, if the server has more resources - #[serde(skip_serializing_if = "Option::is_none")] - pub next_cursor: Option, - /// Resources advertised by the server (proxied MCP `resources/list`) - pub resources: Vec, +pub struct McpExecuteSamplingParams { + /// The original MCP JSON-RPC request ID (string or number). Used by the runtime to correlate the inference with the originating MCP request for telemetry; this is distinct from `requestId` (which is the schema-level cancellation handle). + pub mcp_request_id: serde_json::Value, + /// Raw MCP CreateMessageRequest params, as received in the `sampling.requested` event. Treated as opaque at the schema layer; the runtime converts the embedded MCP messages into the OpenAI chat-completion shape internally. + pub request: McpExecuteSamplingRequest, + /// Caller-provided unique identifier for this sampling execution. Use this same ID with cancelSamplingExecution to cancel the in-flight call. Must be unique within the session for the lifetime of the call. + pub request_id: RequestId, + /// Name of the MCP server that initiated the sampling request + pub server_name: String, } -/// MCP server whose resource templates to enumerate. +/// MCP server filtered by policy, with name, reason, and optional redacted reason. /// ///
/// @@ -6215,15 +6710,36 @@ pub struct McpResourcesListResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpResourcesListTemplatesRequest { - /// Opaque MCP pagination cursor from a prior `nextCursor` value +pub struct McpFilteredServer { + /// Deprecated. This field is no longer populated. + #[doc(hidden)] + #[deprecated] #[serde(skip_serializing_if = "Option::is_none")] - pub cursor: Option, - /// Name of the MCP server whose resource templates to enumerate - pub server_name: String, + pub enterprise_name: Option, + /// Filtered server name + pub name: String, + /// Human-readable filter reason + pub reason: String, + /// PII-free filter reason + #[serde(skip_serializing_if = "Option::is_none")] + pub redacted_reason: Option, } -/// An MCP resource template descriptor (spec `ResourceTemplate`): an RFC 6570 URI template, name, and optional title, description, MIME type, icons, annotations, and metadata. Server-provided fields outside the standard descriptor shape are exposed under `additionalProperties`. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct McpHeadersHandlePendingHeadersRefreshRequestHeaders { + /// Headers to overlay onto the MCP request. Dynamic headers override static config headers but do not replace SDK-managed request headers. + pub headers: HashMap, + pub kind: McpHeadersHandlePendingHeadersRefreshRequestHeadersKind, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct McpHeadersHandlePendingHeadersRefreshRequestNone { + pub kind: McpHeadersHandlePendingHeadersRefreshRequestNoneKind, +} + +/// MCP headers refresh request id and the host response. /// ///
/// @@ -6231,37 +6747,16 @@ pub struct McpResourcesListTemplatesRequest { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpResourceTemplate { - /// Resource-template-level metadata - #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] - pub meta: Option>, - /// Server-provided non-standard descriptor fields preserved from the MCP response - #[serde(skip_serializing_if = "Option::is_none")] - pub additional_properties: Option>, - /// Model/client annotations associated with this template - #[serde(skip_serializing_if = "Option::is_none")] - pub annotations: Option, - /// Optional description of what this template is for - #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, - /// Icons associated with resources matching this template - #[serde(skip_serializing_if = "Option::is_none")] - pub icons: Option>, - /// MIME type for resources matching this template, if uniform - #[serde(skip_serializing_if = "Option::is_none")] - pub mime_type: Option, - /// The programmatic name of the resource template - pub name: String, - /// Optional human-readable display title - #[serde(skip_serializing_if = "Option::is_none")] - pub title: Option, - /// An RFC 6570 URI template for constructing resource URIs - pub uri_template: String, +pub struct McpHeadersHandlePendingHeadersRefreshRequestRequest { + /// Headers refresh request identifier from mcp.headers_refresh_required + pub request_id: RequestId, + /// Host response: supply dynamic headers or decline this refresh. + pub result: McpHeadersHandlePendingHeadersRefreshRequest, } -/// One page of resource templates advertised by the named MCP server. +/// Indicates whether the pending MCP headers refresh response was accepted. /// ///
/// @@ -6271,15 +6766,12 @@ pub struct McpResourceTemplate { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpResourcesListTemplatesResult { - /// Opaque cursor for the next page, if the server has more resource templates - #[serde(skip_serializing_if = "Option::is_none")] - pub next_cursor: Option, - /// Resource templates advertised by the server (proxied MCP `resources/templates/list`) - pub resource_templates: Vec, +pub struct McpHeadersHandlePendingHeadersRefreshRequestResult { + /// Whether the response was accepted. False if the request was unknown, timed out, or already resolved. + pub success: bool, } -/// MCP server and resource URI to fetch. +/// Recorded MCP server connection failure. /// ///
/// @@ -6289,14 +6781,14 @@ pub struct McpResourcesListTemplatesResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpResourcesReadRequest { - /// Name of the MCP server hosting the resource - pub server_name: String, - /// Resource URI - pub uri: String, +pub struct McpServerFailureInfo { + /// Failure message produced when the MCP server connection failed. + pub message: String, + /// epoch-ms timestamp at which the failure was recorded. + pub timestamp: i64, } -/// Resource contents returned by the MCP server. +/// Recorded MCP server pending-auth state. /// ///
/// @@ -6306,12 +6798,12 @@ pub struct McpResourcesReadRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpResourcesReadResult { - /// Resource contents returned by the server - pub contents: Vec, +pub struct McpServerNeedsAuthInfo { + /// epoch-ms timestamp at which the server signalled it needs authentication. + pub timestamp: i64, } -/// Server name and optional replacement configuration for an individual MCP server restart. Omit `config` for a config-free restart-by-name of an already-configured server. +/// Host-level state, omitted when no MCP host is initialized. /// ///
/// @@ -6321,15 +6813,24 @@ pub struct McpResourcesReadResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpRestartServerRequest { - /// Replacement MCP server configuration (stdio process or remote HTTP/SSE). Omit to restart the server with its already-registered configuration (config-free restart-by-name). - #[serde(skip_serializing_if = "Option::is_none")] - pub config: Option, - /// Name of the MCP server to restart - pub server_name: String, +pub struct McpHostState { + /// Names of currently-connected MCP clients. + pub clients: Vec, + /// Configured servers that are explicitly disabled. + pub disabled_servers: Vec, + /// Map of server name to recorded connection failure. + pub failed_servers: HashMap, + /// Configured servers filtered out by MCP server policy. + pub filtered_servers: Vec, + /// Whether third-party MCP servers are policy-enabled for this session. + pub mcp3p_enabled: bool, + /// Map of server name to recorded pending-auth state. + pub needs_auth_servers: HashMap, + /// Names of servers with in-flight connection attempts. + pub pending_connections: Vec, } -/// Outcome of an MCP sampling execution: success result, failure error, or cancellation. +/// Server name to check running status for. /// ///
/// @@ -6339,18 +6840,12 @@ pub struct McpRestartServerRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpSamplingExecutionResult { - /// Outcome of the sampling inference. 'success' produced a response; 'failure' encountered an error (including agent-side rejection by content filter or criteria); 'cancelled' the caller cancelled this execution via cancelSamplingExecution. - pub action: McpSamplingExecutionAction, - /// Error description, present when action='failure'. - #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, - /// MCP CreateMessageResult payload (with optional 'tools' extension), present when action='success'. Treated as opaque at the schema layer; consumers should construct/consume it per the MCP CreateMessageResult shape. - #[serde(skip_serializing_if = "Option::is_none")] - pub result: Option, +pub struct McpIsServerRunningRequest { + /// Name of the MCP server to check + pub server_name: String, } -/// MCP server status entry, including config source/plugin source and any connection error. +/// Whether the named MCP server is running. /// ///
/// @@ -6360,26 +6855,12 @@ pub struct McpSamplingExecutionResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpServer { - /// Error message if the server failed to connect - #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, - /// Server name (config key) - pub name: String, - /// Configuration source: user, workspace, plugin, or builtin - #[serde(skip_serializing_if = "Option::is_none")] - pub source: Option, - /// Plugin name that provided this server, when source is plugin. - #[serde(skip_serializing_if = "Option::is_none")] - pub source_plugin: Option, - /// Plugin version that provided this server, when source is plugin. - #[serde(skip_serializing_if = "Option::is_none")] - pub source_plugin_version: Option, - /// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured - pub status: McpServerStatus, +pub struct McpIsServerRunningResult { + /// True if the server has an active client and transport. + pub running: bool, } -/// Authentication settings with optional redirect port configuration. +/// Server name whose tool list should be returned. /// ///
/// @@ -6389,13 +6870,12 @@ pub struct McpServer { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpServerAuthConfigRedirectPort { - /// Fixed port for the OAuth redirect callback server. - #[serde(skip_serializing_if = "Option::is_none")] - pub redirect_port: Option, +pub struct McpListToolsRequest { + /// Name of the connected MCP server whose tools to list. + pub server_name: String, } -/// Remote MCP server configuration accessed over HTTP or SSE. +/// Normalized MCP Apps discovery metadata from a tool's `_meta.ui` block. /// ///
/// @@ -6405,48 +6885,16 @@ pub struct McpServerAuthConfigRedirectPort { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpServerConfigHttp { - /// Set to `true` to use defaults, or provide an object with additional auth or OIDC settings. - #[serde(skip_serializing_if = "Option::is_none")] - pub auth: Option, - /// Controls if tools provided by this server can be loaded on demand via tool search (auto) or always included in the initial tool list (never) - #[serde(skip_serializing_if = "Option::is_none")] - pub defer_tools: Option, - /// Content filtering mode to apply to all tools, or a map of tool name to content filtering mode. - #[serde(skip_serializing_if = "Option::is_none")] - pub filter_mapping: Option, - /// HTTP headers to include in requests to the remote MCP server. - #[serde(skip_serializing_if = "Option::is_none")] - pub headers: Option>, - /// Whether this server is a built-in fallback used when the user has not configured their own server. - #[serde(skip_serializing_if = "Option::is_none")] - pub is_default_server: Option, - /// OAuth client ID for a pre-registered remote MCP OAuth client. - #[serde(skip_serializing_if = "Option::is_none")] - pub oauth_client_id: Option, - /// OAuth grant type to use when authenticating to the remote MCP server. - #[serde(skip_serializing_if = "Option::is_none")] - pub oauth_grant_type: Option, - /// Whether the configured OAuth client is public and does not require a client secret. - #[serde(skip_serializing_if = "Option::is_none")] - pub oauth_public_client: Option, - /// Set to `true` to use defaults, or provide an object with additional auth or OIDC settings. - #[serde(skip_serializing_if = "Option::is_none")] - pub oidc: Option, - /// Timeout in milliseconds for tool calls to this server. - #[serde(skip_serializing_if = "Option::is_none")] - pub timeout: Option, - /// Tools to include. Defaults to all tools if not specified. +pub struct McpToolUi { + /// URI of the tool's MCP App resource, typically a `ui://` resource identifier. Use `session.mcp.resources.read` to fetch its HTML and resource metadata. #[serde(skip_serializing_if = "Option::is_none")] - pub tools: Option>, - /// Remote transport type. Defaults to "http" when omitted. + pub resource_uri: Option, + /// Tool visibility advertised by the server. When absent, MCP Apps defaults apply. #[serde(skip_serializing_if = "Option::is_none")] - pub r#type: Option, - /// URL of the remote MCP server endpoint. - pub url: String, + pub visibility: Option>, } -/// Stdio MCP server configuration launched as a child process. +/// MCP tool metadata with tool name, optional description, and normalized MCP Apps discovery metadata. /// ///
/// @@ -6456,42 +6904,18 @@ pub struct McpServerConfigHttp { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpServerConfigStdio { - /// Command-line arguments passed to the Stdio MCP server process. - #[serde(skip_serializing_if = "Option::is_none")] - pub args: Option>, - /// Set to `true` to use defaults, or provide an object with additional auth or OIDC settings. - #[serde(skip_serializing_if = "Option::is_none")] - pub auth: Option, - /// Executable command used to start the Stdio MCP server process. - pub command: String, - /// Working directory for the Stdio MCP server process. - #[serde(skip_serializing_if = "Option::is_none")] - pub cwd: Option, - /// Controls if tools provided by this server can be loaded on demand via tool search (auto) or always included in the initial tool list (never) - #[serde(skip_serializing_if = "Option::is_none")] - pub defer_tools: Option, - /// Environment variables to pass to the Stdio MCP server process. - #[serde(skip_serializing_if = "Option::is_none")] - pub env: Option>, - /// Content filtering mode to apply to all tools, or a map of tool name to content filtering mode. - #[serde(skip_serializing_if = "Option::is_none")] - pub filter_mapping: Option, - /// Whether this server is a built-in fallback used when the user has not configured their own server. - #[serde(skip_serializing_if = "Option::is_none")] - pub is_default_server: Option, - /// Set to `true` to use defaults, or provide an object with additional auth or OIDC settings. - #[serde(skip_serializing_if = "Option::is_none")] - pub oidc: Option, - /// Timeout in milliseconds for tool calls to this server. +pub struct McpTools { + /// Tool description, when provided. #[serde(skip_serializing_if = "Option::is_none")] - pub timeout: Option, - /// Tools to include. Defaults to all tools if not specified. + pub description: Option, + /// Tool name. + pub name: String, + /// Normalized MCP Apps discovery metadata. An empty object indicates that a valid `_meta.ui` block was present without recognized fields. #[serde(skip_serializing_if = "Option::is_none")] - pub tools: Option>, + pub ui: Option, } -/// MCP servers configured for the session, with their connection status and host-level state. +/// Tools exposed by the connected MCP server. Throws when the server is not connected. /// ///
/// @@ -6501,15 +6925,12 @@ pub struct McpServerConfigStdio { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpServerList { - /// Host-level state, omitted when no MCP host is initialized. - #[serde(skip_serializing_if = "Option::is_none")] - pub host: Option, - /// Configured MCP servers - pub servers: Vec, +pub struct McpListToolsResult { + /// Tools exposed by the server. + pub tools: Vec, } -/// Mode controlling how MCP server env values are resolved (`direct` or `indirect`). +/// Identifies the MCP server whose persisted OAuth credentials were updated. /// ///
/// @@ -6519,12 +6940,36 @@ pub struct McpServerList { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpSetEnvValueModeParams { - /// How environment-variable values supplied to MCP servers are resolved. "direct" passes literal string values; "indirect" treats values as references (e.g. names of environment variables on the host) that the runtime resolves before launch. Defaults to the runtime's startup mode; clients that intentionally launch MCP servers with literal values (e.g. CLI prompt mode and ACP) set this to "direct". - pub mode: McpSetEnvValueModeDetails, +pub struct McpOauthAuthenticationStateChangedRequest { + /// Whether the target session must mint a session-scoped access token instead of reusing a shared access token persisted by another session. + #[serde(skip_serializing_if = "Option::is_none")] + pub refresh_session_token: Option, + /// Name of the MCP server whose OAuth credentials were updated. Omit only when the host cannot identify the server. + #[serde(skip_serializing_if = "Option::is_none")] + pub server_name: Option, } -/// Env-value mode recorded on the session after the update. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct McpOauthPendingRequestResponseToken { + /// Access token acquired by the SDK host + pub access_token: String, + /// Token lifetime in seconds, if known. + #[serde(skip_serializing_if = "Option::is_none")] + pub expires_in: Option, + pub kind: McpOauthPendingRequestResponseTokenKind, + /// OAuth token type. Defaults to Bearer when omitted. + #[serde(skip_serializing_if = "Option::is_none")] + pub token_type: Option, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct McpOauthPendingRequestResponseCancelled { + pub kind: McpOauthPendingRequestResponseCancelledKind, +} + +/// Pending MCP OAuth request ID and host-provided token or cancellation response. /// ///
/// @@ -6532,14 +6977,16 @@ pub struct McpSetEnvValueModeParams { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpSetEnvValueModeResult { - /// Mode recorded on the session after the update - pub mode: McpSetEnvValueModeDetails, +pub struct McpOauthHandlePendingRequest { + /// OAuth request identifier from the mcp.oauth_required event + pub request_id: RequestId, + /// Host response to the pending OAuth request. + pub result: McpOauthPendingRequestResponse, } -/// Server name and configuration for an individual MCP server start. +/// Indicates whether the pending MCP OAuth response was accepted. /// ///
/// @@ -6549,14 +6996,12 @@ pub struct McpSetEnvValueModeResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpStartServerRequest { - /// MCP server configuration (stdio process or remote HTTP/SSE) - pub config: serde_json::Value, - /// Name of the MCP server to start - pub server_name: String, +pub struct McpOauthHandlePendingResult { + /// Whether the response was accepted. False if the request was unknown, timed out, or already resolved. + pub success: bool, } -/// MCP server startup filtering result. +/// Remote MCP server name and optional overrides controlling reauthentication, OAuth client display name, callback success-page copy, and static OAuth client selection. /// ///
/// @@ -6566,15 +7011,33 @@ pub struct McpStartServerRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpStartServersResult { - /// Non-default servers allowed by policy +pub struct McpOauthLoginRequest { + /// Optional override for the body text shown on the OAuth loopback callback success page. When omitted, the runtime applies a neutral fallback; callers driving interactive auth should pass surface-specific copy telling the user where to return. #[serde(skip_serializing_if = "Option::is_none")] - pub allowed_servers: Option>, - /// Servers filtered out before startup - pub filtered_servers: Vec, + pub callback_success_message: Option, + /// Optional OAuth client ID override for this login. When set, the runtime uses this pre-registered static client instead of dynamic client registration. + #[serde(skip_serializing_if = "Option::is_none")] + pub client_id: Option, + /// Optional override for the OAuth client display name shown on the consent screen. Applies to newly registered dynamic clients only — existing registrations keep the name they were created with. When omitted, the runtime applies a neutral fallback; callers driving interactive auth should pass their own surface-specific label so the consent screen matches the product the user sees. + #[serde(skip_serializing_if = "Option::is_none")] + pub client_name: Option, + /// Optional OAuth client secret override for this login. The runtime treats this as an ephemeral host-owned secret, uses it for this authentication attempt and does not persist it. + #[serde(skip_serializing_if = "Option::is_none")] + pub client_secret: Option, + /// When true, clears any cached OAuth token for the server and runs a full new authorization. Use when the user explicitly wants to switch accounts or believes their session is stuck. + #[serde(skip_serializing_if = "Option::is_none")] + pub force_reauth: Option, + /// Optional OAuth grant type override for this login. Defaults to the server configuration, or authorization_code when no grant type is specified. + #[serde(skip_serializing_if = "Option::is_none")] + pub grant_type: Option, + /// Optional override indicating whether the static OAuth client is public. When false, the runtime treats it as confidential and uses the per-login clientSecret if provided, otherwise retrieving the client secret from the MCP OAuth secret store. + #[serde(skip_serializing_if = "Option::is_none")] + pub public_client: Option, + /// Name of the remote MCP server to authenticate + pub server_name: String, } -/// Server name for an individual MCP server stop. +/// OAuth authorization URL the caller should open, or empty when cached tokens already authenticated the server. /// ///
/// @@ -6584,12 +7047,13 @@ pub struct McpStartServersResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpStopServerRequest { - /// Name of the MCP server to stop - pub server_name: String, +pub struct McpOauthLoginResult { + /// URL the caller should open in a browser to complete OAuth. Omitted when cached tokens were still valid and no browser interaction was needed — the server is already reconnected in that case. When present, the runtime starts the callback listener before returning and continues the flow in the background; completion is signaled via session.mcp_server_status_changed. + #[serde(skip_serializing_if = "Option::is_none")] + pub authorization_url: Option, } -/// Server name identifying the external client to remove. +/// Pending MCP OAuth request id to respond to. /// ///
/// @@ -6599,12 +7063,12 @@ pub struct McpStopServerRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub(crate) struct McpUnregisterExternalClientRequest { - /// Server name of the external client to unregister - pub server_name: String, +pub struct McpOauthRespondRequest { + /// OAuth request identifier from the mcp.oauth_required event + pub request_id: RequestId, } -/// Memory configuration for this session. +/// Indicates whether the pending MCP OAuth response was accepted. /// ///
/// @@ -6614,51 +7078,12 @@ pub(crate) struct McpUnregisterExternalClientRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct MemoryConfiguration { - /// Whether memory is enabled for the session. - pub enabled: bool, +pub struct McpOauthRespondResult { + /// Whether the response was accepted. False if the request was unknown, timed out, or already resolved. + pub success: bool, } -/// Successful compaction history for the session. -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct MetadataContextAttributionResultContextAttributionCompactions { - /// Number of successful compactions in this session. - pub count: i64, -} - -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct MetadataContextAttributionResultContextAttributionEntriesItem { - /// Supplementary per-entry metadata (e.g. `messageCount`, `role`, `evictable`, `pluginSource`). Values are stringified; parse as needed and ignore unrecognized keys. - #[serde(skip_serializing_if = "Option::is_none")] - pub attributes: Option>, - /// Identifier for this entry, formed by joining its `kind` and source name (e.g. `tool:bash`, `skill:tmux`, `toolDefinition:bash`); unique within the snapshot. Use it to match the same entry across snapshots, to correlate with other APIs (skill/agent/MCP registries), and as the `parentId` target for nesting. Distinct from the human-facing `label`. - pub id: String, - /// Source category for this entry. Not a closed set — tolerate unknown values. Known values today: `skill`, `subagent`, `mcpServer`, `tool`, `system`, `toolDefinition`, `plugin`. - pub kind: String, - /// Human-readable display label, e.g. `bash` or `skill: tmux`. Presentation-only; may be localized/reformatted without notice — do not key off it. - pub label: String, - /// Optional `id` of the parent entry: e.g. a `plugin` entry parenting its `skill`/`mcpServer` entries, or the `system` entry parenting `toolDefinition` entries. Omitted for top-level entries. - #[serde(skip_serializing_if = "Option::is_none")] - pub parent_id: Option, - /// Token count currently in context attributable to this entry. - pub tokens: i64, -} - -/// Per-source token attribution snapshot for the current context window. The heaviest individual messages are available separately via `metadata.getContextHeaviestMessages`. -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct MetadataContextAttributionResultContextAttribution { - /// Successful compaction history for the session. - pub compactions: MetadataContextAttributionResultContextAttributionCompactions, - /// Flat list of per-source attribution entries. Group by `kind` and render unrecognized kinds generically. Nesting and rollups are expressed via `parentId`. - pub entries: Vec, - /// Total token count of the current context window the entries are measured against (system message + conversation messages + tool definitions — the same total reported by /context). Divide an entry's `tokens` by this to derive its share. - pub total_tokens: i64, -} - -/// Per-source attribution breakdown for the session's current context window, or null if uninitialized. +/// Registration parameters for an external MCP client. /// ///
/// @@ -6668,12 +7093,21 @@ pub struct MetadataContextAttributionResultContextAttribution { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct MetadataContextAttributionResult { - /// Per-source context-window attribution, or null if the session has not yet been initialized (no system prompt or tool metadata cached). - pub context_attribution: Option, +pub(crate) struct McpRegisterExternalClientRequest { + /// In-process MCP Client instance. Marked internal: cannot be serialized across the JSON-RPC boundary. + #[doc(hidden)] + pub(crate) client: serde_json::Value, + /// In-process server config (MCPServerConfig) paired with the in-process client/transport. Marked internal alongside its companions. + #[doc(hidden)] + pub(crate) config: serde_json::Value, + /// Logical server name for the external client + pub server_name: String, + /// In-process MCP Transport instance. Marked internal: cannot be serialized across the JSON-RPC boundary. + #[doc(hidden)] + pub(crate) transport: serde_json::Value, } -/// Parameters for the heaviest-messages query. +/// Opaque MCP reload configuration. /// ///
/// @@ -6683,13 +7117,13 @@ pub struct MetadataContextAttributionResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct MetadataContextHeaviestMessagesRequest { - /// Maximum number of messages to return, most-expensive first. Omit for the server default. - #[serde(skip_serializing_if = "Option::is_none")] - pub limit: Option, +pub(crate) struct McpReloadWithConfigRequest { + /// Opaque runtime MCP reload configuration. Marked internal: an in-process runtime shape (reloadMcpServers throws over the wire). + #[doc(hidden)] + pub(crate) config: serde_json::Value, } -/// The heaviest individual messages in the session's context window, most-expensive first. +/// Indicates whether the auto-managed `github` MCP server was removed (false when nothing to remove). /// ///
/// @@ -6699,14 +7133,12 @@ pub struct MetadataContextHeaviestMessagesRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct MetadataContextHeaviestMessagesResult { - /// Heaviest messages, most-expensive first. - pub messages: Vec, - /// Total token count of the current context window, so callers can compute each message's share without a second call. - pub total_tokens: i64, +pub struct McpRemoveGitHubResult { + /// True when the auto-managed `github` MCP server was removed; false when no removal happened (e.g. user has explicitly configured a `github` server, or the server was not registered). + pub removed: bool, } -/// Model identifier and token limits used to compute the context-info breakdown. +/// Standard MCP resource annotations plus preserved non-standard annotation fields. /// ///
/// @@ -6716,43 +7148,22 @@ pub struct MetadataContextHeaviestMessagesResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct MetadataContextInfoRequest { - /// Maximum output tokens allowed by the target model. Pass 0 if unknown. - pub output_token_limit: i64, - /// Maximum prompt tokens allowed by the target model. Pass 0 to use the runtime default. - pub prompt_token_limit: i64, - /// Model identifier used for tokenization. Omit to use the session default. Used both for token counting and to compute display values. +pub struct McpResourceAnnotations { + /// Server-provided non-standard annotation fields preserved from the MCP response #[serde(skip_serializing_if = "Option::is_none")] - pub selected_model: Option, -} - -/// Token-usage breakdown for the session's current context window -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct MetadataContextInfoResultContextInfo { - /// Output reserve plus tokens after the buffer-exhaustion blocking threshold (default 95%) - pub buffer_tokens: i64, - /// Token count at which background compaction starts (configurable percentage of promptTokenLimit) - pub compaction_threshold: i64, - /// Tokens consumed by user/assistant/tool messages - pub conversation_tokens: i64, - /// Prompt token limit plus the model's full output token limit. - pub limit: i64, - /// Tokens consumed by MCP tool definitions (subset of toolDefinitionsTokens, excludes deferred tools) - pub mcp_tools_tokens: i64, - /// The model used for token counting - pub model_name: String, - /// Maximum prompt tokens allowed by the model (or DEFAULT_TOKEN_LIMIT if unspecified) - pub prompt_token_limit: i64, - /// Tokens consumed by the system prompt - pub system_tokens: i64, - /// Tokens consumed by tool definitions sent to the model (excludes deferred tools) - pub tool_definitions_tokens: i64, - /// Sum of system, conversation and tool-definition tokens - pub total_tokens: i64, + pub additional_properties: Option>, + /// Intended audience roles for this resource + #[serde(skip_serializing_if = "Option::is_none")] + pub audience: Option>, + /// Last-modified timestamp hint + #[serde(skip_serializing_if = "Option::is_none")] + pub last_modified: Option, + /// Priority hint for model/client use + #[serde(skip_serializing_if = "Option::is_none")] + pub priority: Option, } -/// Token breakdown for the session's current context window, or null if uninitialized. +/// A resource icon descriptor plus preserved non-standard icon fields. /// ///
/// @@ -6762,12 +7173,24 @@ pub struct MetadataContextInfoResultContextInfo { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct MetadataContextInfoResult { - /// Token breakdown for the current context window, or null if the session has not yet been initialized (no system prompt or tool metadata cached). - pub context_info: Option, +pub struct McpResourceIcon { + /// Server-provided non-standard icon fields preserved from the MCP response + #[serde(skip_serializing_if = "Option::is_none")] + pub additional_properties: Option>, + /// Icon MIME type, when known + #[serde(skip_serializing_if = "Option::is_none")] + pub mime_type: Option, + /// Icon sizes hint + #[serde(skip_serializing_if = "Option::is_none")] + pub sizes: Option, + /// Icon URI + pub src: String, + /// Theme hint for this icon + #[serde(skip_serializing_if = "Option::is_none")] + pub theme: Option, } -/// Indicates whether the local session is currently processing a turn or background continuation. +/// An MCP resource descriptor (spec `Resource`): URI, name, and optional title, description, MIME type, size, icons, annotations, and metadata. Server-provided fields outside the standard descriptor shape are exposed under `additionalProperties`. /// ///
/// @@ -6777,12 +7200,38 @@ pub struct MetadataContextInfoResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct MetadataIsProcessingResult { - /// Whether the session is currently processing user/agent messages. False for non-local sessions (which don't run a local agentic loop). Reflects an in-flight turn or background continuation. - pub processing: bool, +pub struct McpResource { + /// Resource-level metadata + #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] + pub meta: Option>, + /// Server-provided non-standard descriptor fields preserved from the MCP response + #[serde(skip_serializing_if = "Option::is_none")] + pub additional_properties: Option>, + /// Model/client annotations associated with this resource + #[serde(skip_serializing_if = "Option::is_none")] + pub annotations: Option, + /// Optional description of what this resource represents + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + /// Icons associated with this resource + #[serde(skip_serializing_if = "Option::is_none")] + pub icons: Option>, + /// MIME type of the resource, if known + #[serde(skip_serializing_if = "Option::is_none")] + pub mime_type: Option, + /// The programmatic name of the resource + pub name: String, + /// Resource size in bytes, when known + #[serde(skip_serializing_if = "Option::is_none")] + pub size: Option, + /// Optional human-readable display title + #[serde(skip_serializing_if = "Option::is_none")] + pub title: Option, + /// The resource URI (e.g. ui://... or file:///...) + pub uri: String, } -/// Model identifier to use when re-tokenizing the session's existing messages. +/// MCP resource content with URI, optional MIME type, text or base64 blob, and resource metadata. /// ///
/// @@ -6792,12 +7241,24 @@ pub struct MetadataIsProcessingResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct MetadataRecomputeContextTokensRequest { - /// Model identifier used for tokenization. The runtime token-counts both chat-context and system-context messages against this model. - pub model_id: String, +pub struct McpResourceContent { + /// Resource-level metadata (CSP, permissions, etc.) + #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] + pub meta: Option>, + /// Base64-encoded binary content + #[serde(skip_serializing_if = "Option::is_none")] + pub blob: Option, + /// MIME type of the content + #[serde(skip_serializing_if = "Option::is_none")] + pub mime_type: Option, + /// Text content (e.g. HTML) + #[serde(skip_serializing_if = "Option::is_none")] + pub text: Option, + /// The resource URI + pub uri: String, } -/// Re-tokenize the session's existing messages against `modelId` and return the token totals. Useful for hosts that want an initial estimate of context usage on session resume, before the next agent turn fires `session.context_info_changed` events. Returns zeros for an empty session. +/// MCP server whose resources to enumerate. /// ///
/// @@ -6807,16 +7268,15 @@ pub struct MetadataRecomputeContextTokensRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct MetadataRecomputeContextTokensResult { - /// Tokens contributed by user/assistant/tool messages (excludes system/developer prompts). - pub messages_token_count: i64, - /// Tokens contributed by system/developer prompt snapshots. - pub system_token_count: i64, - /// Sum of tokens across chat-context and system-context messages currently held by the session. - pub total_tokens: i64, +pub struct McpResourcesListRequest { + /// Opaque MCP pagination cursor from a prior `nextCursor` value + #[serde(skip_serializing_if = "Option::is_none")] + pub cursor: Option, + /// Name of the MCP server whose resources to enumerate + pub server_name: String, } -/// Updated working directory and git context. Emitted as the new payload of `session.context_changed`. +/// One page of resources advertised by the named MCP server. /// ///
/// @@ -6826,33 +7286,15 @@ pub struct MetadataRecomputeContextTokensResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionWorkingDirectoryContext { - /// Merge-base commit SHA (fork point from the remote default branch) - #[serde(skip_serializing_if = "Option::is_none")] - pub base_commit: Option, - /// Current git branch name - #[serde(skip_serializing_if = "Option::is_none")] - pub branch: Option, - /// Current working directory path - pub cwd: String, - /// Root directory of the git repository, resolved via git rev-parse - #[serde(skip_serializing_if = "Option::is_none")] - pub git_root: Option, - /// Head commit of the current git branch - #[serde(skip_serializing_if = "Option::is_none")] - pub head_commit: Option, - /// Hosting platform type of the repository - #[serde(skip_serializing_if = "Option::is_none")] - pub host_type: Option, - /// Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) - #[serde(skip_serializing_if = "Option::is_none")] - pub repository: Option, - /// Raw host string from the git remote URL (e.g. "github.com", "dev.azure.com") +pub struct McpResourcesListResult { + /// Opaque cursor for the next page, if the server has more resources #[serde(skip_serializing_if = "Option::is_none")] - pub repository_host: Option, + pub next_cursor: Option, + /// Resources advertised by the server (proxied MCP `resources/list`) + pub resources: Vec, } -/// Updated working-directory/git context to record on the session. +/// MCP server whose resource templates to enumerate. /// ///
/// @@ -6862,12 +7304,15 @@ pub struct SessionWorkingDirectoryContext { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct MetadataRecordContextChangeRequest { - /// Updated working directory and git context. Emitted as the new payload of `session.context_changed`. - pub context: SessionWorkingDirectoryContext, +pub struct McpResourcesListTemplatesRequest { + /// Opaque MCP pagination cursor from a prior `nextCursor` value + #[serde(skip_serializing_if = "Option::is_none")] + pub cursor: Option, + /// Name of the MCP server whose resource templates to enumerate + pub server_name: String, } -/// Notify the session that its working directory context has changed. Emits a `session.context_changed` event so consumers (telemetry, OTel tracker, ACP, the timeline UI) can react. Use this when the host has detected a cwd/branch/repo change outside the session's normal lifecycle (e.g., after a shell command in interactive mode). For a local session, a report whose `cwd` diverges from the session's current working directory is ignored (the call still succeeds but records nothing and emits no event); move a local session's working directory via `metadata.setWorkingDirectory` instead. +/// An MCP resource template descriptor (spec `ResourceTemplate`): an RFC 6570 URI template, name, and optional title, description, MIME type, icons, annotations, and metadata. Server-provided fields outside the standard descriptor shape are exposed under `additionalProperties`. /// ///
/// @@ -6877,11 +7322,37 @@ pub struct MetadataRecordContextChangeRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct MetadataRecordContextChangeResult {} - -/// Absolute path to set as the session's new working directory. For local sessions the path must be absolute and exist on disk: it is validated before any session state changes, and a failing validation rejects the call with nothing mutated, persisted, or emitted. Remote sessions record the path as-is. -/// -///
+pub struct McpResourceTemplate { + /// Resource-template-level metadata + #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] + pub meta: Option>, + /// Server-provided non-standard descriptor fields preserved from the MCP response + #[serde(skip_serializing_if = "Option::is_none")] + pub additional_properties: Option>, + /// Model/client annotations associated with this template + #[serde(skip_serializing_if = "Option::is_none")] + pub annotations: Option, + /// Optional description of what this template is for + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + /// Icons associated with resources matching this template + #[serde(skip_serializing_if = "Option::is_none")] + pub icons: Option>, + /// MIME type for resources matching this template, if uniform + #[serde(skip_serializing_if = "Option::is_none")] + pub mime_type: Option, + /// The programmatic name of the resource template + pub name: String, + /// Optional human-readable display title + #[serde(skip_serializing_if = "Option::is_none")] + pub title: Option, + /// An RFC 6570 URI template for constructing resource URIs + pub uri_template: String, +} + +/// One page of resource templates advertised by the named MCP server. +/// +///
/// /// **Experimental.** This type is part of an experimental wire-protocol surface /// and may change or be removed in future SDK or CLI releases. @@ -6889,12 +7360,15 @@ pub struct MetadataRecordContextChangeResult {} ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct MetadataSetWorkingDirectoryRequest { - /// Absolute path to set as the session's working directory. The runtime updates the session's recorded cwd so subsequent operations (shell tools, file lookups, telemetry) anchor to it. - pub working_directory: String, +pub struct McpResourcesListTemplatesResult { + /// Opaque cursor for the next page, if the server has more resource templates + #[serde(skip_serializing_if = "Option::is_none")] + pub next_cursor: Option, + /// Resource templates advertised by the server (proxied MCP `resources/templates/list`) + pub resource_templates: Vec, } -/// Update the session's working directory. Used by the host when the user explicitly changes cwd (e.g., the `/cd` slash command). The host is responsible for any related side-effects (file index, etc.); it does NOT change the process working directory (a session's cwd is per-session, not process-global). For local sessions the runtime validates the target first (an absolute path that exists on disk) and re-bases the permission primary directory; a rejected validation fails the call before anything is mutated, persisted, or emitted. Location-scoped permission rules are then re-keyed to the new directory (best-effort). Remote sessions only record the path. +/// MCP server and resource URI to fetch. /// ///
/// @@ -6904,12 +7378,14 @@ pub struct MetadataSetWorkingDirectoryRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct MetadataSetWorkingDirectoryResult { - /// Working directory after the update - pub working_directory: String, +pub struct McpResourcesReadRequest { + /// Name of the MCP server hosting the resource + pub server_name: String, + /// Resource URI + pub uri: String, } -/// The repository the remote session targets. +/// Resource contents returned by the MCP server. /// ///
/// @@ -6919,16 +7395,12 @@ pub struct MetadataSetWorkingDirectoryResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct MetadataSnapshotRemoteMetadataRepository { - /// The branch the remote session is operating on. - pub branch: String, - /// The GitHub repository name (without owner). - pub name: String, - /// The GitHub owner (user or organization) of the target repository. - pub owner: String, +pub struct McpResourcesReadResult { + /// Resource contents returned by the server + pub contents: Vec, } -/// Remote-session-specific metadata. Populated only when `isRemote` is true. Fields are immutable for the lifetime of the session. +/// Server name and optional replacement configuration for an individual MCP server restart. Omit `config` for a config-free restart-by-name of an already-configured server. /// ///
/// @@ -6938,21 +7410,15 @@ pub struct MetadataSnapshotRemoteMetadataRepository { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct MetadataSnapshotRemoteMetadata { - /// The pull request number the remote session is associated with, if any. - #[serde(skip_serializing_if = "Option::is_none")] - pub pull_request_number: Option, - /// The repository the remote session targets. - pub repository: MetadataSnapshotRemoteMetadataRepository, - /// The original resource identifier (task ID or PR node ID), preserved across event-replay reconstructions. Falls back to `sessionId` when absent. - #[serde(skip_serializing_if = "Option::is_none")] - pub resource_id: Option, - /// Whether the remote task originated from Copilot Coding Agent (cca) or a CLI `--remote` invocation. +pub struct McpRestartServerRequest { + /// Replacement MCP server configuration (stdio process or remote HTTP/SSE). Omit to restart the server with its already-registered configuration (config-free restart-by-name). #[serde(skip_serializing_if = "Option::is_none")] - pub task_type: Option, + pub config: Option, + /// Name of the MCP server to restart + pub server_name: String, } -/// Active server-driven promotion for a model, including its discount and expiry. +/// Outcome of an MCP sampling execution: success result, failure error, or cancellation. /// ///
/// @@ -6962,21 +7428,18 @@ pub struct MetadataSnapshotRemoteMetadata { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ModelBillingPromo { - /// Percentage discount (0-100) applied while the promotion is active. May be fractional. - #[serde(skip_serializing_if = "Option::is_none")] - pub discount_percent: Option, - /// UTC ISO 8601 timestamp marking when the promotion ends. Always present: the API only surfaces a promo whose expiry parses and is in the future. Consumers should treat a past value as expired. - pub ends_at: String, - /// Stable identifier for the promotion campaign. +pub struct McpSamplingExecutionResult { + /// Outcome of the sampling inference. 'success' produced a response; 'failure' encountered an error (including agent-side rejection by content filter or criteria); 'cancelled' the caller cancelled this execution via cancelSamplingExecution. + pub action: McpSamplingExecutionAction, + /// Error description, present when action='failure'. #[serde(skip_serializing_if = "Option::is_none")] - pub id: Option, - /// Human-readable promotion message. Does not include the expiry timestamp; consumers may format endsAt and append it. + pub error: Option, + /// MCP CreateMessageResult payload (with optional 'tools' extension), present when action='success'. Treated as opaque at the schema layer; consumers should construct/consume it per the MCP CreateMessageResult shape. #[serde(skip_serializing_if = "Option::is_none")] - pub message: Option, + pub result: Option, } -/// Long context tier pricing (available for models with extended context windows) +/// MCP server status entry, including config source/plugin source and any connection error. /// ///
/// @@ -6986,35 +7449,42 @@ pub struct ModelBillingPromo { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ModelBillingTokenPricesLongContext { - /// Use cacheReadPrice instead. AI Credits cost per billing batch of cached tokens - #[doc(hidden)] - #[deprecated] - #[serde(skip_serializing_if = "Option::is_none")] - pub cache_price: Option, - /// AI Credits cost per billing batch of cached (read) tokens - #[serde(skip_serializing_if = "Option::is_none")] - pub cache_read_price: Option, - /// AI Credits cost per billing batch of cache-write (cache creation) tokens. +pub struct McpServer { + /// Error message if the server failed to connect #[serde(skip_serializing_if = "Option::is_none")] - pub cache_write_price: Option, - /// Use maxPromptTokens instead. Prompt token budget for the long context tier. The total context window is this value plus the model's max_output_tokens. - #[doc(hidden)] - #[deprecated] + pub error: Option, + /// Server name (config key) + pub name: String, + /// Configuration source: user, workspace, plugin, or builtin #[serde(skip_serializing_if = "Option::is_none")] - pub context_max: Option, - /// AI Credits cost per billing batch of input tokens + pub source: Option, + /// Plugin name that provided this server, when source is plugin. #[serde(skip_serializing_if = "Option::is_none")] - pub input_price: Option, - /// Prompt token budget for the long context tier. The total context window is this value plus the model's max_output_tokens. + pub source_plugin: Option, + /// Plugin version that provided this server, when source is plugin. #[serde(skip_serializing_if = "Option::is_none")] - pub max_prompt_tokens: Option, - /// AI Credits cost per billing batch of output tokens + pub source_plugin_version: Option, + /// Connection status: connected, failed, needs-auth, pending, disabled, stopped, or not_configured + pub status: McpServerStatus, +} + +/// Authentication settings with optional redirect port configuration. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct McpServerAuthConfigRedirectPort { + /// Fixed port for the OAuth redirect callback server. #[serde(skip_serializing_if = "Option::is_none")] - pub output_price: Option, + pub redirect_port: Option, } -/// Token-level pricing information for this model +/// Remote MCP server configuration accessed over HTTP or SSE. /// ///
/// @@ -7024,41 +7494,51 @@ pub struct ModelBillingTokenPricesLongContext { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ModelBillingTokenPrices { - /// Number of tokens per standard billing batch +pub struct McpServerConfigHttp { + /// Set to `true` to use defaults, or provide an object with additional auth or OIDC settings. #[serde(skip_serializing_if = "Option::is_none")] - pub batch_size: Option, - /// Use cacheReadPrice instead. AI Credits cost per billing batch of cached tokens - #[doc(hidden)] - #[deprecated] + pub auth: Option, + /// Controls if tools provided by this server can be loaded on demand via tool search (auto) or always included in the initial tool list (never) #[serde(skip_serializing_if = "Option::is_none")] - pub cache_price: Option, - /// AI Credits cost per billing batch of cached (read) tokens + pub defer_tools: Option, + /// Set to true to disable persisted MCP tool snapshots for this server. Live tool discovery is unaffected. #[serde(skip_serializing_if = "Option::is_none")] - pub cache_read_price: Option, - /// AI Credits cost per billing batch of cache-write (cache creation) tokens. + pub disable_tool_cache: Option, + /// Content filtering mode to apply to all tools, or a map of tool name to content filtering mode. #[serde(skip_serializing_if = "Option::is_none")] - pub cache_write_price: Option, - /// Use maxPromptTokens instead. Prompt token budget for the default tier. The total context window is this value plus the model's max_output_tokens. - #[doc(hidden)] - #[deprecated] + pub filter_mapping: Option, + /// HTTP headers to include in requests to the remote MCP server. #[serde(skip_serializing_if = "Option::is_none")] - pub context_max: Option, - /// AI Credits cost per billing batch of input tokens + pub headers: Option>, + /// Whether this server is a built-in fallback used when the user has not configured their own server. #[serde(skip_serializing_if = "Option::is_none")] - pub input_price: Option, - /// Long context tier pricing (available for models with extended context windows) + pub is_default_server: Option, + /// OAuth client ID for a pre-registered remote MCP OAuth client. #[serde(skip_serializing_if = "Option::is_none")] - pub long_context: Option, - /// Prompt token budget for the default tier. The total context window is this value plus the model's max_output_tokens. + pub oauth_client_id: Option, + /// OAuth grant type to use when authenticating to the remote MCP server. #[serde(skip_serializing_if = "Option::is_none")] - pub max_prompt_tokens: Option, - /// AI Credits cost per billing batch of output tokens + pub oauth_grant_type: Option, + /// Whether the configured OAuth client is public and does not require a client secret. #[serde(skip_serializing_if = "Option::is_none")] - pub output_price: Option, + pub oauth_public_client: Option, + /// Set to `true` to use defaults, or provide an object with additional auth or OIDC settings. + #[serde(skip_serializing_if = "Option::is_none")] + pub oidc: Option, + /// Timeout in milliseconds for tool calls to this server. + #[serde(skip_serializing_if = "Option::is_none")] + pub timeout: Option, + /// Tools to include. Defaults to all tools if not specified. + #[serde(skip_serializing_if = "Option::is_none")] + pub tools: Option>, + /// Remote transport type. Defaults to "http" when omitted. + #[serde(skip_serializing_if = "Option::is_none")] + pub r#type: Option, + /// URL of the remote MCP server endpoint. + pub url: String, } -/// Billing information +/// Stdio MCP server configuration launched as a child process. /// ///
/// @@ -7068,22 +7548,45 @@ pub struct ModelBillingTokenPrices { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ModelBilling { - /// Whole-number percentage discount (0-100) applied to usage billed through this model. Populated for the synthetic `auto` model, where requests routed by auto-mode are billed at a reduced rate; absent for concrete models. +pub struct McpServerConfigStdio { + /// Command-line arguments passed to the Stdio MCP server process. #[serde(skip_serializing_if = "Option::is_none")] - pub discount_percent: Option, - /// Billing cost multiplier relative to the base rate + pub args: Option>, + /// Set to `true` to use defaults, or provide an object with additional auth or OIDC settings. #[serde(skip_serializing_if = "Option::is_none")] - pub multiplier: Option, - /// Active server-driven promotion for this model, if any. Present when the model is being promoted with a time-boxed discount. + pub auth: Option, + /// Executable command used to start the Stdio MCP server process. + pub command: String, + /// Working directory for the Stdio MCP server process. #[serde(skip_serializing_if = "Option::is_none")] - pub promo: Option, - /// Token-level pricing information for this model + pub cwd: Option, + /// Controls if tools provided by this server can be loaded on demand via tool search (auto) or always included in the initial tool list (never) #[serde(skip_serializing_if = "Option::is_none")] - pub token_prices: Option, + pub defer_tools: Option, + /// Set to true to disable persisted MCP tool snapshots for this server. Live tool discovery is unaffected. + #[serde(skip_serializing_if = "Option::is_none")] + pub disable_tool_cache: Option, + /// Environment variables to pass to the Stdio MCP server process. + #[serde(skip_serializing_if = "Option::is_none")] + pub env: Option>, + /// Content filtering mode to apply to all tools, or a map of tool name to content filtering mode. + #[serde(skip_serializing_if = "Option::is_none")] + pub filter_mapping: Option, + /// Whether this server is a built-in fallback used when the user has not configured their own server. + #[serde(skip_serializing_if = "Option::is_none")] + pub is_default_server: Option, + /// Set to `true` to use defaults, or provide an object with additional auth or OIDC settings. + #[serde(skip_serializing_if = "Option::is_none")] + pub oidc: Option, + /// Timeout in milliseconds for tool calls to this server. + #[serde(skip_serializing_if = "Option::is_none")] + pub timeout: Option, + /// Tools to include. Defaults to all tools if not specified. + #[serde(skip_serializing_if = "Option::is_none")] + pub tools: Option>, } -/// Vision-specific limits +/// MCP servers configured for the session, with their connection status and host-level state. /// ///
/// @@ -7093,19 +7596,15 @@ pub struct ModelBilling { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ModelCapabilitiesLimitsVision { - /// Maximum image size in bytes - #[serde(rename = "max_prompt_image_size")] - pub max_prompt_image_size: i64, - /// Maximum number of images per prompt - #[serde(rename = "max_prompt_images")] - pub max_prompt_images: i64, - /// MIME types the model accepts - #[serde(rename = "supported_media_types")] - pub supported_media_types: Vec, -} - -/// Token limits for prompts, outputs, and context window +pub struct McpServerList { + /// Host-level state, omitted when no MCP host is initialized. + #[serde(skip_serializing_if = "Option::is_none")] + pub host: Option, + /// Configured MCP servers + pub servers: Vec, +} + +/// Mode controlling how MCP server env values are resolved (`direct` or `indirect`). /// ///
/// @@ -7115,25 +7614,12 @@ pub struct ModelCapabilitiesLimitsVision { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ModelCapabilitiesLimits { - /// Maximum total context window size in tokens - #[serde( - rename = "max_context_window_tokens", - skip_serializing_if = "Option::is_none" - )] - pub max_context_window_tokens: Option, - /// Maximum number of output/completion tokens - #[serde(rename = "max_output_tokens", skip_serializing_if = "Option::is_none")] - pub max_output_tokens: Option, - /// Maximum number of prompt/input tokens - #[serde(rename = "max_prompt_tokens", skip_serializing_if = "Option::is_none")] - pub max_prompt_tokens: Option, - /// Vision-specific limits - #[serde(skip_serializing_if = "Option::is_none")] - pub vision: Option, +pub struct McpSetEnvValueModeParams { + /// How environment-variable values supplied to MCP servers are resolved. "direct" passes literal string values; "indirect" treats values as references (e.g. names of environment variables on the host) that the runtime resolves before launch. Defaults to the runtime's startup mode; clients that intentionally launch MCP servers with literal values (e.g. CLI prompt mode and ACP) set this to "direct". + pub mode: McpSetEnvValueModeDetails, } -/// Feature flags indicating what the model supports +/// Env-value mode recorded on the session after the update. /// ///
/// @@ -7143,19 +7629,12 @@ pub struct ModelCapabilitiesLimits { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ModelCapabilitiesSupports { - /// Resolved Anthropic adaptive-thinking capability — unsupported / optional / required. 'required' models reject thinking.type='enabled' with HTTP 400 (e.g. opus-4.7/4.8). - #[serde(rename = "adaptive_thinking", skip_serializing_if = "Option::is_none")] - pub adaptive_thinking: Option, - /// Whether this model supports reasoning effort configuration - #[serde(skip_serializing_if = "Option::is_none")] - pub reasoning_effort: Option, - /// Whether this model supports vision/image input - #[serde(skip_serializing_if = "Option::is_none")] - pub vision: Option, +pub struct McpSetEnvValueModeResult { + /// Mode recorded on the session after the update + pub mode: McpSetEnvValueModeDetails, } -/// Model capabilities and limits +/// Server name and optional configuration for an individual MCP server start. Omit `config` for a config-free start-by-name of an already-configured server. /// ///
/// @@ -7165,16 +7644,15 @@ pub struct ModelCapabilitiesSupports { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ModelCapabilities { - /// Token limits for prompts, outputs, and context window - #[serde(skip_serializing_if = "Option::is_none")] - pub limits: Option, - /// Feature flags indicating what the model supports +pub struct McpStartServerRequest { + /// MCP server configuration (stdio process or remote HTTP/SSE). Omit to start the server with its already-registered configuration (config-free start-by-name). #[serde(skip_serializing_if = "Option::is_none")] - pub supports: Option, + pub config: Option, + /// Name of the MCP server to start + pub server_name: String, } -/// Policy state (if applicable) +/// MCP server startup filtering result. /// ///
/// @@ -7184,15 +7662,15 @@ pub struct ModelCapabilities { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ModelPolicy { - /// Current policy state for this model - pub state: ModelPolicyState, - /// Usage terms or conditions for this model +pub struct McpStartServersResult { + /// Non-default servers allowed by policy #[serde(skip_serializing_if = "Option::is_none")] - pub terms: Option, + pub allowed_servers: Option>, + /// Servers filtered out before startup + pub filtered_servers: Vec, } -/// Copilot model metadata, including identifier, display name, capabilities, policy, billing, reasoning efforts, and picker categories. +/// Server name for an individual MCP server stop. /// ///
/// @@ -7202,34 +7680,12 @@ pub struct ModelPolicy { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct Model { - /// Billing information - #[serde(skip_serializing_if = "Option::is_none")] - pub billing: Option, - /// Model capabilities and limits - pub capabilities: ModelCapabilities, - /// Default reasoning effort level (only present if model supports reasoning effort) - #[serde(skip_serializing_if = "Option::is_none")] - pub default_reasoning_effort: Option, - /// Model identifier (e.g., "claude-sonnet-4.5") - pub id: String, - /// Model capability category for grouping in the model picker - #[serde(skip_serializing_if = "Option::is_none")] - pub model_picker_category: Option, - /// Relative cost tier for token-based billing users - #[serde(skip_serializing_if = "Option::is_none")] - pub model_picker_price_category: Option, - /// Display name - pub name: String, - /// Policy state (if applicable) - #[serde(skip_serializing_if = "Option::is_none")] - pub policy: Option, - /// Supported reasoning effort levels (only present if model supports reasoning effort) - #[serde(skip_serializing_if = "Option::is_none")] - pub supported_reasoning_efforts: Option>, +pub struct McpStopServerRequest { + /// Name of the MCP server to stop + pub server_name: String, } -/// Vision-specific limits +/// Server name identifying the external client to remove. /// ///
/// @@ -7239,25 +7695,12 @@ pub struct Model { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ModelCapabilitiesOverrideLimitsVision { - /// Maximum image size in bytes - #[serde( - rename = "max_prompt_image_size", - skip_serializing_if = "Option::is_none" - )] - pub max_prompt_image_size: Option, - /// Maximum number of images per prompt - #[serde(rename = "max_prompt_images", skip_serializing_if = "Option::is_none")] - pub max_prompt_images: Option, - /// MIME types the model accepts - #[serde( - rename = "supported_media_types", - skip_serializing_if = "Option::is_none" - )] - pub supported_media_types: Option>, +pub(crate) struct McpUnregisterExternalClientRequest { + /// Server name of the external client to unregister + pub server_name: String, } -/// Token limits for prompts, outputs, and context window +/// Memory configuration for this session. /// ///
/// @@ -7267,81 +7710,85 @@ pub struct ModelCapabilitiesOverrideLimitsVision { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ModelCapabilitiesOverrideLimits { - /// Maximum total context window size in tokens - #[serde( - rename = "max_context_window_tokens", - skip_serializing_if = "Option::is_none" - )] - pub max_context_window_tokens: Option, - /// Maximum number of output/completion tokens - #[serde(rename = "max_output_tokens", skip_serializing_if = "Option::is_none")] - pub max_output_tokens: Option, - /// Maximum number of prompt/input tokens - #[serde(rename = "max_prompt_tokens", skip_serializing_if = "Option::is_none")] - pub max_prompt_tokens: Option, - /// Vision-specific limits - #[serde(skip_serializing_if = "Option::is_none")] - pub vision: Option, +pub struct MemoryConfiguration { + /// Whether memory is enabled for the session. + pub enabled: bool, } -/// Feature flags indicating what the model supports -/// -///
-/// -/// **Experimental.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
+/// The six normalized `/context` header buckets, computed from the same tokenization as `entries` so the two never disagree. Convenience rollups: `freeSpace` and `buffer` describe window capacity rather than occupied context, so the values do not sum to `totalTokens`. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ModelCapabilitiesOverrideSupports { - /// Resolved Anthropic adaptive-thinking capability — unsupported / optional / required. 'required' models reject thinking.type='enabled' with HTTP 400 (e.g. opus-4.7/4.8). - #[serde(rename = "adaptive_thinking", skip_serializing_if = "Option::is_none")] - pub adaptive_thinking: Option, - /// Whether this model supports reasoning effort configuration - #[serde(skip_serializing_if = "Option::is_none")] - pub reasoning_effort: Option, - /// Whether this model supports vision/image input - #[serde(skip_serializing_if = "Option::is_none")] - pub vision: Option, +pub struct MetadataContextAttributionResultContextAttributionCategories { + /// Output reserve plus post-blocking-threshold buffer. + pub buffer: i64, + /// Custom-instructions tokens (0 when none are configured). + pub custom_instructions: i64, + /// Remaining unused window capacity (clamped at 0). + pub free_space: i64, + /// MCP tool-definition tokens. + pub mcp_tools: i64, + /// Conversation (user/assistant/tool) message tokens. + pub messages: i64, + /// System prompt tokens, excluding custom instructions. + pub system_prompt: i64, + /// Non-MCP tool-definition tokens. + pub system_tools: i64, } -/// Optional capability overrides (vision, tool_calls, reasoning, etc.). -/// -///
-/// -/// **Experimental.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
+/// Successful compaction history for the session. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ModelCapabilitiesOverride { - /// Token limits for prompts, outputs, and context window +pub struct MetadataContextAttributionResultContextAttributionCompactions { + /// Number of successful compactions in this session. + pub count: i64, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MetadataContextAttributionResultContextAttributionEntriesItem { + /// Supplementary per-entry metadata (e.g. `messageCount`, `role`, `evictable`, `pluginSource`). Values are stringified; parse as needed and ignore unrecognized keys. #[serde(skip_serializing_if = "Option::is_none")] - pub limits: Option, - /// Feature flags indicating what the model supports + pub attributes: Option>, + /// Identifier for this entry, formed by joining its `kind` and source name (e.g. `tool:bash`, `skill:tmux`, `toolDefinition:bash`); unique within the snapshot. Use it to match the same entry across snapshots, to correlate with other APIs (skill/agent/MCP registries), and as the `parentId` target for nesting. Distinct from the human-facing `label`. + pub id: String, + /// Source category for this entry. Not a closed set — tolerate unknown values. Known values today: `skill`, `subagent`, `mcpServer`, `tool`, `system`, `toolDefinition`, `plugin`. + pub kind: String, + /// Human-readable display label, e.g. `bash` or `skill: tmux`. Presentation-only; may be localized/reformatted without notice — do not key off it. + pub label: String, + /// Optional `id` of the parent entry: e.g. a `plugin` entry parenting its `skill`/`mcpServer` entries, or the `system` entry parenting `toolDefinition` entries. Omitted for top-level entries. #[serde(skip_serializing_if = "Option::is_none")] - pub supports: Option, + pub parent_id: Option, + /// Token count currently in context attributable to this entry. + pub tokens: i64, } -/// List of Copilot models available to the resolved user, including capabilities and billing metadata. -/// -///
-/// -/// **Experimental.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
+/// Per-source token attribution snapshot for the current context window. The heaviest individual messages are available separately via `metadata.getContextHeaviestMessages`. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ModelList { - /// List of available models with full metadata - pub models: Vec, +pub struct MetadataContextAttributionResultContextAttribution { + /// Output reserve plus the tokens past the buffer-exhaustion blocking threshold. Mirrors `SessionContextInfo.bufferTokens`. + pub buffer_tokens: i64, + /// The six normalized `/context` header buckets, computed from the same tokenization as `entries` so the two never disagree. Convenience rollups: `freeSpace` and `buffer` describe window capacity rather than occupied context, so the values do not sum to `totalTokens`. + pub categories: MetadataContextAttributionResultContextAttributionCategories, + /// Successful compaction history for the session. + pub compactions: MetadataContextAttributionResultContextAttributionCompactions, + /// Token count at which background compaction starts. Mirrors `SessionContextInfo.compactionThreshold`. + pub compaction_threshold: i64, + /// Flat list of per-source attribution entries. Group by `kind` and render unrecognized kinds generically. Nesting and rollups are expressed via `parentId`. + pub entries: Vec, + /// Prompt limit plus the model's output reserve: the full context window `categories.freeSpace` and `categories.buffer` are measured against. Mirrors `SessionContextInfo.limit`. + pub limit: i64, + /// The concrete model id the entire breakdown was tokenized against (feeds the per-model token multiplier). Under `Auto` (Free/Student) this is the resolved model, not the literal `auto` sentinel, so totals are not undercounted. A single-model approximation of a potentially multi-model Auto session. + pub model_id: String, + /// How `modelId` was chosen. Not a closed set — tolerate unknown values. Known values today: `autoResolved` (the model Auto resolved to), `selected` (the user's explicitly selected model), `default` (a fallback before any model is known). + pub model_source: String, + /// Maximum prompt tokens the resolved model accepts — the denominator for a `##k/###k` context-usage display. Mirrors `SessionContextInfo.promptTokenLimit`. + pub prompt_token_limit: i64, + /// Total token count of the current context window the entries are measured against (system message + conversation messages + tool definitions — the same total reported by /context). Divide an entry's `tokens` by this to derive its share. + pub total_tokens: i64, } -/// Optional listing options. +/// Per-source attribution breakdown for the session's current context window, or null if uninitialized. /// ///
/// @@ -7351,13 +7798,12 @@ pub struct ModelList { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ModelListRequest { - /// If true, bypasses the per-session model list cache and re-fetches from CAPI. - #[serde(skip_serializing_if = "Option::is_none")] - pub skip_cache: Option, +pub struct MetadataContextAttributionResult { + /// Per-source context-window attribution, or null if the session has not yet been initialized (no system prompt or tool metadata cached). + pub context_attribution: Option, } -/// Reasoning effort level to apply to the currently selected model. +/// Parameters for the heaviest-messages query. /// ///
/// @@ -7367,12 +7813,13 @@ pub struct ModelListRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ModelSetReasoningEffortRequest { - /// Reasoning effort level to apply to the currently selected model. The host is responsible for validating the value against the model's supported levels before calling. - pub reasoning_effort: String, +pub struct MetadataContextHeaviestMessagesRequest { + /// Maximum number of messages to return, most-expensive first. Omit for the server default. + #[serde(skip_serializing_if = "Option::is_none")] + pub limit: Option, } -/// Update the session's reasoning effort without changing the selected model. Use `switchTo` instead when you also need to change the model. The runtime stores the effort on the session and applies it to subsequent turns. +/// The heaviest individual messages in the session's context window, most-expensive first. /// ///
/// @@ -7382,12 +7829,14 @@ pub struct ModelSetReasoningEffortRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ModelSetReasoningEffortResult { - /// Reasoning effort level recorded on the session after the update - pub reasoning_effort: String, +pub struct MetadataContextHeaviestMessagesResult { + /// Heaviest messages, most-expensive first. + pub messages: Vec, + /// Total token count of the current context window, so callers can compute each message's share without a second call. + pub total_tokens: i64, } -/// Optional GitHub token used to list models for a specific user instead of the global auth context. +/// Model identifier and token limits used to compute the context-info breakdown. /// ///
/// @@ -7397,15 +7846,45 @@ pub struct ModelSetReasoningEffortResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ModelsListRequest { - /// GitHub token for per-user model listing. When provided, resolves this token to determine the user's Copilot plan and available models instead of using the global auth. +pub struct MetadataContextInfoRequest { + /// Maximum output tokens allowed by the target model. Pass 0 if unknown. + pub output_token_limit: i64, + /// Maximum prompt tokens allowed by the target model. Pass 0 to use the runtime default. + pub prompt_token_limit: i64, + /// Model identifier used for tokenization. Omit to use the session default. Used both for token counting and to compute display values. #[serde(skip_serializing_if = "Option::is_none")] - pub git_hub_token: Option, + pub selected_model: Option, } -/// Target model identifier and optional reasoning effort, summary, capability overrides, and context tier. -/// -///
+/// Token-usage breakdown for the session's current context window +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MetadataContextInfoResultContextInfo { + /// Output reserve plus tokens after the buffer-exhaustion blocking threshold (default 95%) + pub buffer_tokens: i64, + /// Token count at which background compaction starts (configurable percentage of promptTokenLimit) + pub compaction_threshold: i64, + /// Tokens consumed by user/assistant/tool messages + pub conversation_tokens: i64, + /// Prompt token limit plus the model's full output token limit. + pub limit: i64, + /// Tokens consumed by MCP tool definitions (subset of toolDefinitionsTokens, excludes deferred tools) + pub mcp_tools_tokens: i64, + /// The model used for token counting + pub model_name: String, + /// Maximum prompt tokens allowed by the model (or DEFAULT_TOKEN_LIMIT if unspecified) + pub prompt_token_limit: i64, + /// Tokens consumed by the system prompt + pub system_tokens: i64, + /// Tokens consumed by tool definitions sent to the model (excludes deferred tools) + pub tool_definitions_tokens: i64, + /// Sum of system, conversation and tool-definition tokens + pub total_tokens: i64, +} + +/// Token breakdown for the session's current context window, or null if uninitialized. +/// +///
/// /// **Experimental.** This type is part of an experimental wire-protocol surface /// and may change or be removed in future SDK or CLI releases. @@ -7413,27 +7892,12 @@ pub struct ModelsListRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ModelSwitchToRequest { - /// Explicit context tier for the selected model. `"default"` / `"long_context"` apply the requested tier; omit this field to use normal model behavior with no explicit tier. - #[serde(skip_serializing_if = "Option::is_none")] - pub context_tier: Option, - /// Override individual model capabilities resolved by the runtime - #[serde(skip_serializing_if = "Option::is_none")] - pub model_capabilities: Option, - /// Model selection id to switch to, as returned by `list`. A bare id (e.g. `claude-sonnet-4.6`) names a Copilot (CAPI) model; a provider-qualified id (`provider/id`, e.g. `acme/claude-sonnet`) targets a registry BYOK model. - pub model_id: String, - /// Reasoning effort level to use for the model. "none" disables reasoning. - #[serde(skip_serializing_if = "Option::is_none")] - pub reasoning_effort: Option, - /// Reasoning summary mode to request for supported model clients - #[serde(skip_serializing_if = "Option::is_none")] - pub reasoning_summary: Option, - /// Output verbosity level to request for supported models - #[serde(skip_serializing_if = "Option::is_none")] - pub verbosity: Option, +pub struct MetadataContextInfoResult { + /// Token breakdown for the current context window, or null if the session has not yet been initialized (no system prompt or tool metadata cached). + pub context_info: Option, } -/// The model identifier active on the session after the switch. +/// Indicates whether the local session is currently processing a turn or background continuation. /// ///
/// @@ -7443,13 +7907,12 @@ pub struct ModelSwitchToRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ModelSwitchToResult { - /// Currently active model identifier after the switch - #[serde(skip_serializing_if = "Option::is_none")] - pub model_id: Option, +pub struct MetadataIsProcessingResult { + /// Whether the session is currently processing user/agent messages. False for non-local sessions (which don't run a local agentic loop). Reflects an in-flight turn or background continuation. + pub processing: bool, } -/// Agent interaction mode to apply to the session. +/// Model identifier to use when re-tokenizing the session's existing messages. /// ///
/// @@ -7459,12 +7922,12 @@ pub struct ModelSwitchToResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ModeSetRequest { - /// The session mode the agent is operating in - pub mode: SessionMode, +pub struct MetadataRecomputeContextTokensRequest { + /// Model identifier used for tokenization. The runtime token-counts both chat-context and system-context messages against this model. + pub model_id: String, } -/// Azure-specific provider options. +/// Re-tokenize the session's existing messages against `modelId` and return the token totals. Useful for hosts that want an initial estimate of context usage on session resume, before the next agent turn fires `session.context_info_changed` events. Returns zeros for an empty session. /// ///
/// @@ -7474,13 +7937,16 @@ pub struct ModeSetRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ProviderConfigAzure { - /// API version. When set, uses the versioned deployment route. When omitted, uses the GA versionless v1 route. - #[serde(skip_serializing_if = "Option::is_none")] - pub api_version: Option, +pub struct MetadataRecomputeContextTokensResult { + /// Tokens contributed by user/assistant/tool messages (excludes system/developer prompts). + pub messages_token_count: i64, + /// Tokens contributed by system/developer prompt snapshots. + pub system_token_count: i64, + /// Sum of tokens across chat-context and system-context messages currently held by the session. + pub total_tokens: i64, } -/// A named BYOK provider connection (transport + credentials). +/// Updated working directory and git context. Emitted as the new payload of `session.context_changed`. /// ///
/// @@ -7490,38 +7956,33 @@ pub struct ProviderConfigAzure { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct NamedProviderConfig { - /// API key. Optional for local providers like Ollama. - #[serde(skip_serializing_if = "Option::is_none")] - pub api_key: Option, - /// Azure-specific provider options. +pub struct SessionWorkingDirectoryContext { + /// Merge-base commit SHA (fork point from the remote default branch) #[serde(skip_serializing_if = "Option::is_none")] - pub azure: Option, - /// API endpoint URL. - pub base_url: String, - /// Bearer token for authentication. Sets the Authorization header directly. Takes precedence over apiKey when both are set. + pub base_commit: Option, + /// Current git branch name #[serde(skip_serializing_if = "Option::is_none")] - pub bearer_token: Option, - /// When true, the SDK client supplies bearer tokens on demand: the runtime calls the client-session `providerToken.getToken` callback before each request and applies the returned token as an `Authorization: Bearer ` header. This is the bearer/OAuth scheme used by Azure AD / managed-identity tokens and provider OAuth access tokens (including Anthropic's), not a provider-specific API-key header such as Anthropic's `x-api-key`. The token-acquiring function itself stays on the SDK side and is never serialized; only this flag crosses the wire. When set alongside `apiKey`/`bearerToken`, the callback takes precedence: the runtime applies the token returned by `providerToken.getToken` as the `Authorization: Bearer` header for each request and does not send the static credential. + pub branch: Option, + /// Current working directory path + pub cwd: String, + /// Root directory of the git repository, resolved via git rev-parse #[serde(skip_serializing_if = "Option::is_none")] - pub has_bearer_token_provider: Option, - /// Custom HTTP headers to include in all outbound requests to the provider. + pub git_root: Option, + /// Head commit of the current git branch #[serde(skip_serializing_if = "Option::is_none")] - pub headers: Option>, - /// Stable identifier referenced by BYOK model definitions. Must not contain '/'. - pub name: String, - /// Provider transport. Defaults to "http". + pub head_commit: Option, + /// Hosting platform type of the repository #[serde(skip_serializing_if = "Option::is_none")] - pub transport: Option, - /// Provider type. Defaults to "openai" for generic OpenAI-compatible APIs. + pub host_type: Option, + /// Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) #[serde(skip_serializing_if = "Option::is_none")] - pub r#type: Option, - /// Wire API format (openai/azure only). Defaults to "completions". + pub repository: Option, + /// Raw host string from the git remote URL (e.g. "github.com", "dev.azure.com") #[serde(skip_serializing_if = "Option::is_none")] - pub wire_api: Option, + pub repository_host: Option, } -/// The session's friendly name, or null when not yet set. +/// Updated working-directory/git context to record on the session. /// ///
/// @@ -7531,12 +7992,12 @@ pub struct NamedProviderConfig { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct NameGetResult { - /// The session name (user-set or auto-generated), or null if not yet set - pub name: Option, +pub struct MetadataRecordContextChangeRequest { + /// Updated working directory and git context. Emitted as the new payload of `session.context_changed`. + pub context: SessionWorkingDirectoryContext, } -/// Auto-generated session summary to apply as the session's name when no user-set name exists. +/// Notify the session that its working directory context has changed. Emits a `session.context_changed` event so consumers (telemetry, OTel tracker, ACP, the timeline UI) can react. Use this when the host has detected a cwd/branch/repo change outside the session's normal lifecycle (e.g., after a shell command in interactive mode). For a local session, a report whose `cwd` diverges from the session's current working directory is ignored (the call still succeeds but records nothing and emits no event); move a local session's working directory via `metadata.setWorkingDirectory` instead. /// ///
/// @@ -7546,12 +8007,9 @@ pub struct NameGetResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct NameSetAutoRequest { - /// Auto-generated session summary. Empty/whitespace-only values are ignored; values are trimmed before persisting. - pub summary: String, -} +pub struct MetadataRecordContextChangeResult {} -/// Indicates whether the auto-generated summary was applied as the session's name. +/// Absolute path to set as the session's new working directory. For local sessions the path must be absolute and exist on disk: it is validated before any session state changes, and a failing validation rejects the call with nothing mutated, persisted, or emitted. Remote sessions record the path as-is. /// ///
/// @@ -7561,12 +8019,12 @@ pub struct NameSetAutoRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct NameSetAutoResult { - /// Whether the auto-generated summary was persisted. False if the session already has a user-set name, the summary normalized to empty, or the session does not have a workspace. - pub applied: bool, +pub struct MetadataSetWorkingDirectoryRequest { + /// Absolute path to set as the session's working directory. The runtime updates the session's recorded cwd so subsequent operations (shell tools, file lookups, telemetry) anchor to it. + pub working_directory: String, } -/// New friendly name to apply to the session. +/// Update the session's working directory. Used by the host when the user explicitly changes cwd (e.g., the `/cd` slash command). The host is responsible for any related side-effects (file index, etc.); it does NOT change the process working directory (a session's cwd is per-session, not process-global). For local sessions the runtime validates the target first (an absolute path that exists on disk) and re-bases the permission primary directory; a rejected validation fails the call before anything is mutated, persisted, or emitted. Location-scoped permission rules are then re-keyed to the new directory (best-effort). Remote sessions only record the path. /// ///
/// @@ -7576,12 +8034,12 @@ pub struct NameSetAutoResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct NameSetRequest { - /// New session name (1–100 characters, trimmed of leading/trailing whitespace) - pub name: String, +pub struct MetadataSetWorkingDirectoryResult { + /// Working directory after the update + pub working_directory: String, } -/// Source descriptor for a `session.options.update` content-exclusion rule, with source name and type. +/// The repository the remote session targets. /// ///
/// @@ -7591,12 +8049,16 @@ pub struct NameSetRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct OptionsUpdateAdditionalContentExclusionPolicyRuleSource { +pub struct MetadataSnapshotRemoteMetadataRepository { + /// The branch the remote session is operating on. + pub branch: String, + /// The GitHub repository name (without owner). pub name: String, - pub r#type: String, + /// The GitHub owner (user or organization) of the target repository. + pub owner: String, } -/// Single content-exclusion rule supplied to `session.options.update`, with paths, match conditions, and source. +/// Remote-session-specific metadata. Populated only when `isRemote` is true. Fields are immutable for the lifetime of the session. /// ///
/// @@ -7606,17 +8068,21 @@ pub struct OptionsUpdateAdditionalContentExclusionPolicyRuleSource { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct OptionsUpdateAdditionalContentExclusionPolicyRule { +pub struct MetadataSnapshotRemoteMetadata { + /// The pull request number the remote session is associated with, if any. #[serde(skip_serializing_if = "Option::is_none")] - pub if_any_match: Option>, + pub pull_request_number: Option, + /// The repository the remote session targets. + pub repository: MetadataSnapshotRemoteMetadataRepository, + /// The original resource identifier (task ID or PR node ID), preserved across event-replay reconstructions. Falls back to `sessionId` when absent. #[serde(skip_serializing_if = "Option::is_none")] - pub if_none_match: Option>, - pub paths: Vec, - /// Source descriptor for a `session.options.update` content-exclusion rule, with source name and type. - pub source: OptionsUpdateAdditionalContentExclusionPolicyRuleSource, + pub resource_id: Option, + /// Whether the remote task originated from Copilot Coding Agent (cca) or a CLI `--remote` invocation. + #[serde(skip_serializing_if = "Option::is_none")] + pub task_type: Option, } -/// Content-exclusion policy supplied to `session.options.update`, with rules, last-updated data, and scope. +/// Active server-driven promotion for a model, including its discount and optional expiry. /// ///
/// @@ -7626,32 +8092,22 @@ pub struct OptionsUpdateAdditionalContentExclusionPolicyRule { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct OptionsUpdateAdditionalContentExclusionPolicy { - #[serde(rename = "last_updated_at")] - pub last_updated_at: serde_json::Value, - pub rules: Vec, - /// Allowed values for the `OptionsUpdateAdditionalContentExclusionPolicyScope` enumeration. - pub scope: OptionsUpdateAdditionalContentExclusionPolicyScope, -} - -/// Pending permission prompt reconstructed from event history, with request ID and user-facing prompt details. -/// -///
-/// -/// **Experimental.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
-#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct PendingPermissionRequest { - /// The user-facing permission prompt details (commands, write, read, mcp, url, memory, custom-tool, path, hook) - pub request: PermissionPromptRequest, - /// Unique identifier for the pending permission request - pub request_id: RequestId, +pub struct ModelBillingPromo { + /// Percentage discount (0-100) applied while the promotion is active. May be fractional. + #[serde(skip_serializing_if = "Option::is_none")] + pub discount_percent: Option, + /// UTC ISO 8601 timestamp marking when the promotion ends. Optional: an open-ended promotion omits this field. When present, the API only surfaces a promo whose expiry parses and is in the future, so consumers should treat a past value as expired. + #[serde(skip_serializing_if = "Option::is_none")] + pub ends_at: Option, + /// Stable identifier for the promotion campaign. + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + /// Human-readable promotion message. Does not include the expiry timestamp; consumers may format endsAt and append it when present. + #[serde(skip_serializing_if = "Option::is_none")] + pub message: Option, } -/// List of pending permission requests reconstructed from event history. +/// Long context tier pricing (available for models with extended context windows) /// ///
/// @@ -7661,12 +8117,35 @@ pub struct PendingPermissionRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PendingPermissionRequestList { - /// Pending permission prompts reconstructed from the session's event history. Equivalent to the set of `permission.requested` events that have not yet been followed by a matching `permission.completed` event. Used by clients (e.g. the CLI) to hydrate UI for prompts that were emitted before the client attached to the session. - pub items: Vec, +pub struct ModelBillingTokenPricesLongContext { + /// Use cacheReadPrice instead. AI Credits cost per billing batch of cached tokens + #[doc(hidden)] + #[deprecated] + #[serde(skip_serializing_if = "Option::is_none")] + pub cache_price: Option, + /// AI Credits cost per billing batch of cached (read) tokens + #[serde(skip_serializing_if = "Option::is_none")] + pub cache_read_price: Option, + /// AI Credits cost per billing batch of cache-write (cache creation) tokens. + #[serde(skip_serializing_if = "Option::is_none")] + pub cache_write_price: Option, + /// Use maxPromptTokens instead. Prompt token budget for the long context tier. The total context window is this value plus the model's max_output_tokens. + #[doc(hidden)] + #[deprecated] + #[serde(skip_serializing_if = "Option::is_none")] + pub context_max: Option, + /// AI Credits cost per billing batch of input tokens + #[serde(skip_serializing_if = "Option::is_none")] + pub input_price: Option, + /// Prompt token budget for the long context tier. The total context window is this value plus the model's max_output_tokens. + #[serde(skip_serializing_if = "Option::is_none")] + pub max_prompt_tokens: Option, + /// AI Credits cost per billing batch of output tokens + #[serde(skip_serializing_if = "Option::is_none")] + pub output_price: Option, } -/// Permission-decision request variant to approve only the current permission request. +/// Token-level pricing information for this model /// ///
/// @@ -7676,29 +8155,41 @@ pub struct PendingPermissionRequestList { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionApproveOnce { - /// Approve this single request only - pub kind: PermissionDecisionApproveOnceKind, -} - -/// Session-scoped approval details for specific command identifiers. -/// -///
-/// -/// **Experimental.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct PermissionDecisionApproveForSessionApprovalCommands { - /// Command identifiers covered by this approval. - pub command_identifiers: Vec, - /// Approval scoped to specific command identifiers. - pub kind: PermissionDecisionApproveForSessionApprovalCommandsKind, +pub struct ModelBillingTokenPrices { + /// Number of tokens per standard billing batch + #[serde(skip_serializing_if = "Option::is_none")] + pub batch_size: Option, + /// Use cacheReadPrice instead. AI Credits cost per billing batch of cached tokens + #[doc(hidden)] + #[deprecated] + #[serde(skip_serializing_if = "Option::is_none")] + pub cache_price: Option, + /// AI Credits cost per billing batch of cached (read) tokens + #[serde(skip_serializing_if = "Option::is_none")] + pub cache_read_price: Option, + /// AI Credits cost per billing batch of cache-write (cache creation) tokens. + #[serde(skip_serializing_if = "Option::is_none")] + pub cache_write_price: Option, + /// Use maxPromptTokens instead. Prompt token budget for the default tier. The total context window is this value plus the model's max_output_tokens. + #[doc(hidden)] + #[deprecated] + #[serde(skip_serializing_if = "Option::is_none")] + pub context_max: Option, + /// AI Credits cost per billing batch of input tokens + #[serde(skip_serializing_if = "Option::is_none")] + pub input_price: Option, + /// Long context tier pricing (available for models with extended context windows) + #[serde(skip_serializing_if = "Option::is_none")] + pub long_context: Option, + /// Prompt token budget for the default tier. The total context window is this value plus the model's max_output_tokens. + #[serde(skip_serializing_if = "Option::is_none")] + pub max_prompt_tokens: Option, + /// AI Credits cost per billing batch of output tokens + #[serde(skip_serializing_if = "Option::is_none")] + pub output_price: Option, } -/// Session-scoped approval details for read-only filesystem operations. +/// Billing information /// ///
/// @@ -7708,12 +8199,22 @@ pub struct PermissionDecisionApproveForSessionApprovalCommands { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionApproveForSessionApprovalRead { - /// Approval covering read-only filesystem operations. - pub kind: PermissionDecisionApproveForSessionApprovalReadKind, +pub struct ModelBilling { + /// Whole-number percentage discount (0-100) applied to usage billed through this model. Populated for the synthetic `auto` model, where requests routed by auto-mode are billed at a reduced rate; absent for concrete models. + #[serde(skip_serializing_if = "Option::is_none")] + pub discount_percent: Option, + /// Billing cost multiplier relative to the base rate + #[serde(skip_serializing_if = "Option::is_none")] + pub multiplier: Option, + /// Active server-driven promotion for this model, if any. Present when the model is being promoted with a discount, which may be time-boxed or open-ended. + #[serde(skip_serializing_if = "Option::is_none")] + pub promo: Option, + /// Token-level pricing information for this model + #[serde(skip_serializing_if = "Option::is_none")] + pub token_prices: Option, } -/// Session-scoped approval details for filesystem write operations. +/// Vision-specific limits /// ///
/// @@ -7723,12 +8224,19 @@ pub struct PermissionDecisionApproveForSessionApprovalRead { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionApproveForSessionApprovalWrite { - /// Approval covering filesystem write operations. - pub kind: PermissionDecisionApproveForSessionApprovalWriteKind, +pub struct ModelCapabilitiesLimitsVision { + /// Maximum image size in bytes + #[serde(rename = "max_prompt_image_size")] + pub max_prompt_image_size: i64, + /// Maximum number of images per prompt + #[serde(rename = "max_prompt_images")] + pub max_prompt_images: i64, + /// MIME types the model accepts + #[serde(rename = "supported_media_types")] + pub supported_media_types: Vec, } -/// Session-scoped approval details for an MCP server tool, or all tools on the server when `toolName` is null. +/// Token limits for prompts, outputs, and context window /// ///
/// @@ -7738,16 +8246,25 @@ pub struct PermissionDecisionApproveForSessionApprovalWrite { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionApproveForSessionApprovalMcp { - /// Approval covering an MCP tool. - pub kind: PermissionDecisionApproveForSessionApprovalMcpKind, - /// MCP server name. - pub server_name: String, - /// MCP tool name, or null to cover every tool on the server. - pub tool_name: Option, +pub struct ModelCapabilitiesLimits { + /// Maximum total context window size in tokens + #[serde( + rename = "max_context_window_tokens", + skip_serializing_if = "Option::is_none" + )] + pub max_context_window_tokens: Option, + /// Maximum number of output/completion tokens + #[serde(rename = "max_output_tokens", skip_serializing_if = "Option::is_none")] + pub max_output_tokens: Option, + /// Maximum number of prompt/input tokens + #[serde(rename = "max_prompt_tokens", skip_serializing_if = "Option::is_none")] + pub max_prompt_tokens: Option, + /// Vision-specific limits + #[serde(skip_serializing_if = "Option::is_none")] + pub vision: Option, } -/// Session-scoped approval details for MCP sampling requests from a server. +/// Feature flags indicating what the model supports /// ///
/// @@ -7757,14 +8274,19 @@ pub struct PermissionDecisionApproveForSessionApprovalMcp { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionApproveForSessionApprovalMcpSampling { - /// Approval covering MCP sampling requests for a server. - pub kind: PermissionDecisionApproveForSessionApprovalMcpSamplingKind, - /// MCP server name. - pub server_name: String, +pub struct ModelCapabilitiesSupports { + /// Resolved Anthropic adaptive-thinking capability — unsupported / optional / required. 'required' models reject thinking.type='enabled' with HTTP 400 (e.g. opus-4.7/4.8). + #[serde(rename = "adaptive_thinking", skip_serializing_if = "Option::is_none")] + pub adaptive_thinking: Option, + /// Whether this model supports reasoning effort configuration + #[serde(skip_serializing_if = "Option::is_none")] + pub reasoning_effort: Option, + /// Whether this model supports vision/image input + #[serde(skip_serializing_if = "Option::is_none")] + pub vision: Option, } -/// Session-scoped approval details for writes to long-term memory. +/// Model capabilities and limits /// ///
/// @@ -7774,12 +8296,16 @@ pub struct PermissionDecisionApproveForSessionApprovalMcpSampling { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionApproveForSessionApprovalMemory { - /// Approval covering writes to long-term memory. - pub kind: PermissionDecisionApproveForSessionApprovalMemoryKind, +pub struct ModelCapabilities { + /// Token limits for prompts, outputs, and context window + #[serde(skip_serializing_if = "Option::is_none")] + pub limits: Option, + /// Feature flags indicating what the model supports + #[serde(skip_serializing_if = "Option::is_none")] + pub supports: Option, } -/// Session-scoped approval details for a custom tool, keyed by tool name. +/// Policy state (if applicable) /// ///
/// @@ -7789,14 +8315,15 @@ pub struct PermissionDecisionApproveForSessionApprovalMemory { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionApproveForSessionApprovalCustomTool { - /// Approval covering a custom tool. - pub kind: PermissionDecisionApproveForSessionApprovalCustomToolKind, - /// Custom tool name. - pub tool_name: String, +pub struct ModelPolicy { + /// Current policy state for this model + pub state: ModelPolicyState, + /// Usage terms or conditions for this model + #[serde(skip_serializing_if = "Option::is_none")] + pub terms: Option, } -/// Session-scoped approval details for extension-management operations, optionally narrowed by operation. +/// Copilot model metadata, including identifier, display name, capabilities, policy, billing, reasoning efforts, and picker categories. /// ///
/// @@ -7806,15 +8333,31 @@ pub struct PermissionDecisionApproveForSessionApprovalCustomTool { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionApproveForSessionApprovalExtensionManagement { - /// Approval covering extension lifecycle operations such as enable, disable, or reload. - pub kind: PermissionDecisionApproveForSessionApprovalExtensionManagementKind, - /// Optional operation identifier; when omitted, the approval covers all extension management operations. +pub struct Model { + /// Billing information #[serde(skip_serializing_if = "Option::is_none")] - pub operation: Option, + pub billing: Option, + /// Model capabilities and limits + pub capabilities: ModelCapabilities, + /// Model identifier (e.g., "claude-sonnet-4.5") + pub id: String, + /// Model capability category for grouping in the model picker + #[serde(skip_serializing_if = "Option::is_none")] + pub model_picker_category: Option, + /// Relative cost tier for token-based billing users + #[serde(skip_serializing_if = "Option::is_none")] + pub model_picker_price_category: Option, + /// Display name + pub name: String, + /// Policy state (if applicable) + #[serde(skip_serializing_if = "Option::is_none")] + pub policy: Option, + /// Supported reasoning effort levels (only present if model supports reasoning effort) + #[serde(skip_serializing_if = "Option::is_none")] + pub supported_reasoning_efforts: Option>, } -/// Session-scoped approval details for an extension's permission-gated capability access, keyed by extension name. +/// Vision-specific limits /// ///
/// @@ -7824,14 +8367,25 @@ pub struct PermissionDecisionApproveForSessionApprovalExtensionManagement { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess { - /// Extension name. - pub extension_name: String, - /// Approval covering an extension's request to access a permission-gated capability. - pub kind: PermissionDecisionApproveForSessionApprovalExtensionPermissionAccessKind, +pub struct ModelCapabilitiesOverrideLimitsVision { + /// Maximum image size in bytes + #[serde( + rename = "max_prompt_image_size", + skip_serializing_if = "Option::is_none" + )] + pub max_prompt_image_size: Option, + /// Maximum number of images per prompt + #[serde(rename = "max_prompt_images", skip_serializing_if = "Option::is_none")] + pub max_prompt_images: Option, + /// MIME types the model accepts + #[serde( + rename = "supported_media_types", + skip_serializing_if = "Option::is_none" + )] + pub supported_media_types: Option>, } -/// Permission-decision request variant to approve for the rest of the session, with optional tool approval or URL domain. +/// Token limits for prompts, outputs, and context window /// ///
/// @@ -7841,18 +8395,25 @@ pub struct PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionApproveForSession { - /// Session-scoped approval to remember (tool prompts only; omitted for path/url prompts) - #[serde(skip_serializing_if = "Option::is_none")] - pub approval: Option, - /// URL domain to approve for the rest of the session (URL prompts only) +pub struct ModelCapabilitiesOverrideLimits { + /// Maximum total context window size in tokens + #[serde( + rename = "max_context_window_tokens", + skip_serializing_if = "Option::is_none" + )] + pub max_context_window_tokens: Option, + /// Maximum number of output/completion tokens + #[serde(rename = "max_output_tokens", skip_serializing_if = "Option::is_none")] + pub max_output_tokens: Option, + /// Maximum number of prompt/input tokens + #[serde(rename = "max_prompt_tokens", skip_serializing_if = "Option::is_none")] + pub max_prompt_tokens: Option, + /// Vision-specific limits #[serde(skip_serializing_if = "Option::is_none")] - pub domain: Option, - /// Approve and remember for the rest of the session - pub kind: PermissionDecisionApproveForSessionKind, + pub vision: Option, } -/// Location-scoped approval details for specific command identifiers. +/// Feature flags indicating what the model supports /// ///
/// @@ -7862,14 +8423,19 @@ pub struct PermissionDecisionApproveForSession { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionApproveForLocationApprovalCommands { - /// Command identifiers covered by this approval. - pub command_identifiers: Vec, - /// Approval scoped to specific command identifiers. - pub kind: PermissionDecisionApproveForLocationApprovalCommandsKind, +pub struct ModelCapabilitiesOverrideSupports { + /// Resolved Anthropic adaptive-thinking capability — unsupported / optional / required. 'required' models reject thinking.type='enabled' with HTTP 400 (e.g. opus-4.7/4.8). + #[serde(rename = "adaptive_thinking", skip_serializing_if = "Option::is_none")] + pub adaptive_thinking: Option, + /// Whether this model supports reasoning effort configuration + #[serde(skip_serializing_if = "Option::is_none")] + pub reasoning_effort: Option, + /// Whether this model supports vision/image input + #[serde(skip_serializing_if = "Option::is_none")] + pub vision: Option, } -/// Location-scoped approval details for read-only filesystem operations. +/// Optional capability overrides (vision, tool_calls, reasoning, etc.). /// ///
/// @@ -7879,12 +8445,16 @@ pub struct PermissionDecisionApproveForLocationApprovalCommands { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionApproveForLocationApprovalRead { - /// Approval covering read-only filesystem operations. - pub kind: PermissionDecisionApproveForLocationApprovalReadKind, +pub struct ModelCapabilitiesOverride { + /// Token limits for prompts, outputs, and context window + #[serde(skip_serializing_if = "Option::is_none")] + pub limits: Option, + /// Feature flags indicating what the model supports + #[serde(skip_serializing_if = "Option::is_none")] + pub supports: Option, } -/// Location-scoped approval details for filesystem write operations. +/// List of Copilot models available to the resolved user, including capabilities and billing metadata. /// ///
/// @@ -7894,12 +8464,12 @@ pub struct PermissionDecisionApproveForLocationApprovalRead { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionApproveForLocationApprovalWrite { - /// Approval covering filesystem write operations. - pub kind: PermissionDecisionApproveForLocationApprovalWriteKind, +pub struct ModelList { + /// List of available models with full metadata + pub models: Vec, } -/// Location-scoped approval details for an MCP server tool, or all tools on the server when `toolName` is null. +/// Optional listing options. /// ///
/// @@ -7909,16 +8479,13 @@ pub struct PermissionDecisionApproveForLocationApprovalWrite { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionApproveForLocationApprovalMcp { - /// Approval covering an MCP tool. - pub kind: PermissionDecisionApproveForLocationApprovalMcpKind, - /// MCP server name. - pub server_name: String, - /// MCP tool name, or null to cover every tool on the server. - pub tool_name: Option, +pub struct ModelListRequest { + /// If true, bypasses the per-session model list cache and re-fetches from CAPI. + #[serde(skip_serializing_if = "Option::is_none")] + pub skip_cache: Option, } -/// Location-scoped approval details for MCP sampling requests from a server. +/// Reasoning effort level to apply to the currently selected model. /// ///
/// @@ -7928,14 +8495,12 @@ pub struct PermissionDecisionApproveForLocationApprovalMcp { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionApproveForLocationApprovalMcpSampling { - /// Approval covering MCP sampling requests for a server. - pub kind: PermissionDecisionApproveForLocationApprovalMcpSamplingKind, - /// MCP server name. - pub server_name: String, +pub struct ModelSetReasoningEffortRequest { + /// Reasoning effort level to apply to the currently selected model. The host is responsible for validating the value against the model's supported levels before calling. + pub reasoning_effort: String, } -/// Location-scoped approval details for writes to long-term memory. +/// Update the session's reasoning effort without changing the selected model. Use `switchTo` instead when you also need to change the model. The runtime stores the effort on the session and applies it to subsequent turns. /// ///
/// @@ -7945,12 +8510,12 @@ pub struct PermissionDecisionApproveForLocationApprovalMcpSampling { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionApproveForLocationApprovalMemory { - /// Approval covering writes to long-term memory. - pub kind: PermissionDecisionApproveForLocationApprovalMemoryKind, +pub struct ModelSetReasoningEffortResult { + /// Reasoning effort level recorded on the session after the update + pub reasoning_effort: String, } -/// Location-scoped approval details for a custom tool, keyed by tool name. +/// Optional GitHub token used to list models for a specific user instead of the global auth context. /// ///
/// @@ -7960,14 +8525,13 @@ pub struct PermissionDecisionApproveForLocationApprovalMemory { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionApproveForLocationApprovalCustomTool { - /// Approval covering a custom tool. - pub kind: PermissionDecisionApproveForLocationApprovalCustomToolKind, - /// Custom tool name. - pub tool_name: String, +pub struct ModelsListRequest { + /// GitHub token for per-user model listing. When provided, resolves this token to determine the user's Copilot plan and available models instead of using the global auth. + #[serde(skip_serializing_if = "Option::is_none")] + pub git_hub_token: Option, } -/// Location-scoped approval details for extension-management operations, optionally narrowed by operation. +/// Target model identifier and optional reasoning effort, summary, capability overrides, and context tier. /// ///
/// @@ -7977,15 +8541,30 @@ pub struct PermissionDecisionApproveForLocationApprovalCustomTool { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionApproveForLocationApprovalExtensionManagement { - /// Approval covering extension lifecycle operations such as enable, disable, or reload. - pub kind: PermissionDecisionApproveForLocationApprovalExtensionManagementKind, - /// Optional operation identifier; when omitted, the approval covers all extension management operations. +pub struct ModelSwitchToRequest { + /// Explicit context tier for the selected model. `"default"` / `"long_context"` apply the requested tier; omit this field to use normal model behavior with no explicit tier. #[serde(skip_serializing_if = "Option::is_none")] - pub operation: Option, + pub context_tier: Option, + /// When true, defer this switch (enqueue it) if another model change is already queued, even when no turn is active — so it drains last (FIFO) and wins over the already-queued change. Intended for genuine user-initiated model selections; internal restore/reapply switches omit it and apply immediately when no turn is active. When no other model change is queued this has no effect (a switch still applies immediately unless a turn is active). + #[serde(skip_serializing_if = "Option::is_none")] + pub defer_if_model_change_queued: Option, + /// Override individual model capabilities resolved by the runtime + #[serde(skip_serializing_if = "Option::is_none")] + pub model_capabilities: Option, + /// Model selection id to switch to, as returned by `list`. A bare id (e.g. `claude-sonnet-4.6`) names a Copilot (CAPI) model; a provider-qualified id (`provider/id`, e.g. `acme/claude-sonnet`) targets a registry BYOK model. + pub model_id: String, + /// Reasoning effort level to use for the model. CAPI values are model-defined and validated against the selected model; BYOK providers may define additional values. "none" disables reasoning. When omitted, no effort override is applied. + #[serde(skip_serializing_if = "Option::is_none")] + pub reasoning_effort: Option, + /// Reasoning summary mode to request for supported model clients + #[serde(skip_serializing_if = "Option::is_none")] + pub reasoning_summary: Option, + /// Output verbosity level to request for supported models + #[serde(skip_serializing_if = "Option::is_none")] + pub verbosity: Option, } -/// Location-scoped approval details for an extension's permission-gated capability access, keyed by extension name. +/// The model identifier active on the session after the switch. /// ///
/// @@ -7995,14 +8574,16 @@ pub struct PermissionDecisionApproveForLocationApprovalExtensionManagement { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess { - /// Extension name. - pub extension_name: String, - /// Approval covering an extension's request to access a permission-gated capability. - pub kind: PermissionDecisionApproveForLocationApprovalExtensionPermissionAccessKind, +pub struct ModelSwitchToResult { + /// True when the switch was deferred (enqueued as a cancellable `/model` command) because a turn was active or another model change was already queued, rather than applied immediately. When true, the session's live model is unchanged until the queued change drains. + #[serde(skip_serializing_if = "Option::is_none")] + pub deferred: Option, + /// Currently active model identifier after the switch + #[serde(skip_serializing_if = "Option::is_none")] + pub model_id: Option, } -/// Permission-decision request variant to approve and persist a permission for a project location, with approval details and location key. +/// Agent interaction mode to apply to the session. /// ///
/// @@ -8010,18 +8591,14 @@ pub struct PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionApproveForLocation { - /// Approval to persist for this location - pub approval: PermissionDecisionApproveForLocationApproval, - /// Approve and persist for this project location - pub kind: PermissionDecisionApproveForLocationKind, - /// Location key (git root or cwd) to persist the approval to - pub location_key: String, +pub struct ModeSetRequest { + /// The session mode the agent is operating in + pub mode: SessionMode, } -/// Permission-decision request variant to permanently approve a URL domain across sessions. +/// Azure-specific provider options. /// ///
/// @@ -8031,14 +8608,13 @@ pub struct PermissionDecisionApproveForLocation { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionApprovePermanently { - /// URL domain to approve permanently - pub domain: String, - /// Approve and persist across sessions (URL prompts only) - pub kind: PermissionDecisionApprovePermanentlyKind, +pub struct ProviderConfigAzure { + /// API version. When set, uses the versioned deployment route. When omitted, uses the GA versionless v1 route. + #[serde(skip_serializing_if = "Option::is_none")] + pub api_version: Option, } -/// Permission-decision request variant to reject a pending permission request, with optional feedback. +/// A named BYOK provider connection (transport + credentials). /// ///
/// @@ -8048,15 +8624,38 @@ pub struct PermissionDecisionApprovePermanently { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionReject { - /// Optional feedback explaining the rejection +pub struct NamedProviderConfig { + /// API key. Optional for local providers like Ollama. #[serde(skip_serializing_if = "Option::is_none")] - pub feedback: Option, - /// Reject the request - pub kind: PermissionDecisionRejectKind, + pub api_key: Option, + /// Azure-specific provider options. + #[serde(skip_serializing_if = "Option::is_none")] + pub azure: Option, + /// API endpoint URL. + pub base_url: String, + /// Bearer token for authentication. Sets the Authorization header directly. Takes precedence over apiKey when both are set. + #[serde(skip_serializing_if = "Option::is_none")] + pub bearer_token: Option, + /// When true, the SDK client supplies bearer tokens on demand: the runtime calls the client-session `providerToken.getToken` callback before each request and applies the returned token as an `Authorization: Bearer ` header. This is the bearer/OAuth scheme used by Azure AD / managed-identity tokens and provider OAuth access tokens (including Anthropic's), not a provider-specific API-key header such as Anthropic's `x-api-key`. The token-acquiring function itself stays on the SDK side and is never serialized; only this flag crosses the wire. When set alongside `apiKey`/`bearerToken`, the callback takes precedence: the runtime applies the token returned by `providerToken.getToken` as the `Authorization: Bearer` header for each request and does not send the static credential. + #[serde(skip_serializing_if = "Option::is_none")] + pub has_bearer_token_provider: Option, + /// Custom HTTP headers to include in all outbound requests to the provider. + #[serde(skip_serializing_if = "Option::is_none")] + pub headers: Option>, + /// Stable identifier referenced by BYOK model definitions. Must not contain '/'. + pub name: String, + /// Provider transport. Defaults to "http". + #[serde(skip_serializing_if = "Option::is_none")] + pub transport: Option, + /// Provider type. Defaults to "openai" for generic OpenAI-compatible APIs. + #[serde(skip_serializing_if = "Option::is_none")] + pub r#type: Option, + /// Wire API format (openai/azure only). Defaults to "completions". + #[serde(skip_serializing_if = "Option::is_none")] + pub wire_api: Option, } -/// Permission-decision variant indicating no user was available to confirm the request. +/// The session's friendly name, or null when not yet set. /// ///
/// @@ -8066,12 +8665,12 @@ pub struct PermissionDecisionReject { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionUserNotAvailable { - /// No user is available to confirm the request - pub kind: PermissionDecisionUserNotAvailableKind, +pub struct NameGetResult { + /// The session name (user-set or auto-generated), or null if not yet set + pub name: Option, } -/// Permission-decision variant indicating the request was approved. +/// Auto-generated session summary to apply as the session's name when no user-set name exists. /// ///
/// @@ -8081,12 +8680,12 @@ pub struct PermissionDecisionUserNotAvailable { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionApproved { - /// The permission request was approved - pub kind: PermissionDecisionApprovedKind, +pub struct NameSetAutoRequest { + /// Auto-generated session summary. Empty/whitespace-only values are ignored; values are trimmed before persisting. + pub summary: String, } -/// Permission-decision variant indicating approval was remembered for the session, with approval details. +/// Indicates whether the auto-generated summary was applied as the session's name. /// ///
/// @@ -8094,16 +8693,14 @@ pub struct PermissionDecisionApproved { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionApprovedForSession { - /// The approval to add as a session-scoped rule - pub approval: UserToolSessionApproval, - /// Approved and remembered for the rest of the session - pub kind: PermissionDecisionApprovedForSessionKind, +pub struct NameSetAutoResult { + /// Whether the auto-generated summary was persisted. False if the session already has a user-set name, the summary normalized to empty, or the session does not have a workspace. + pub applied: bool, } -/// Permission-decision variant indicating approval was persisted for a project location, with approval details and location key. +/// New friendly name to apply to the session. /// ///
/// @@ -8111,18 +8708,14 @@ pub struct PermissionDecisionApprovedForSession { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionApprovedForLocation { - /// The approval to persist for this location - pub approval: UserToolSessionApproval, - /// Approved and persisted for this project location - pub kind: PermissionDecisionApprovedForLocationKind, - /// The location key (git root or cwd) to persist the approval to - pub location_key: String, +pub struct NameSetRequest { + /// New session name (1–100 characters, trimmed of leading/trailing whitespace) + pub name: String, } -/// Permission-decision variant indicating the request was cancelled before use, with an optional reason. +/// Source descriptor for a `session.options.update` content-exclusion rule, with source name and type. /// ///
/// @@ -8132,15 +8725,12 @@ pub struct PermissionDecisionApprovedForLocation { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionCancelled { - /// The permission request was cancelled before a response was used - pub kind: PermissionDecisionCancelledKind, - /// Optional explanation of why the request was cancelled - #[serde(skip_serializing_if = "Option::is_none")] - pub reason: Option, +pub struct OptionsUpdateAdditionalContentExclusionPolicyRuleSource { + pub name: String, + pub r#type: String, } -/// Permission-decision variant indicating explicit denial by permission rules, with the matching rules. +/// Single content-exclusion rule supplied to `session.options.update`, with paths, match conditions, and source. /// ///
/// @@ -8150,14 +8740,17 @@ pub struct PermissionDecisionCancelled { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionDeniedByRules { - /// Denied because approval rules explicitly blocked it - pub kind: PermissionDecisionDeniedByRulesKind, - /// Rules that denied the request - pub rules: Vec, +pub struct OptionsUpdateAdditionalContentExclusionPolicyRule { + #[serde(skip_serializing_if = "Option::is_none")] + pub if_any_match: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub if_none_match: Option>, + pub paths: Vec, + /// Source descriptor for a `session.options.update` content-exclusion rule, with source name and type. + pub source: OptionsUpdateAdditionalContentExclusionPolicyRuleSource, } -/// Permission-decision variant indicating no approval rule matched and user confirmation was unavailable. +/// Content-exclusion policy supplied to `session.options.update`, with rules, last-updated data, and scope. /// ///
/// @@ -8167,12 +8760,15 @@ pub struct PermissionDecisionDeniedByRules { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser { - /// Denied because no approval rule matched and user confirmation was unavailable - pub kind: PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUserKind, +pub struct OptionsUpdateAdditionalContentExclusionPolicy { + #[serde(rename = "last_updated_at")] + pub last_updated_at: serde_json::Value, + pub rules: Vec, + /// Allowed values for the `OptionsUpdateAdditionalContentExclusionPolicyScope` enumeration. + pub scope: OptionsUpdateAdditionalContentExclusionPolicyScope, } -/// Permission-decision variant indicating the user denied an interactive prompt, with optional feedback and force-reject flag. +/// Pending permission prompt reconstructed from event history, with request ID and user-facing prompt details. /// ///
/// @@ -8180,20 +8776,16 @@ pub struct PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionDeniedInteractivelyByUser { - /// Optional feedback from the user explaining the denial - #[serde(skip_serializing_if = "Option::is_none")] - pub feedback: Option, - /// Whether to force-reject the current agent turn - #[serde(skip_serializing_if = "Option::is_none")] - pub force_reject: Option, - /// Denied by the user during an interactive prompt - pub kind: PermissionDecisionDeniedInteractivelyByUserKind, +pub struct PendingPermissionRequest { + /// The user-facing permission prompt details (commands, write, read, mcp, url, memory, custom-tool, path, hook) + pub request: PermissionPromptRequest, + /// Unique identifier for the pending permission request + pub request_id: RequestId, } -/// Permission-decision variant indicating denial by content-exclusion policy, with path and message. +/// List of pending permission requests reconstructed from event history. /// ///
/// @@ -8203,16 +8795,12 @@ pub struct PermissionDecisionDeniedInteractivelyByUser { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionDeniedByContentExclusionPolicy { - /// Denied by the organization's content exclusion policy - pub kind: PermissionDecisionDeniedByContentExclusionPolicyKind, - /// Human-readable explanation of why the path was excluded - pub message: String, - /// File path that triggered the exclusion - pub path: String, +pub struct PendingPermissionRequestList { + /// Pending permission prompts reconstructed from the session's event history. Equivalent to the set of `permission.requested` events that have not yet been followed by a matching `permission.completed` event. Used by clients (e.g. the CLI) to hydrate UI for prompts that were emitted before the client attached to the session. + pub items: Vec, } -/// Permission-decision variant indicating denial by a permission request hook, with optional message and interrupt flag. +/// Permission-decision request variant to approve only the current permission request. /// ///
/// @@ -8222,35 +8810,15 @@ pub struct PermissionDecisionDeniedByContentExclusionPolicy { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionDecisionDeniedByPermissionRequestHook { - /// Whether to interrupt the current agent turn - #[serde(skip_serializing_if = "Option::is_none")] - pub interrupt: Option, - /// Denied by a permission request hook registered by an extension or plugin - pub kind: PermissionDecisionDeniedByPermissionRequestHookKind, - /// Optional message from the hook explaining the denial +pub struct PermissionDecisionApproveOnce { + /// True only when a host surfaced this request to a user who approved it. #[serde(skip_serializing_if = "Option::is_none")] - pub message: Option, -} - -/// Pending permission request ID and the decision to apply (approve/reject and scope). -/// -///
-/// -/// **Experimental.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
-#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct PermissionDecisionRequest { - /// Request ID of the pending permission request - pub request_id: RequestId, - /// The client's response to the pending permission prompt - pub result: PermissionDecision, + pub approved_interactively: Option, + /// Approve this single request only + pub kind: PermissionDecisionApproveOnceKind, } -/// Location-persisted tool approval details for specific command identifiers. +/// Session-scoped approval details for specific command identifiers. /// ///
/// @@ -8260,14 +8828,14 @@ pub struct PermissionDecisionRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsLocationsAddToolApprovalDetailsCommands { +pub struct PermissionDecisionApproveForSessionApprovalCommands { /// Command identifiers covered by this approval. pub command_identifiers: Vec, /// Approval scoped to specific command identifiers. - pub kind: PermissionsLocationsAddToolApprovalDetailsCommandsKind, + pub kind: PermissionDecisionApproveForSessionApprovalCommandsKind, } -/// Location-persisted tool approval details for read-only filesystem operations. +/// Session-scoped approval details for read-only filesystem operations. /// ///
/// @@ -8277,12 +8845,12 @@ pub struct PermissionsLocationsAddToolApprovalDetailsCommands { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsLocationsAddToolApprovalDetailsRead { +pub struct PermissionDecisionApproveForSessionApprovalRead { /// Approval covering read-only filesystem operations. - pub kind: PermissionsLocationsAddToolApprovalDetailsReadKind, + pub kind: PermissionDecisionApproveForSessionApprovalReadKind, } -/// Location-persisted tool approval details for filesystem write operations. +/// Session-scoped approval details for filesystem write operations. /// ///
/// @@ -8292,12 +8860,12 @@ pub struct PermissionsLocationsAddToolApprovalDetailsRead { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsLocationsAddToolApprovalDetailsWrite { +pub struct PermissionDecisionApproveForSessionApprovalWrite { /// Approval covering filesystem write operations. - pub kind: PermissionsLocationsAddToolApprovalDetailsWriteKind, + pub kind: PermissionDecisionApproveForSessionApprovalWriteKind, } -/// Location-persisted tool approval details for an MCP server tool, or all tools when `toolName` is null. +/// Session-scoped approval details for an MCP server tool, or all tools on the server when `toolName` is null. /// ///
/// @@ -8307,16 +8875,16 @@ pub struct PermissionsLocationsAddToolApprovalDetailsWrite { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsLocationsAddToolApprovalDetailsMcp { +pub struct PermissionDecisionApproveForSessionApprovalMcp { /// Approval covering an MCP tool. - pub kind: PermissionsLocationsAddToolApprovalDetailsMcpKind, + pub kind: PermissionDecisionApproveForSessionApprovalMcpKind, /// MCP server name. pub server_name: String, /// MCP tool name, or null to cover every tool on the server. pub tool_name: Option, } -/// Location-persisted tool approval details for MCP sampling requests from a server. +/// Session-scoped approval details for MCP sampling requests from a server. /// ///
/// @@ -8326,14 +8894,14 @@ pub struct PermissionsLocationsAddToolApprovalDetailsMcp { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsLocationsAddToolApprovalDetailsMcpSampling { +pub struct PermissionDecisionApproveForSessionApprovalMcpSampling { /// Approval covering MCP sampling requests for a server. - pub kind: PermissionsLocationsAddToolApprovalDetailsMcpSamplingKind, + pub kind: PermissionDecisionApproveForSessionApprovalMcpSamplingKind, /// MCP server name. pub server_name: String, } -/// Location-persisted tool approval details for writes to long-term memory. +/// Session-scoped approval details for writes to long-term memory. /// ///
/// @@ -8343,12 +8911,12 @@ pub struct PermissionsLocationsAddToolApprovalDetailsMcpSampling { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsLocationsAddToolApprovalDetailsMemory { +pub struct PermissionDecisionApproveForSessionApprovalMemory { /// Approval covering writes to long-term memory. - pub kind: PermissionsLocationsAddToolApprovalDetailsMemoryKind, + pub kind: PermissionDecisionApproveForSessionApprovalMemoryKind, } -/// Location-persisted tool approval details for a custom tool, keyed by tool name. +/// Session-scoped approval details for a custom tool, keyed by tool name. /// ///
/// @@ -8358,14 +8926,14 @@ pub struct PermissionsLocationsAddToolApprovalDetailsMemory { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsLocationsAddToolApprovalDetailsCustomTool { +pub struct PermissionDecisionApproveForSessionApprovalCustomTool { /// Approval covering a custom tool. - pub kind: PermissionsLocationsAddToolApprovalDetailsCustomToolKind, + pub kind: PermissionDecisionApproveForSessionApprovalCustomToolKind, /// Custom tool name. pub tool_name: String, } -/// Location-persisted tool approval details for extension-management operations, optionally narrowed by operation. +/// Session-scoped approval details for extension-management operations, optionally narrowed by operation. /// ///
/// @@ -8375,15 +8943,15 @@ pub struct PermissionsLocationsAddToolApprovalDetailsCustomTool { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsLocationsAddToolApprovalDetailsExtensionManagement { +pub struct PermissionDecisionApproveForSessionApprovalExtensionManagement { /// Approval covering extension lifecycle operations such as enable, disable, or reload. - pub kind: PermissionsLocationsAddToolApprovalDetailsExtensionManagementKind, + pub kind: PermissionDecisionApproveForSessionApprovalExtensionManagementKind, /// Optional operation identifier; when omitted, the approval covers all extension management operations. #[serde(skip_serializing_if = "Option::is_none")] pub operation: Option, } -/// Location-persisted tool approval details for an extension's permission-gated capability access, keyed by extension name. +/// Session-scoped factory approval, optionally narrowed by approval key. /// ///
/// @@ -8393,14 +8961,15 @@ pub struct PermissionsLocationsAddToolApprovalDetailsExtensionManagement { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess { - /// Extension name. - pub extension_name: String, - /// Approval covering an extension's request to access a permission-gated capability. - pub kind: PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccessKind, +pub struct PermissionDecisionApproveForSessionApprovalFactory { + /// Optional factory operation name or canonical approval key; when omitted, the approval covers all factory operations. + #[serde(skip_serializing_if = "Option::is_none")] + pub approval_key: Option, + /// Approval covering factory operations. + pub kind: PermissionDecisionApproveForSessionApprovalFactoryKind, } -/// Location-scoped tool approval to persist. +/// Session-scoped approval details for an extension's permission-gated capability access, keyed by extension name. /// ///
/// @@ -8408,16 +8977,16 @@ pub struct PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionLocationAddToolApprovalParams { - /// Tool approval to persist and apply - pub approval: PermissionsLocationsAddToolApprovalDetails, - /// Location key (git root or cwd) to persist the approval to - pub location_key: String, +pub struct PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess { + /// Extension name. + pub extension_name: String, + /// Approval covering an extension's request to access a permission-gated capability. + pub kind: PermissionDecisionApproveForSessionApprovalExtensionPermissionAccessKind, } -/// Working directory to load persisted location permissions for. +/// Permission-decision request variant to approve for the rest of the session, with optional tool approval or URL domain. /// ///
/// @@ -8427,12 +8996,18 @@ pub struct PermissionLocationAddToolApprovalParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionLocationApplyParams { - /// Working directory whose persisted location permissions should be applied - pub working_directory: String, +pub struct PermissionDecisionApproveForSession { + /// Session-scoped approval to remember (tool prompts only; omitted for path/url prompts) + #[serde(skip_serializing_if = "Option::is_none")] + pub approval: Option, + /// URL domain to approve for the rest of the session (URL prompts only) + #[serde(skip_serializing_if = "Option::is_none")] + pub domain: Option, + /// Approve and remember for the rest of the session + pub kind: PermissionDecisionApproveForSessionKind, } -/// Summary of persisted location permissions applied to the session. +/// Location-scoped approval details for specific command identifiers. /// ///
/// @@ -8442,22 +9017,14 @@ pub struct PermissionLocationApplyParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionLocationApplyResult { - /// Number of persisted allowed directories added to the live path manager - pub applied_directory_count: i64, - /// Number of location-scoped rules added to the live permission service - pub applied_rule_count: i64, - /// Location-scoped rules applied to the live permission service - pub applied_rules: Vec, - /// Whether a different location was applied since the previous apply call - pub changed: bool, - /// Location key used in the location-permissions store - pub location_key: String, - /// Whether the location is a git repo or directory - pub location_type: PermissionLocationType, +pub struct PermissionDecisionApproveForLocationApprovalCommands { + /// Command identifiers covered by this approval. + pub command_identifiers: Vec, + /// Approval scoped to specific command identifiers. + pub kind: PermissionDecisionApproveForLocationApprovalCommandsKind, } -/// Working directory to resolve into a location-permissions key. +/// Location-scoped approval details for read-only filesystem operations. /// ///
/// @@ -8467,12 +9034,12 @@ pub struct PermissionLocationApplyResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionLocationResolveParams { - /// Working directory whose permission location should be resolved - pub working_directory: String, +pub struct PermissionDecisionApproveForLocationApprovalRead { + /// Approval covering read-only filesystem operations. + pub kind: PermissionDecisionApproveForLocationApprovalReadKind, } -/// Resolved location-permissions key and type. +/// Location-scoped approval details for filesystem write operations. /// ///
/// @@ -8482,14 +9049,12 @@ pub struct PermissionLocationResolveParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionLocationResolveResult { - /// Location key used in the location-permissions store - pub location_key: String, - /// Whether the location is a git repo or directory - pub location_type: PermissionLocationType, +pub struct PermissionDecisionApproveForLocationApprovalWrite { + /// Approval covering filesystem write operations. + pub kind: PermissionDecisionApproveForLocationApprovalWriteKind, } -/// Directory path to add to the session's allowed directories. +/// Location-scoped approval details for an MCP server tool, or all tools on the server when `toolName` is null. /// ///
/// @@ -8499,12 +9064,16 @@ pub struct PermissionLocationResolveResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionPathsAddParams { - /// Directory to add to the allow-list. The runtime resolves and validates the path before adding. - pub path: String, +pub struct PermissionDecisionApproveForLocationApprovalMcp { + /// Approval covering an MCP tool. + pub kind: PermissionDecisionApproveForLocationApprovalMcpKind, + /// MCP server name. + pub server_name: String, + /// MCP tool name, or null to cover every tool on the server. + pub tool_name: Option, } -/// Path to evaluate against the session's allowed directories. +/// Location-scoped approval details for MCP sampling requests from a server. /// ///
/// @@ -8514,12 +9083,14 @@ pub struct PermissionPathsAddParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionPathsAllowedCheckParams { - /// Path to check against the session's allowed directories - pub path: String, +pub struct PermissionDecisionApproveForLocationApprovalMcpSampling { + /// Approval covering MCP sampling requests for a server. + pub kind: PermissionDecisionApproveForLocationApprovalMcpSamplingKind, + /// MCP server name. + pub server_name: String, } -/// Indicates whether the supplied path is within the session's allowed directories. +/// Location-scoped approval details for writes to long-term memory. /// ///
/// @@ -8529,12 +9100,12 @@ pub struct PermissionPathsAllowedCheckParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionPathsAllowedCheckResult { - /// Whether the path is within the session's allowed directories - pub allowed: bool, +pub struct PermissionDecisionApproveForLocationApprovalMemory { + /// Approval covering writes to long-term memory. + pub kind: PermissionDecisionApproveForLocationApprovalMemoryKind, } -/// If specified, replaces the session's path-permission policy. The runtime constructs the appropriate PathManager based on these inputs (rooted at the session's working directory). Omit to leave the current path policy unchanged. +/// Location-scoped approval details for a custom tool, keyed by tool name. /// ///
/// @@ -8544,22 +9115,14 @@ pub struct PermissionPathsAllowedCheckResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionPathsConfig { - /// Additional directories to allow tool access to (in addition to the session's working directory). When `unrestricted` is true, these are still pre-populated on the UnrestrictedPathManager so they remain visible via getDirectories() (e.g. for @-mention completion). - #[serde(skip_serializing_if = "Option::is_none")] - pub additional_directories: Option>, - /// Whether to include the system temp directory in the allowed list (defaults to true). Ignored when `unrestricted` is true. - #[serde(skip_serializing_if = "Option::is_none")] - pub include_temp_directory: Option, - /// If true, the runtime allows access to all paths without prompting. Equivalent to constructing an UnrestrictedPathManager. - #[serde(skip_serializing_if = "Option::is_none")] - pub unrestricted: Option, - /// Workspace root path (special-cased to be allowed even before the directory exists). Ignored when `unrestricted` is true. - #[serde(skip_serializing_if = "Option::is_none")] - pub workspace_path: Option, +pub struct PermissionDecisionApproveForLocationApprovalCustomTool { + /// Approval covering a custom tool. + pub kind: PermissionDecisionApproveForLocationApprovalCustomToolKind, + /// Custom tool name. + pub tool_name: String, } -/// Snapshot of the session's allow-listed directories and primary working directory. +/// Location-scoped approval details for extension-management operations, optionally narrowed by operation. /// ///
/// @@ -8569,14 +9132,15 @@ pub struct PermissionPathsConfig { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionPathsList { - /// All directories currently allowed for tool access on this session. - pub directories: Vec, - /// The primary working directory for this session. - pub primary: String, +pub struct PermissionDecisionApproveForLocationApprovalExtensionManagement { + /// Approval covering extension lifecycle operations such as enable, disable, or reload. + pub kind: PermissionDecisionApproveForLocationApprovalExtensionManagementKind, + /// Optional operation identifier; when omitted, the approval covers all extension management operations. + #[serde(skip_serializing_if = "Option::is_none")] + pub operation: Option, } -/// Directory path to set as the session's new primary working directory. +/// Location-scoped factory approval, optionally narrowed by approval key. /// ///
/// @@ -8586,12 +9150,15 @@ pub struct PermissionPathsList { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionPathsUpdatePrimaryParams { - /// Directory to set as the new primary working directory for the session's permission policy. - pub path: String, +pub struct PermissionDecisionApproveForLocationApprovalFactory { + /// Optional factory operation name or canonical approval key; when omitted, the approval covers all factory operations. + #[serde(skip_serializing_if = "Option::is_none")] + pub approval_key: Option, + /// Approval covering factory operations. + pub kind: PermissionDecisionApproveForLocationApprovalFactoryKind, } -/// Path to evaluate against the session's workspace (primary) directory. +/// Location-scoped approval details for an extension's permission-gated capability access, keyed by extension name. /// ///
/// @@ -8601,12 +9168,14 @@ pub struct PermissionPathsUpdatePrimaryParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionPathsWorkspaceCheckParams { - /// Path to check against the session workspace directory - pub path: String, +pub struct PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess { + /// Extension name. + pub extension_name: String, + /// Approval covering an extension's request to access a permission-gated capability. + pub kind: PermissionDecisionApproveForLocationApprovalExtensionPermissionAccessKind, } -/// Indicates whether the supplied path is within the session's workspace directory. +/// Permission-decision request variant to approve and persist a permission for a project location, with approval details and location key. /// ///
/// @@ -8614,14 +9183,18 @@ pub struct PermissionPathsWorkspaceCheckParams { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionPathsWorkspaceCheckResult { - /// Whether the path is within the session workspace directory - pub allowed: bool, +pub struct PermissionDecisionApproveForLocation { + /// Approval to persist for this location + pub approval: PermissionDecisionApproveForLocationApproval, + /// Approve and persist for this project location + pub kind: PermissionDecisionApproveForLocationKind, + /// Location key (git root or cwd) to persist the approval to + pub location_key: String, } -/// Notification payload describing the permission prompt that the client just rendered. +/// Permission-decision request variant to permanently approve a URL domain across sessions. /// ///
/// @@ -8631,12 +9204,14 @@ pub struct PermissionPathsWorkspaceCheckResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionPromptShownNotification { - /// Human-readable description of the prompt the user is being asked to approve. Used by the runtime to fire the registered `permission_prompt` notification hook (e.g. terminal bell, desktop notification). - pub message: String, +pub struct PermissionDecisionApprovePermanently { + /// URL domain to approve permanently + pub domain: String, + /// Approve and persist across sessions (URL prompts only) + pub kind: PermissionDecisionApprovePermanentlyKind, } -/// Indicates whether the permission decision was applied; false when the request was already resolved. +/// Permission-decision request variant to reject a pending permission request, with optional feedback. /// ///
/// @@ -8646,12 +9221,15 @@ pub struct PermissionPromptShownNotification { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionRequestResult { - /// Whether the permission request was handled successfully - pub success: bool, +pub struct PermissionDecisionReject { + /// Optional feedback explaining the rejection + #[serde(skip_serializing_if = "Option::is_none")] + pub feedback: Option, + /// Reject the request + pub kind: PermissionDecisionRejectKind, } -/// If specified, replaces the session's approved/denied permission rules. Omit to leave the current rules unchanged. +/// Permission-decision variant indicating no user was available to confirm the request. /// ///
/// @@ -8661,14 +9239,12 @@ pub struct PermissionRequestResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionRulesSet { - /// Rules that auto-approve matching requests - pub approved: Vec, - /// Rules that auto-deny matching requests - pub denied: Vec, +pub struct PermissionDecisionUserNotAvailable { + /// No user is available to confirm the request + pub kind: PermissionDecisionUserNotAvailableKind, } -/// Source descriptor for a `session.permissions.configure` content-exclusion rule, with source name and type. +/// Permission-decision variant indicating the request was approved. /// ///
/// @@ -8678,12 +9254,12 @@ pub struct PermissionRulesSet { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsConfigureAdditionalContentExclusionPolicyRuleSource { - pub name: String, - pub r#type: String, +pub struct PermissionDecisionApproved { + /// The permission request was approved + pub kind: PermissionDecisionApprovedKind, } -/// Single content-exclusion rule supplied to `session.permissions.configure`, with paths, match conditions, and source. +/// Permission-decision variant indicating approval was remembered for the session, with approval details. /// ///
/// @@ -8691,19 +9267,16 @@ pub struct PermissionsConfigureAdditionalContentExclusionPolicyRuleSource { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsConfigureAdditionalContentExclusionPolicyRule { - #[serde(skip_serializing_if = "Option::is_none")] - pub if_any_match: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub if_none_match: Option>, - pub paths: Vec, - /// Source descriptor for a `session.permissions.configure` content-exclusion rule, with source name and type. - pub source: PermissionsConfigureAdditionalContentExclusionPolicyRuleSource, +pub struct PermissionDecisionApprovedForSession { + /// The approval to add as a session-scoped rule + pub approval: UserToolSessionApproval, + /// Approved and remembered for the rest of the session + pub kind: PermissionDecisionApprovedForSessionKind, } -/// Content-exclusion policy supplied to `session.permissions.configure`, with rules, last-updated data, and scope. +/// Permission-decision variant indicating approval was persisted for a project location, with approval details and location key. /// ///
/// @@ -8711,17 +9284,18 @@ pub struct PermissionsConfigureAdditionalContentExclusionPolicyRule { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsConfigureAdditionalContentExclusionPolicy { - #[serde(rename = "last_updated_at")] - pub last_updated_at: serde_json::Value, - pub rules: Vec, - /// Allowed values for the `PermissionsConfigureAdditionalContentExclusionPolicyScope` enumeration. - pub scope: PermissionsConfigureAdditionalContentExclusionPolicyScope, +pub struct PermissionDecisionApprovedForLocation { + /// The approval to persist for this location + pub approval: UserToolSessionApproval, + /// Approved and persisted for this project location + pub kind: PermissionDecisionApprovedForLocationKind, + /// The location key (git root or cwd) to persist the approval to + pub location_key: String, } -/// If specified, replaces the session's URL-permission policy. The runtime constructs a fresh DefaultUrlManager based on these inputs. Omit to leave the current URL policy unchanged. +/// Permission-decision variant indicating the request was cancelled before use, with an optional reason. /// ///
/// @@ -8731,16 +9305,15 @@ pub struct PermissionsConfigureAdditionalContentExclusionPolicy { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionUrlsConfig { - /// Initial list of allowed URL/domain patterns. Patterns may include path components. Ignored when `unrestricted` is true. - #[serde(skip_serializing_if = "Option::is_none")] - pub initial_allowed: Option>, - /// If true, the runtime allows access to all URLs without prompting. Initial allow-list is ignored when this is true. +pub struct PermissionDecisionCancelled { + /// The permission request was cancelled before a response was used + pub kind: PermissionDecisionCancelledKind, + /// Optional explanation of why the request was cancelled #[serde(skip_serializing_if = "Option::is_none")] - pub unrestricted: Option, + pub reason: Option, } -/// Patch of permission policy fields to apply (omit a field to leave it unchanged). +/// Permission-decision variant indicating explicit denial by permission rules, with the matching rules. /// ///
/// @@ -8750,29 +9323,14 @@ pub struct PermissionUrlsConfig { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsConfigureParams { - /// If specified, replaces the host-supplied GitHub Content Exclusion policies on the session (combined with natively-discovered policies when evaluating tool/file access). Omit to leave the current policies unchanged. - #[serde(skip_serializing_if = "Option::is_none")] - pub additional_content_exclusion_policies: - Option>, - /// If specified, sets whether path/URL read permission requests are auto-approved. Omit to leave the current value unchanged. - #[serde(skip_serializing_if = "Option::is_none")] - pub approve_all_read_permission_requests: Option, - /// If specified, sets whether tool permission requests are auto-approved without prompting. Omit to leave the current value unchanged. - #[serde(skip_serializing_if = "Option::is_none")] - pub approve_all_tool_permission_requests: Option, - /// If specified, replaces the session's path-permission policy. The runtime constructs the appropriate PathManager based on these inputs (rooted at the session's working directory). Omit to leave the current path policy unchanged. - #[serde(skip_serializing_if = "Option::is_none")] - pub paths: Option, - /// If specified, replaces the session's approved/denied permission rules. Omit to leave the current rules unchanged. - #[serde(skip_serializing_if = "Option::is_none")] - pub rules: Option, - /// If specified, replaces the session's URL-permission policy. The runtime constructs a fresh DefaultUrlManager based on these inputs. Omit to leave the current URL policy unchanged. - #[serde(skip_serializing_if = "Option::is_none")] - pub urls: Option, +pub struct PermissionDecisionDeniedByRules { + /// Denied because approval rules explicitly blocked it + pub kind: PermissionDecisionDeniedByRulesKind, + /// Rules that denied the request + pub rules: Vec, } -/// Indicates whether the operation succeeded. +/// Permission-decision variant indicating no approval rule matched and user confirmation was unavailable. /// ///
/// @@ -8782,12 +9340,12 @@ pub struct PermissionsConfigureParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsConfigureResult { - /// Whether the operation succeeded - pub success: bool, +pub struct PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser { + /// Denied because no approval rule matched and user confirmation was unavailable + pub kind: PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUserKind, } -/// Indicates whether the operation succeeded. +/// Permission-decision variant indicating the user denied an interactive prompt, with optional feedback and force-reject flag. /// ///
/// @@ -8797,12 +9355,18 @@ pub struct PermissionsConfigureResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsFolderTrustAddTrustedResult { - /// Whether the operation succeeded - pub success: bool, +pub struct PermissionDecisionDeniedInteractivelyByUser { + /// Optional feedback from the user explaining the denial + #[serde(skip_serializing_if = "Option::is_none")] + pub feedback: Option, + /// Whether to force-reject the current agent turn + #[serde(skip_serializing_if = "Option::is_none")] + pub force_reject: Option, + /// Denied by the user during an interactive prompt + pub kind: PermissionDecisionDeniedInteractivelyByUserKind, } -/// No parameters. +/// Permission-decision variant indicating denial by content-exclusion policy, with path and message. /// ///
/// @@ -8812,9 +9376,16 @@ pub struct PermissionsFolderTrustAddTrustedResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsGetAllowAllRequest {} +pub struct PermissionDecisionDeniedByContentExclusionPolicy { + /// Denied by the organization's content exclusion policy + pub kind: PermissionDecisionDeniedByContentExclusionPolicyKind, + /// Human-readable explanation of why the path was excluded + pub message: String, + /// File path that triggered the exclusion + pub path: String, +} -/// Indicates whether the operation succeeded. +/// Permission-decision variant indicating denial by a permission request hook, with optional message and interrupt flag. /// ///
/// @@ -8824,12 +9395,18 @@ pub struct PermissionsGetAllowAllRequest {} ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsLocationsAddToolApprovalResult { - /// Whether the operation succeeded - pub success: bool, +pub struct PermissionDecisionDeniedByPermissionRequestHook { + /// Whether to interrupt the current agent turn + #[serde(skip_serializing_if = "Option::is_none")] + pub interrupt: Option, + /// Denied by a permission request hook registered by an extension or plugin + pub kind: PermissionDecisionDeniedByPermissionRequestHookKind, + /// Optional message from the hook explaining the denial + #[serde(skip_serializing_if = "Option::is_none")] + pub message: Option, } -/// Scope and add/remove instructions for modifying session- or location-scoped permission rules. +/// Optional informational context describing how and where the permission decision was made. This does not affect permission behavior. /// ///
/// @@ -8839,21 +9416,16 @@ pub struct PermissionsLocationsAddToolApprovalResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsModifyRulesParams { - /// Rules to add to the scope. Applied before `remove`/`removeAll`. - #[serde(skip_serializing_if = "Option::is_none")] - pub add: Option>, - /// Specific rules to remove from the scope. Ignored when `removeAll` is true. - #[serde(skip_serializing_if = "Option::is_none")] - pub remove: Option>, - /// When true, removes every rule currently in the scope (after any `add` is applied). Useful for clearing the location scope wholesale. - #[serde(skip_serializing_if = "Option::is_none")] - pub remove_all: Option, - /// Whether the change applies to ephemeral session-scoped rules (cleared at session end) or to location-scoped rules persisted via the location-permissions config file. - pub scope: PermissionsModifyRulesScope, +pub struct PermissionDecisionContext { + /// Disposition of the permission request as observed by the responding client. + pub outcome: PermissionDecisionOutcome, + /// Controlled reason or actor responsible for the response. + pub source: PermissionDecisionSource, + /// Client surface that submitted the response. + pub surface: PermissionDecisionSurface, } -/// Indicates whether the operation succeeded. +/// Pending permission request ID and the decision to apply (approve/reject and scope). /// ///
/// @@ -8861,14 +9433,19 @@ pub struct PermissionsModifyRulesParams { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsModifyRulesResult { - /// Whether the operation succeeded - pub success: bool, +pub struct PermissionDecisionRequest { + /// Optional informational context describing how and where this response was made. Omit it to preserve legacy behavior without attributing an origin. + #[serde(skip_serializing_if = "Option::is_none")] + pub decision_context: Option, + /// Request ID of the pending permission request + pub request_id: RequestId, + /// The client's response to the pending permission prompt + pub result: PermissionDecision, } -/// Indicates whether the operation succeeded. +/// Location-persisted tool approval details for specific command identifiers. /// ///
/// @@ -8878,12 +9455,14 @@ pub struct PermissionsModifyRulesResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsNotifyPromptShownResult { - /// Whether the operation succeeded - pub success: bool, +pub struct PermissionsLocationsAddToolApprovalDetailsCommands { + /// Command identifiers covered by this approval. + pub command_identifiers: Vec, + /// Approval scoped to specific command identifiers. + pub kind: PermissionsLocationsAddToolApprovalDetailsCommandsKind, } -/// Indicates whether the operation succeeded. +/// Location-persisted tool approval details for read-only filesystem operations. /// ///
/// @@ -8893,12 +9472,12 @@ pub struct PermissionsNotifyPromptShownResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsPathsAddResult { - /// Whether the operation succeeded - pub success: bool, +pub struct PermissionsLocationsAddToolApprovalDetailsRead { + /// Approval covering read-only filesystem operations. + pub kind: PermissionsLocationsAddToolApprovalDetailsReadKind, } -/// No parameters; returns the session's allow-listed directories. +/// Location-persisted tool approval details for filesystem write operations. /// ///
/// @@ -8908,9 +9487,12 @@ pub struct PermissionsPathsAddResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsPathsListRequest {} +pub struct PermissionsLocationsAddToolApprovalDetailsWrite { + /// Approval covering filesystem write operations. + pub kind: PermissionsLocationsAddToolApprovalDetailsWriteKind, +} -/// Indicates whether the operation succeeded. +/// Location-persisted tool approval details for an MCP server tool, or all tools when `toolName` is null. /// ///
/// @@ -8920,12 +9502,16 @@ pub struct PermissionsPathsListRequest {} ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsPathsUpdatePrimaryResult { - /// Whether the operation succeeded - pub success: bool, +pub struct PermissionsLocationsAddToolApprovalDetailsMcp { + /// Approval covering an MCP tool. + pub kind: PermissionsLocationsAddToolApprovalDetailsMcpKind, + /// MCP server name. + pub server_name: String, + /// MCP tool name, or null to cover every tool on the server. + pub tool_name: Option, } -/// No parameters; returns currently-pending permission requests for the session. +/// Location-persisted tool approval details for MCP sampling requests from a server. /// ///
/// @@ -8935,9 +9521,14 @@ pub struct PermissionsPathsUpdatePrimaryResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsPendingRequestsRequest {} +pub struct PermissionsLocationsAddToolApprovalDetailsMcpSampling { + /// Approval covering MCP sampling requests for a server. + pub kind: PermissionsLocationsAddToolApprovalDetailsMcpSamplingKind, + /// MCP server name. + pub server_name: String, +} -/// No parameters; clears all session-scoped tool permission approvals. +/// Location-persisted tool approval details for writes to long-term memory. /// ///
/// @@ -8947,9 +9538,12 @@ pub struct PermissionsPendingRequestsRequest {} ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsResetSessionApprovalsRequest {} +pub struct PermissionsLocationsAddToolApprovalDetailsMemory { + /// Approval covering writes to long-term memory. + pub kind: PermissionsLocationsAddToolApprovalDetailsMemoryKind, +} -/// Indicates whether the operation succeeded. +/// Location-persisted tool approval details for a custom tool, keyed by tool name. /// ///
/// @@ -8959,12 +9553,14 @@ pub struct PermissionsResetSessionApprovalsRequest {} ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsResetSessionApprovalsResult { - /// Whether the operation succeeded - pub success: bool, +pub struct PermissionsLocationsAddToolApprovalDetailsCustomTool { + /// Approval covering a custom tool. + pub kind: PermissionsLocationsAddToolApprovalDetailsCustomToolKind, + /// Custom tool name. + pub tool_name: String, } -/// Allow-all mode to apply for the session. +/// Location-persisted tool approval details for extension-management operations, optionally narrowed by operation. /// ///
/// @@ -8974,22 +9570,15 @@ pub struct PermissionsResetSessionApprovalsResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsSetAllowAllRequest { - /// Legacy full allow-all toggle. Prefer `mode`; when `mode` is omitted, `enabled: true` is treated as `mode: "on"` and any other value is treated as `mode: "off"`. - #[serde(skip_serializing_if = "Option::is_none")] - pub enabled: Option, - /// Allow-all mode to apply. `on` enables full allow-all; `auto` enables advisory LLM auto-approval; `off` disables both. - #[serde(skip_serializing_if = "Option::is_none")] - pub mode: Option, - /// Optional model id for the `auto` mode auto-approval LLM judging. Only meaningful when `mode` is `auto`; ignored otherwise. When omitted, the session's active model is used. - #[serde(skip_serializing_if = "Option::is_none")] - pub model: Option, - /// Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers. +pub struct PermissionsLocationsAddToolApprovalDetailsExtensionManagement { + /// Approval covering extension lifecycle operations such as enable, disable, or reload. + pub kind: PermissionsLocationsAddToolApprovalDetailsExtensionManagementKind, + /// Optional operation identifier; when omitted, the approval covers all extension management operations. #[serde(skip_serializing_if = "Option::is_none")] - pub source: Option, + pub operation: Option, } -/// Allow-all toggle for tool permission requests, with an optional telemetry source. +/// Location-persisted factory approval, optionally narrowed by approval key. /// ///
/// @@ -8999,15 +9588,15 @@ pub struct PermissionsSetAllowAllRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsSetApproveAllRequest { - /// Whether to auto-approve all tool permission requests - pub enabled: bool, - /// Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers. +pub struct PermissionsLocationsAddToolApprovalDetailsFactory { + /// Optional factory operation name or canonical approval key; when omitted, the approval covers all factory operations. #[serde(skip_serializing_if = "Option::is_none")] - pub source: Option, + pub approval_key: Option, + /// Approval covering factory operations. + pub kind: PermissionsLocationsAddToolApprovalDetailsFactoryKind, } -/// Indicates whether the operation succeeded. +/// Location-persisted tool approval details for an extension's permission-gated capability access, keyed by extension name. /// ///
/// @@ -9017,27 +9606,31 @@ pub struct PermissionsSetApproveAllRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsSetApproveAllResult { - /// Whether the operation succeeded - pub success: bool, -} - -/// Toggles whether permission prompts should be bridged into session events for this client. -/// +pub struct PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess { + /// Extension name. + pub extension_name: String, + /// Approval covering an extension's request to access a permission-gated capability. + pub kind: PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccessKind, +} + +/// Location-scoped tool approval to persist. +/// ///
/// /// **Experimental.** This type is part of an experimental wire-protocol surface /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsSetRequiredRequest { - /// Whether the client wants `permission.requested` events bridged from the session-owned permission service. CLI clients that render prompt UI set this to `true` for as long as their listener is mounted; headless callers leave it unset (the default is `false`). - pub required: bool, +pub struct PermissionLocationAddToolApprovalParams { + /// Tool approval to persist and apply + pub approval: PermissionsLocationsAddToolApprovalDetails, + /// Location key (git root or cwd) to persist the approval to + pub location_key: String, } -/// Indicates whether the operation succeeded. +/// Working directory to load persisted location permissions for. /// ///
/// @@ -9047,12 +9640,12 @@ pub struct PermissionsSetRequiredRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsSetRequiredResult { - /// Whether the operation succeeded - pub success: bool, +pub struct PermissionLocationApplyParams { + /// Working directory whose persisted location permissions should be applied + pub working_directory: String, } -/// Indicates whether the operation succeeded. +/// Summary of persisted location permissions applied to the session. /// ///
/// @@ -9062,12 +9655,22 @@ pub struct PermissionsSetRequiredResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionsUrlsSetUnrestrictedModeResult { - /// Whether the operation succeeded - pub success: bool, +pub struct PermissionLocationApplyResult { + /// Number of persisted allowed directories added to the live path manager + pub applied_directory_count: i64, + /// Number of location-scoped rules added to the live permission service + pub applied_rule_count: i64, + /// Location-scoped rules applied to the live permission service + pub applied_rules: Vec, + /// Whether a different location was applied since the previous apply call + pub changed: bool, + /// Location key used in the location-permissions store + pub location_key: String, + /// Whether the location is a git repo or directory + pub location_type: PermissionLocationType, } -/// Whether the URL-permission policy should run in unrestricted mode. +/// Working directory to resolve into a location-permissions key. /// ///
/// @@ -9077,12 +9680,12 @@ pub struct PermissionsUrlsSetUnrestrictedModeResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PermissionUrlsSetUnrestrictedModeParams { - /// Whether to allow access to all URLs without prompting. Toggles the runtime's URL-permission policy in place. - pub enabled: bool, +pub struct PermissionLocationResolveParams { + /// Working directory whose permission location should be resolved + pub working_directory: String, } -/// Optional message to echo back to the caller. +/// Resolved location-permissions key and type. /// ///
/// @@ -9092,13 +9695,14 @@ pub struct PermissionUrlsSetUnrestrictedModeParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PingRequest { - /// Optional message to echo back - #[serde(skip_serializing_if = "Option::is_none")] - pub message: Option, +pub struct PermissionLocationResolveResult { + /// Location key used in the location-permissions store + pub location_key: String, + /// Whether the location is a git repo or directory + pub location_type: PermissionLocationType, } -/// Server liveness response, including the echoed message, current server timestamp, and protocol version. +/// Directory path to add to the session's allowed directories. /// ///
/// @@ -9108,16 +9712,12 @@ pub struct PingRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PingResult { - /// Echoed message (or default greeting) - pub message: String, - /// Server protocol version number - pub protocol_version: i64, - /// ISO 8601 timestamp when the server handled the ping - pub timestamp: String, +pub struct PermissionPathsAddParams { + /// Directory to add to the allow-list. The runtime resolves and validates the path before adding. + pub path: String, } -/// Existence, contents, and resolved path of the session plan file. +/// Path to evaluate against the session's allowed directories. /// ///
/// @@ -9127,16 +9727,12 @@ pub struct PingResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PlanReadResult { - /// The content of the plan file, or null if it does not exist - pub content: Option, - /// Whether the plan file exists in the workspace - pub exists: bool, - /// Absolute file path of the plan file, or null if workspace is not enabled - pub path: Option, +pub struct PermissionPathsAllowedCheckParams { + /// Path to check against the session's allowed directories + pub path: String, } -/// A single todo row read from the session SQL `todos` table. All fields are optional because the SQL schema is best-effort and the agent may not have populated every column. +/// Indicates whether the supplied path is within the session's allowed directories. /// ///
/// @@ -9146,22 +9742,12 @@ pub struct PlanReadResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PlanSqlTodosRow { - /// Todo description. - #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, - /// Todo identifier. - #[serde(skip_serializing_if = "Option::is_none")] - pub id: Option, - /// Todo status. - #[serde(skip_serializing_if = "Option::is_none")] - pub status: Option, - /// Todo title. - #[serde(skip_serializing_if = "Option::is_none")] - pub title: Option, +pub struct PermissionPathsAllowedCheckResult { + /// Whether the path is within the session's allowed directories + pub allowed: bool, } -/// Todo rows read from the session SQL database. Empty when no session database is available. +/// If specified, replaces the session's path-permission policy. The runtime constructs the appropriate PathManager based on these inputs (rooted at the session's working directory). Omit to leave the current path policy unchanged. /// ///
/// @@ -9171,12 +9757,22 @@ pub struct PlanSqlTodosRow { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PlanReadSqlTodosResult { - /// Rows from the session SQL todos table, ordered by creation time and id. - pub rows: Vec, +pub struct PermissionPathsConfig { + /// Additional directories to allow tool access to (in addition to the session's working directory). When `unrestricted` is true, these are still pre-populated on the UnrestrictedPathManager so they remain visible via getDirectories() (e.g. for @-mention completion). + #[serde(skip_serializing_if = "Option::is_none")] + pub additional_directories: Option>, + /// Whether to include the system temp directory in the allowed list (defaults to true). Ignored when `unrestricted` is true. + #[serde(skip_serializing_if = "Option::is_none")] + pub include_temp_directory: Option, + /// If true, the runtime allows access to all paths without prompting. Equivalent to constructing an UnrestrictedPathManager. + #[serde(skip_serializing_if = "Option::is_none")] + pub unrestricted: Option, + /// Workspace root path (special-cased to be allowed even before the directory exists). Ignored when `unrestricted` is true. + #[serde(skip_serializing_if = "Option::is_none")] + pub workspace_path: Option, } -/// A single dependency edge read from the session SQL `todo_deps` table, indicating that one todo must complete before another. +/// Snapshot of the session's allow-listed directories and primary working directory. /// ///
/// @@ -9186,14 +9782,14 @@ pub struct PlanReadSqlTodosResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PlanSqlTodoDependency { - /// ID of the todo it depends on. - pub depends_on: String, - /// ID of the todo that has the dependency. - pub todo_id: String, +pub struct PermissionPathsList { + /// All directories currently allowed for tool access on this session. + pub directories: Vec, + /// The primary working directory for this session. + pub primary: String, } -/// Todo rows + dependency edges read from the session SQL database. +/// Directory path to set as the session's new primary working directory. /// ///
/// @@ -9203,14 +9799,12 @@ pub struct PlanSqlTodoDependency { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PlanReadSqlTodosWithDependenciesResult { - /// Edges from the session SQL todo_deps table. Empty when no database, no todo_deps table, or the SELECT failed. Read independently from `rows`, so a broken todo_deps table does not affect the rows result and vice versa. - pub dependencies: Vec, - /// Rows from the session SQL todos table, ordered by creation time and id. Empty when no database, no todos table, or the SELECT failed. - pub rows: Vec, +pub struct PermissionPathsUpdatePrimaryParams { + /// Directory to set as the new primary working directory for the session's permission policy. + pub path: String, } -/// Replacement contents to write to the session plan file. +/// Path to evaluate against the session's workspace (primary) directory. /// ///
/// @@ -9220,12 +9814,12 @@ pub struct PlanReadSqlTodosWithDependenciesResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PlanUpdateRequest { - /// The new content for the plan file - pub content: String, +pub struct PermissionPathsWorkspaceCheckParams { + /// Path to check against the session workspace directory + pub path: String, } -/// Session plugin metadata, with name, marketplace, optional version, and enabled state. +/// Indicates whether the supplied path is within the session's workspace directory. /// ///
/// @@ -9235,19 +9829,12 @@ pub struct PlanUpdateRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct Plugin { - /// Whether the plugin is currently enabled - pub enabled: bool, - /// Marketplace the plugin came from - pub marketplace: String, - /// Plugin name - pub name: String, - /// Installed version - #[serde(skip_serializing_if = "Option::is_none")] - pub version: Option, +pub struct PermissionPathsWorkspaceCheckResult { + /// Whether the path is within the session workspace directory + pub allowed: bool, } -/// Result of installing a plugin. +/// Notification payload describing the permission prompt that the client just rendered. /// ///
/// @@ -9257,20 +9844,12 @@ pub struct Plugin { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PluginInstallResult { - /// Set when the install path is deprecated (e.g. direct repo / URL / local installs). Callers should surface this to end users. - #[serde(skip_serializing_if = "Option::is_none")] - pub deprecation_warning: Option, - /// The newly installed plugin's metadata - pub plugin: InstalledPluginInfo, - /// Optional post-install message provided by the plugin (e.g. setup instructions) - #[serde(skip_serializing_if = "Option::is_none")] - pub post_install_message: Option, - /// Number of skills discovered and installed from the plugin - pub skills_installed: i64, +pub struct PermissionPromptShownNotification { + /// Human-readable description of the prompt the user is being asked to approve. Used by the runtime to fire the registered `permission_prompt` notification hook (e.g. terminal bell, desktop notification). + pub message: String, } -/// Plugins installed for the session, with their enabled state and version metadata. +/// Indicates whether the permission decision was applied; false when the request was already resolved. /// ///
/// @@ -9280,12 +9859,12 @@ pub struct PluginInstallResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PluginList { - /// Installed plugins - pub plugins: Vec, +pub struct PermissionRequestResult { + /// Whether the permission request was handled successfully + pub success: bool, } -/// Plugins installed in user/global state. +/// If specified, replaces the session's approved/denied permission rules. Omit to leave the current rules unchanged. /// ///
/// @@ -9295,12 +9874,14 @@ pub struct PluginList { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PluginListResult { - /// Installed plugins - pub plugins: Vec, +pub struct PermissionRulesSet { + /// Rules that auto-approve matching requests + pub approved: Vec, + /// Rules that auto-deny matching requests + pub denied: Vec, } -/// Plugin names (or specs) to disable. +/// Source descriptor for a `session.permissions.configure` content-exclusion rule, with source name and type. /// ///
/// @@ -9310,12 +9891,12 @@ pub struct PluginListResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PluginsDisableRequest { - /// Plugin names or "plugin@marketplace" specs to disable. Unknown names are ignored. Non-marketplace direct installs cannot be disabled via this API; uninstall them instead. Plugin-owned MCP servers are stopped in active sessions immediately; other plugin contributions remain available until each session reloads plugins. - pub names: Vec, +pub struct PermissionsConfigureAdditionalContentExclusionPolicyRuleSource { + pub name: String, + pub r#type: String, } -/// Plugin names (or specs) to enable. +/// Single content-exclusion rule supplied to `session.permissions.configure`, with paths, match conditions, and source. /// ///
/// @@ -9325,12 +9906,17 @@ pub struct PluginsDisableRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PluginsEnableRequest { - /// Plugin names or "plugin@marketplace" specs to enable. Unknown names are ignored. Non-marketplace direct installs are always enabled and cannot be toggled via this API. - pub names: Vec, +pub struct PermissionsConfigureAdditionalContentExclusionPolicyRule { + #[serde(skip_serializing_if = "Option::is_none")] + pub if_any_match: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub if_none_match: Option>, + pub paths: Vec, + /// Source descriptor for a `session.permissions.configure` content-exclusion rule, with source name and type. + pub source: PermissionsConfigureAdditionalContentExclusionPolicyRuleSource, } -/// Plugin source and optional working directory for relative-path resolution. +/// Content-exclusion policy supplied to `session.permissions.configure`, with rules, last-updated data, and scope. /// ///
/// @@ -9340,15 +9926,15 @@ pub struct PluginsEnableRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PluginsInstallRequest { - /// Plugin install spec. Accepts the same forms as the CLI: "plugin@marketplace" (marketplace install), "owner/repo" or "owner/repo:subpath" (GitHub direct), an http/https/ssh URL, or a local path. Direct (non-marketplace) installs are deprecated and will produce a deprecationWarning in the result. - pub source: String, - /// Working directory used to resolve relative local paths in `source`. Defaults to the server's current working directory. - #[serde(skip_serializing_if = "Option::is_none")] - pub working_directory: Option, +pub struct PermissionsConfigureAdditionalContentExclusionPolicy { + #[serde(rename = "last_updated_at")] + pub last_updated_at: serde_json::Value, + pub rules: Vec, + /// Allowed values for the `PermissionsConfigureAdditionalContentExclusionPolicyScope` enumeration. + pub scope: PermissionsConfigureAdditionalContentExclusionPolicyScope, } -/// Marketplace source and optional working directory for relative-path resolution. +/// If specified, replaces the session's URL-permission policy. The runtime constructs a fresh DefaultUrlManager based on these inputs. Omit to leave the current URL policy unchanged. /// ///
/// @@ -9358,15 +9944,16 @@ pub struct PluginsInstallRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PluginsMarketplacesAddRequest { - /// Marketplace source. Accepts the same forms as the CLI: "owner/repo" or "owner/repo#ref" (GitHub), an http/https/ssh URL (optionally with #ref), a git scp-style URL (user@host:path), or a local path. The marketplace's own name (from its manifest) is used as the registration key. - pub source: String, - /// Working directory used to resolve relative local paths in `source`. Defaults to the server's current working directory. +pub struct PermissionUrlsConfig { + /// Initial list of allowed URL/domain patterns. Patterns may include path components. Ignored when `unrestricted` is true. #[serde(skip_serializing_if = "Option::is_none")] - pub working_directory: Option, + pub initial_allowed: Option>, + /// If true, the runtime allows access to all URLs without prompting. Initial allow-list is ignored when this is true. + #[serde(skip_serializing_if = "Option::is_none")] + pub unrestricted: Option, } -/// Name of the marketplace whose plugin catalog to fetch. +/// Patch of permission policy fields to apply (omit a field to leave it unchanged). /// ///
/// @@ -9376,12 +9963,29 @@ pub struct PluginsMarketplacesAddRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PluginsMarketplacesBrowseRequest { - /// Marketplace name to browse - pub name: String, +pub struct PermissionsConfigureParams { + /// If specified, replaces the host-supplied GitHub Content Exclusion policies on the session (combined with natively-discovered policies when evaluating tool/file access). Omit to leave the current policies unchanged. + #[serde(skip_serializing_if = "Option::is_none")] + pub additional_content_exclusion_policies: + Option>, + /// If specified, sets whether path/URL read permission requests are auto-approved. Omit to leave the current value unchanged. + #[serde(skip_serializing_if = "Option::is_none")] + pub approve_all_read_permission_requests: Option, + /// If specified, sets whether tool permission requests are auto-approved without prompting. Omit to leave the current value unchanged. + #[serde(skip_serializing_if = "Option::is_none")] + pub approve_all_tool_permission_requests: Option, + /// If specified, replaces the session's path-permission policy. The runtime constructs the appropriate PathManager based on these inputs (rooted at the session's working directory). Omit to leave the current path policy unchanged. + #[serde(skip_serializing_if = "Option::is_none")] + pub paths: Option, + /// If specified, replaces the session's approved/denied permission rules. Omit to leave the current rules unchanged. + #[serde(skip_serializing_if = "Option::is_none")] + pub rules: Option, + /// If specified, replaces the session's URL-permission policy. The runtime constructs a fresh DefaultUrlManager based on these inputs. Omit to leave the current URL policy unchanged. + #[serde(skip_serializing_if = "Option::is_none")] + pub urls: Option, } -/// Optional marketplace name; omit to refresh all. +/// Indicates whether the operation succeeded. /// ///
/// @@ -9391,13 +9995,12 @@ pub struct PluginsMarketplacesBrowseRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PluginsMarketplacesRefreshRequest { - /// Marketplace name to refresh. When omitted, every registered marketplace is refreshed. - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, +pub struct PermissionsConfigureResult { + /// Whether the operation succeeded + pub success: bool, } -/// Name of the marketplace to remove and an optional force flag. +/// Indicates whether the operation succeeded. /// ///
/// @@ -9407,15 +10010,12 @@ pub struct PluginsMarketplacesRefreshRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PluginsMarketplacesRemoveRequest { - /// When true, also uninstall every plugin sourced from this marketplace. When false (default), removal is a no-op if any plugin from this marketplace is installed and the dependent plugin names are returned in the result. - #[serde(skip_serializing_if = "Option::is_none")] - pub force: Option, - /// Marketplace name to remove - pub name: String, +pub struct PermissionsFolderTrustAddTrustedResult { + /// Whether the operation succeeded + pub success: bool, } -/// Optional flags controlling which side effects the reload performs. +/// No parameters. /// ///
/// @@ -9425,25 +10025,9 @@ pub struct PluginsMarketplacesRemoveRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PluginsReloadRequest { - /// When true, skip repo-level hooks during the hook reload. Use before folder trust is confirmed; load them post-trust via `sessions.loadDeferredRepoHooks`. - #[serde(skip_serializing_if = "Option::is_none")] - pub defer_repo_hooks: Option, - /// Re-run custom-agent discovery after refreshing plugins. Defaults to true. - #[serde(skip_serializing_if = "Option::is_none")] - pub reload_custom_agents: Option, - /// Re-discover and relaunch subprocess extensions (including plugin-shipped extensions) after refreshing plugins. Defaults to true. Has no effect when the session has no active extension controller (e.g. extensions were not requested for the session). - #[serde(skip_serializing_if = "Option::is_none")] - pub reload_extensions: Option, - /// Re-load user, plugin, and (subject to `deferRepoHooks`) repo hooks. Defaults to true. Has no effect when the host has not registered a hook reloader (e.g. remote sessions). - #[serde(skip_serializing_if = "Option::is_none")] - pub reload_hooks: Option, - /// Reload MCP server connections after refreshing plugins. Defaults to true. - #[serde(skip_serializing_if = "Option::is_none")] - pub reload_mcp: Option, -} +pub struct PermissionsGetAllowAllRequest {} -/// Name (or spec) of the plugin to uninstall. +/// Indicates whether the operation succeeded. /// ///
/// @@ -9453,15 +10037,12 @@ pub struct PluginsReloadRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PluginsUninstallRequest { - /// Stable source identity for a direct (non-marketplace) install. Disambiguates uninstall when multiple installed plugins share the same name. - #[serde(skip_serializing_if = "Option::is_none")] - pub direct_source_id: Option, - /// Plugin name or "plugin@marketplace" spec to uninstall. When ambiguous, prefer the fully-qualified spec. - pub name: String, +pub struct PermissionsLocationsAddToolApprovalResult { + /// Whether the operation succeeded + pub success: bool, } -/// Name (or spec) of the plugin to update. +/// Scope and add/remove instructions for modifying session- or location-scoped permission rules. /// ///
/// @@ -9471,12 +10052,21 @@ pub struct PluginsUninstallRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PluginsUpdateRequest { - /// Plugin name or "plugin@marketplace" spec to update. - pub name: String, +pub struct PermissionsModifyRulesParams { + /// Rules to add to the scope. Applied before `remove`/`removeAll`. + #[serde(skip_serializing_if = "Option::is_none")] + pub add: Option>, + /// Specific rules to remove from the scope. Ignored when `removeAll` is true. + #[serde(skip_serializing_if = "Option::is_none")] + pub remove: Option>, + /// When true, removes every rule currently in the scope (after any `add` is applied). Useful for clearing the location scope wholesale. + #[serde(skip_serializing_if = "Option::is_none")] + pub remove_all: Option, + /// Whether the change applies to ephemeral session-scoped rules (cleared at session end) or to location-scoped rules persisted via the location-permissions config file. + pub scope: PermissionsModifyRulesScope, } -/// Per-plugin result from updating all plugins, with versions, skills installed, success flag, and optional error. +/// Indicates whether the operation succeeded. /// ///
/// @@ -9486,28 +10076,12 @@ pub struct PluginsUpdateRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PluginUpdateAllEntry { - /// Error message (failure only) - #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, - /// Marketplace the plugin came from. Empty string ("") for direct installs. - pub marketplace: String, - /// Plugin name that was updated - pub name: String, - /// Version after the update, when available - #[serde(skip_serializing_if = "Option::is_none")] - pub new_version: Option, - /// Previously installed version, when available - #[serde(skip_serializing_if = "Option::is_none")] - pub previous_version: Option, - /// Number of skills installed after the update (success only) - #[serde(skip_serializing_if = "Option::is_none")] - pub skills_installed: Option, - /// Whether the update succeeded for this plugin +pub struct PermissionsModifyRulesResult { + /// Whether the operation succeeded pub success: bool, } -/// Result of updating all installed plugins. +/// Indicates whether the operation succeeded. /// ///
/// @@ -9517,12 +10091,12 @@ pub struct PluginUpdateAllEntry { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PluginUpdateAllResult { - /// Per-plugin update results in deterministic order. - pub results: Vec, +pub struct PermissionsNotifyPromptShownResult { + /// Whether the operation succeeded + pub success: bool, } -/// Result of updating a single plugin. +/// Indicates whether the operation succeeded. /// ///
/// @@ -9532,18 +10106,12 @@ pub struct PluginUpdateAllResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PluginUpdateResult { - /// Version after the update, when reported by the plugin manifest - #[serde(skip_serializing_if = "Option::is_none")] - pub new_version: Option, - /// Version that was previously installed, when available - #[serde(skip_serializing_if = "Option::is_none")] - pub previous_version: Option, - /// Number of skills discovered and installed after the update - pub skills_installed: i64, +pub struct PermissionsPathsAddResult { + /// Whether the operation succeeded + pub success: bool, } -/// A BYOK model definition referencing a named provider. +/// No parameters; returns the session's allow-listed directories. /// ///
/// @@ -9553,35 +10121,9 @@ pub struct PluginUpdateResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ProviderModelConfig { - /// Optional capability overrides (vision, tool_calls, reasoning, etc.). - #[serde(skip_serializing_if = "Option::is_none")] - pub capabilities: Option, - /// Provider-local model id, unique within its provider. The session-wide selection id (shown in the model list and passed to switchTo) is the provider-qualified `provider/id`. - pub id: String, - /// Maximum context window tokens for the model. - #[serde(skip_serializing_if = "Option::is_none")] - pub max_context_window_tokens: Option, - /// Maximum output tokens for the model. - #[serde(skip_serializing_if = "Option::is_none")] - pub max_output_tokens: Option, - /// Maximum prompt/input tokens for the model. - #[serde(skip_serializing_if = "Option::is_none")] - pub max_prompt_tokens: Option, - /// Well-known base model id used for behavior/capability/config lookup. Defaults to `id`. - #[serde(skip_serializing_if = "Option::is_none")] - pub model_id: Option, - /// Display name for model pickers. Defaults to the provider-qualified selection id (`provider/id`). - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, - /// Name of the NamedProviderConfig that serves this model. - pub provider: String, - /// The model name sent to the provider API for inference. Defaults to `id`. - #[serde(skip_serializing_if = "Option::is_none")] - pub wire_model: Option, -} +pub struct PermissionsPathsListRequest {} -/// BYOK providers and/or models to add to the session's registry at runtime. Both fields are optional; provide providers, models, or both. +/// Indicates whether the operation succeeded. /// ///
/// @@ -9591,16 +10133,12 @@ pub struct ProviderModelConfig { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ProviderAddRequest { - /// BYOK model definitions to register. Each must reference a provider that is already registered or included in this same call. Selection ids (`provider/id`) must be unique across the registry. - #[serde(skip_serializing_if = "Option::is_none")] - pub models: Option>, - /// Named BYOK provider connections to register, additive to any providers already in the registry. Each name must be unique across the registry and must not contain '/'. - #[serde(skip_serializing_if = "Option::is_none")] - pub providers: Option>, +pub struct PermissionsPathsUpdatePrimaryResult { + /// Whether the operation succeeded + pub success: bool, } -/// The selectable model entries synthesized for the models added by this call. +/// No parameters; returns currently-pending permission requests for the session. /// ///
/// @@ -9610,12 +10148,9 @@ pub struct ProviderAddRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ProviderAddResult { - /// Synthesized selectable model entries for the newly added BYOK models, each under its provider-qualified selection id (`provider/id`). Empty when only providers were added. - pub models: Vec, -} +pub struct PermissionsPendingRequestsRequest {} -/// Custom model-provider configuration (BYOK). +/// Clears session-scoped tool permission approvals, and optionally the location-scoped ones. /// ///
/// @@ -9625,51 +10160,13 @@ pub struct ProviderAddResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ProviderConfig { - /// API key. Optional for local providers like Ollama. - #[serde(skip_serializing_if = "Option::is_none")] - pub api_key: Option, - /// Azure-specific provider options. - #[serde(skip_serializing_if = "Option::is_none")] - pub azure: Option, - /// API endpoint URL. - pub base_url: String, - /// Bearer token for authentication. Sets the Authorization header directly. Takes precedence over apiKey when both are set. +pub struct PermissionsResetSessionApprovalsRequest { + /// Whether location-scoped approvals are cleared too. Defaults to `true`. #[serde(skip_serializing_if = "Option::is_none")] - pub bearer_token: Option, - /// When true, the SDK client supplies bearer tokens on demand: the runtime calls the client-session `providerToken.getToken` callback before each request and applies the returned token as an `Authorization: Bearer ` header. This is the bearer/OAuth scheme used by Azure AD / managed-identity tokens and provider OAuth access tokens (including Anthropic's), not a provider-specific API-key header such as Anthropic's `x-api-key`. The token-acquiring function itself stays on the SDK side and is never serialized; only this flag crosses the wire. When set alongside `apiKey`/`bearerToken`, the callback takes precedence: the runtime applies the token returned by `providerToken.getToken` as the `Authorization: Bearer` header for each request and does not send the static credential. - #[serde(skip_serializing_if = "Option::is_none")] - pub has_bearer_token_provider: Option, - /// Custom HTTP headers to include in all outbound requests to the provider. - #[serde(skip_serializing_if = "Option::is_none")] - pub headers: Option>, - /// Maximum context window tokens for the model. - #[serde(skip_serializing_if = "Option::is_none")] - pub max_context_window_tokens: Option, - /// Maximum output tokens for the model. - #[serde(skip_serializing_if = "Option::is_none")] - pub max_output_tokens: Option, - /// Maximum prompt/input tokens for the model. - #[serde(skip_serializing_if = "Option::is_none")] - pub max_prompt_tokens: Option, - /// Well-known model ID used for capability lookup. When set, agent behavior config and token limits are inferred from this model. - #[serde(skip_serializing_if = "Option::is_none")] - pub model_id: Option, - /// Provider transport. Defaults to "http". - #[serde(skip_serializing_if = "Option::is_none")] - pub transport: Option, - /// Provider type. Defaults to "openai" for generic OpenAI-compatible APIs. - #[serde(skip_serializing_if = "Option::is_none")] - pub r#type: Option, - /// Wire API format (openai/azure only). Defaults to "completions". - #[serde(skip_serializing_if = "Option::is_none")] - pub wire_api: Option, - /// The model identifier sent to the provider API for inference (the "wire" model), as opposed to modelId which is the well-known base. - #[serde(skip_serializing_if = "Option::is_none")] - pub wire_model: Option, + pub include_location: Option, } -/// Short-lived, rotating credential the caller must send on every request, in addition to `apiKey` if one is present. Omitted when the endpoint does not require one. +/// Indicates whether the operation succeeded. /// ///
/// @@ -9679,20 +10176,12 @@ pub struct ProviderConfig { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ProviderSessionToken { - /// When the token expires, if known. Callers should refresh by calling `getEndpoint` again before this time, or reactively on any 401/403 response from `baseUrl`. - #[serde(skip_serializing_if = "Option::is_none")] - pub expires_at: Option, - /// HTTP header name the token must be sent under. - pub header: String, - /// The model the token is bound to, when applicable. When set, the token is only valid for requests against this model. - #[serde(skip_serializing_if = "Option::is_none")] - pub model: Option, - /// The short-lived token value. - pub token: String, +pub struct PermissionsResetSessionApprovalsResult { + /// Whether the operation succeeded + pub success: bool, } -/// A snapshot of the provider endpoint the session is currently configured to talk to. +/// Allow-all mode to apply for the session. /// ///
/// @@ -9702,28 +10191,22 @@ pub struct ProviderSessionToken { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ProviderEndpoint { - /// A credential the caller should use with this endpoint. Omitted only when the endpoint accepts unauthenticated requests. +pub struct PermissionsSetAllowAllRequest { + /// Legacy full allow-all toggle. Prefer `mode`; when `mode` is omitted, `enabled: true` is treated as `mode: "on"` and any other value is treated as `mode: "off"`. #[serde(skip_serializing_if = "Option::is_none")] - pub api_key: Option, - /// Base URL to pass to the LLM client library. - pub base_url: String, - /// HTTP headers the caller must include on every outbound request. - pub headers: HashMap, - /// Short-lived, rotating credential the caller must send on every request, in addition to `apiKey` if one is present. Omitted when the endpoint does not require one. + pub enabled: Option, + /// Allow-all mode to apply. `on` enables full allow-all; `auto` enables advisory LLM auto-approval; `off` disables both. #[serde(skip_serializing_if = "Option::is_none")] - pub session_token: Option, - /// Transport to be used for provider requests. + pub mode: Option, + /// Optional model id for the `auto` mode auto-approval LLM judging. Only meaningful when `mode` is `auto`; ignored otherwise. When omitted, the session resolves a default judge model: `gpt-5.5` for CAPI sessions and the session's active model for BYOK sessions. #[serde(skip_serializing_if = "Option::is_none")] - pub transport: Option, - /// Provider family. Matches the `type` field of a BYOK provider config. - pub r#type: ProviderEndpointType, - /// Wire API to be used, when required for the provider type. + pub model: Option, + /// Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers. #[serde(skip_serializing_if = "Option::is_none")] - pub wire_api: Option, + pub source: Option, } -/// Optional model identifier to scope the endpoint snapshot to. +/// Allow-all toggle for tool permission requests, with an optional telemetry source. /// ///
/// @@ -9733,13 +10216,15 @@ pub struct ProviderEndpoint { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ProviderGetEndpointRequest { - /// Model identifier the caller intends to use against the returned endpoint. Used to pick the correct wire shape. Omit to use whichever model the session is currently using. +pub struct PermissionsSetApproveAllRequest { + /// Whether to auto-approve all tool permission requests + pub enabled: bool, + /// Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers. #[serde(skip_serializing_if = "Option::is_none")] - pub model_id: Option, + pub source: Option, } -/// Asks the SDK client to acquire a bearer token for a BYOK provider whose config set `hasBearerTokenProvider: true`. Issued by the runtime before each outbound model request; the runtime does no caching, so this is sent once per request. +/// Indicates whether the operation succeeded. /// ///
/// @@ -9749,14 +10234,12 @@ pub struct ProviderGetEndpointRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ProviderTokenAcquireRequest { - /// Target session identifier - pub session_id: SessionId, - /// Name of the BYOK provider needing a token. For the legacy whole-session `provider` this is the implicit provider name; for named providers it is `NamedProviderConfig.name`. - pub provider_name: String, +pub struct PermissionsSetApproveAllResult { + /// Whether the operation succeeded + pub success: bool, } -/// A bearer token supplied by the SDK client for a BYOK provider. The runtime sets it as `Authorization: Bearer ` on the outbound request and does no caching; the SDK consumer owns token caching and refresh. +/// Toggles whether permission prompts should be bridged into session events for this client. /// ///
/// @@ -9766,12 +10249,12 @@ pub struct ProviderTokenAcquireRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ProviderTokenAcquireResult { - /// The bearer token value (without the `Bearer ` prefix). - pub token: String, +pub struct PermissionsSetRequiredRequest { + /// Whether the client wants `permission.requested` events bridged from the session-owned permission service. CLI clients that render prompt UI set this to `true` for as long as their listener is mounted; headless callers leave it unset (the default is `false`). + pub required: bool, } -/// Blob attachment with inline base64-encoded data +/// Indicates whether the operation succeeded. /// ///
/// @@ -9781,19 +10264,12 @@ pub struct ProviderTokenAcquireResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PushAttachmentBlob { - /// Base64-encoded content - pub data: String, - /// User-facing display name for the attachment - #[serde(skip_serializing_if = "Option::is_none")] - pub display_name: Option, - /// MIME type of the inline data - pub mime_type: String, - /// Attachment type discriminator - pub r#type: PushAttachmentBlobType, +pub struct PermissionsSetRequiredResult { + /// Whether the operation succeeded + pub success: bool, } -/// Directory attachment +/// Indicates whether the operation succeeded. /// ///
/// @@ -9803,16 +10279,12 @@ pub struct PushAttachmentBlob { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PushAttachmentDirectory { - /// User-facing display name for the attachment - pub display_name: String, - /// Absolute directory path - pub path: String, - /// Attachment type discriminator - pub r#type: PushAttachmentDirectoryType, +pub struct PermissionsUrlsSetUnrestrictedModeResult { + /// Whether the operation succeeded + pub success: bool, } -/// Optional line range to scope the attachment to a specific section of the file +/// Whether the URL-permission policy should run in unrestricted mode. /// ///
/// @@ -9822,14 +10294,12 @@ pub struct PushAttachmentDirectory { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PushAttachmentFileLineRange { - /// End line number (1-based, inclusive) - pub end: i64, - /// Start line number (1-based) - pub start: i64, +pub struct PermissionUrlsSetUnrestrictedModeParams { + /// Whether to allow access to all URLs without prompting. Toggles the runtime's URL-permission policy in place. + pub enabled: bool, } -/// File attachment +/// Optional message to echo back to the caller. /// ///
/// @@ -9839,19 +10309,13 @@ pub struct PushAttachmentFileLineRange { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PushAttachmentFile { - /// User-facing display name for the attachment - pub display_name: String, - /// Optional line range to scope the attachment to a specific section of the file +pub struct PingRequest { + /// Optional message to echo back #[serde(skip_serializing_if = "Option::is_none")] - pub line_range: Option, - /// Absolute file path - pub path: String, - /// Attachment type discriminator - pub r#type: PushAttachmentFileType, + pub message: Option, } -/// Pointer to a GitHub repository. +/// Server liveness response, including the echoed message, current server timestamp, and protocol version. /// ///
/// @@ -9861,17 +10325,16 @@ pub struct PushAttachmentFile { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PushGitHubRepoRef { - /// Numeric GitHub repository id - #[serde(skip_serializing_if = "Option::is_none")] - pub id: Option, - /// Repository name (without owner) - pub name: String, - /// Repository owner login (user or organization) - pub owner: String, +pub struct PingResult { + /// Echoed message (or default greeting) + pub message: String, + /// Server protocol version number + pub protocol_version: i64, + /// ISO 8601 timestamp when the server handled the ping + pub timestamp: String, } -/// Pointer to a GitHub Actions job. +/// Existence, contents, and resolved path of the session plan file. /// ///
/// @@ -9881,25 +10344,16 @@ pub struct PushGitHubRepoRef { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PushAttachmentGitHubActionsJob { - /// Terminal conclusion of the job when finished (e.g., success, failure, cancelled). Absent for in-progress jobs. - #[serde(skip_serializing_if = "Option::is_none")] - pub conclusion: Option, - /// Job id within the workflow run - pub job_id: i64, - /// Display name of the job - pub job_name: String, - /// Repository the workflow run belongs to - pub repo: PushGitHubRepoRef, - /// Attachment type discriminator - pub r#type: PushAttachmentGitHubActionsJobType, - /// URL to the job on GitHub - pub url: String, - /// Display name of the workflow the job ran in - pub workflow_name: String, +pub struct PlanReadResult { + /// The content of the plan file, or null if it does not exist + pub content: Option, + /// Whether the plan file exists in the workspace + pub exists: bool, + /// Absolute file path of the plan file, or null if workspace is not enabled + pub path: Option, } -/// Pointer to a GitHub commit. +/// A single todo row read from the session SQL `todos` table. All fields are optional because the SQL schema is best-effort and the agent may not have populated every column. /// ///
/// @@ -9909,20 +10363,22 @@ pub struct PushAttachmentGitHubActionsJob { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PushAttachmentGitHubCommit { - /// First line of the commit message - pub message: String, - /// Full commit SHA - pub oid: String, - /// Repository the commit belongs to - pub repo: PushGitHubRepoRef, - /// Attachment type discriminator - pub r#type: PushAttachmentGitHubCommitType, - /// URL to the commit on GitHub - pub url: String, +pub struct PlanSqlTodosRow { + /// Todo description. + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + /// Todo identifier. + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + /// Todo status. + #[serde(skip_serializing_if = "Option::is_none")] + pub status: Option, + /// Todo title. + #[serde(skip_serializing_if = "Option::is_none")] + pub title: Option, } -/// Pointer to a file in a GitHub repository at a specific ref. +/// Todo rows read from the session SQL database. Empty when no session database is available. /// ///
/// @@ -9932,20 +10388,12 @@ pub struct PushAttachmentGitHubCommit { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PushAttachmentGitHubFile { - /// Repository-relative path to the file - pub path: String, - /// Git ref the file is read at (branch, tag, or commit SHA) - pub r#ref: String, - /// Repository the file lives in - pub repo: PushGitHubRepoRef, - /// Attachment type discriminator - pub r#type: PushAttachmentGitHubFileType, - /// URL to the file on GitHub - pub url: String, +pub struct PlanReadSqlTodosResult { + /// Rows from the session SQL todos table, ordered by creation time and id. + pub rows: Vec, } -/// One side of a file diff (head or base) +/// A single dependency edge read from the session SQL `todo_deps` table, indicating that one todo must complete before another. /// ///
/// @@ -9955,16 +10403,14 @@ pub struct PushAttachmentGitHubFile { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PushAttachmentGitHubFileDiffSide { - /// Repository-relative path to the file - pub path: String, - /// Git ref (branch, tag, or commit SHA) the file is read at - pub r#ref: String, - /// Repository the file lives in - pub repo: PushGitHubRepoRef, +pub struct PlanSqlTodoDependency { + /// ID of the todo it depends on. + pub depends_on: String, + /// ID of the todo that has the dependency. + pub todo_id: String, } -/// Pointer to a single-file diff. At least one of `head` and `base` must be present. +/// Todo rows + dependency edges read from the session SQL database. /// ///
/// @@ -9974,20 +10420,14 @@ pub struct PushAttachmentGitHubFileDiffSide { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PushAttachmentGitHubFileDiff { - /// File location on the base side of the diff. Absent for additions. - #[serde(skip_serializing_if = "Option::is_none")] - pub base: Option, - /// File location on the head side of the diff. Absent for deletions. - #[serde(skip_serializing_if = "Option::is_none")] - pub head: Option, - /// Attachment type discriminator - pub r#type: PushAttachmentGitHubFileDiffType, - /// URL to the diff on GitHub (e.g., a commit, compare, or PR-file URL) - pub url: String, +pub struct PlanReadSqlTodosWithDependenciesResult { + /// Edges from the session SQL todo_deps table. Empty when no database, no todo_deps table, or the SELECT failed. Read independently from `rows`, so a broken todo_deps table does not affect the rows result and vice versa. + pub dependencies: Vec, + /// Rows from the session SQL todos table, ordered by creation time and id. Empty when no database, no todos table, or the SELECT failed. + pub rows: Vec, } -/// GitHub issue, pull request, or discussion reference +/// Replacement contents to write to the session plan file. /// ///
/// @@ -9997,22 +10437,12 @@ pub struct PushAttachmentGitHubFileDiff { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PushAttachmentGitHubReference { - /// Issue, pull request, or discussion number - pub number: i64, - /// Type of GitHub reference - pub reference_type: PushAttachmentGitHubReferenceType, - /// Current state of the referenced item (e.g., open, closed, merged) - pub state: String, - /// Title of the referenced item - pub title: String, - /// Attachment type discriminator - pub r#type: PushAttachmentGitHubReferenceType, - /// URL to the referenced item on GitHub - pub url: String, +pub struct PlanUpdateRequest { + /// The new content for the plan file + pub content: String, } -/// Pointer to a GitHub release. +/// Session plugin metadata, with name, marketplace, optional version, and enabled state. /// ///
/// @@ -10022,20 +10452,19 @@ pub struct PushAttachmentGitHubReference { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PushAttachmentGitHubRelease { - /// Human-readable release name +pub struct Plugin { + /// Whether the plugin is currently enabled + pub enabled: bool, + /// Marketplace the plugin came from + pub marketplace: String, + /// Plugin name pub name: String, - /// Repository the release belongs to - pub repo: PushGitHubRepoRef, - /// Git tag the release is anchored to - pub tag_name: String, - /// Attachment type discriminator - pub r#type: PushAttachmentGitHubReleaseType, - /// URL to the release on GitHub - pub url: String, + /// Installed version + #[serde(skip_serializing_if = "Option::is_none")] + pub version: Option, } -/// Pointer to a GitHub repository. +/// Result of installing a plugin. /// ///
/// @@ -10045,22 +10474,20 @@ pub struct PushAttachmentGitHubRelease { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PushAttachmentGitHubRepository { - /// Short description of the repository +pub struct PluginInstallResult { + /// Set when the install path is deprecated (e.g. direct repo / URL / local installs). Callers should surface this to end users. #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, - /// Git ref this attachment is anchored at (branch, tag, or commit). When absent the default branch is implied. + pub deprecation_warning: Option, + /// The newly installed plugin's metadata + pub plugin: InstalledPluginInfo, + /// Optional post-install message provided by the plugin (e.g. setup instructions) #[serde(skip_serializing_if = "Option::is_none")] - pub r#ref: Option, - /// Repository pointer - pub repo: PushGitHubRepoRef, - /// Attachment type discriminator - pub r#type: PushAttachmentGitHubRepositoryType, - /// URL to the repository on GitHub - pub url: String, + pub post_install_message: Option, + /// Number of skills discovered and installed from the plugin + pub skills_installed: i64, } -/// Pointer to a line range inside a file in a GitHub repository. +/// Plugins installed for the session, with their enabled state and version metadata. /// ///
/// @@ -10070,22 +10497,12 @@ pub struct PushAttachmentGitHubRepository { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PushAttachmentGitHubSnippet { - /// Line range the snippet covers - pub line_range: PushAttachmentFileLineRange, - /// Repository-relative path to the file - pub path: String, - /// Git ref the file is read at (branch, tag, or commit SHA) - pub r#ref: String, - /// Repository the file lives in - pub repo: PushGitHubRepoRef, - /// Attachment type discriminator - pub r#type: PushAttachmentGitHubSnippetType, - /// URL to the snippet on GitHub (with line anchor) - pub url: String, +pub struct PluginList { + /// Installed plugins + pub plugins: Vec, } -/// One side of a tree comparison (head or base) +/// Plugins installed in user/global state. /// ///
/// @@ -10095,14 +10512,12 @@ pub struct PushAttachmentGitHubSnippet { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PushAttachmentGitHubTreeComparisonSide { - /// Repository the revision belongs to - pub repo: PushGitHubRepoRef, - /// Git revision (branch, tag, or commit SHA) - pub revision: String, +pub struct PluginListResult { + /// Installed plugins + pub plugins: Vec, } -/// Pointer to a comparison between two git revisions. +/// Plugin names (or specs) to disable. /// ///
/// @@ -10112,18 +10527,12 @@ pub struct PushAttachmentGitHubTreeComparisonSide { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PushAttachmentGitHubTreeComparison { - /// Base side of the comparison - pub base: PushAttachmentGitHubTreeComparisonSide, - /// Head side of the comparison - pub head: PushAttachmentGitHubTreeComparisonSide, - /// Attachment type discriminator - pub r#type: PushAttachmentGitHubTreeComparisonType, - /// URL to the comparison on GitHub - pub url: String, +pub struct PluginsDisableRequest { + /// Plugin names or "plugin@marketplace" specs to disable. Unknown names are ignored. Non-marketplace direct installs cannot be disabled via this API; uninstall them instead. Plugin-owned MCP servers are stopped in active sessions immediately; other plugin contributions remain available until each session reloads plugins. + pub names: Vec, } -/// Generic GitHub URL reference. +/// Plugin names (or specs) to enable. /// ///
/// @@ -10133,14 +10542,12 @@ pub struct PushAttachmentGitHubTreeComparison { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PushAttachmentGitHubUrl { - /// Attachment type discriminator - pub r#type: PushAttachmentGitHubUrlType, - /// URL to the GitHub resource - pub url: String, +pub struct PluginsEnableRequest { + /// Plugin names or "plugin@marketplace" specs to enable. Unknown names are ignored. Non-marketplace direct installs are always enabled and cannot be toggled via this API. + pub names: Vec, } -/// End position of the selection +/// Plugin source and optional working directory for relative-path resolution. /// ///
/// @@ -10150,14 +10557,15 @@ pub struct PushAttachmentGitHubUrl { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PushAttachmentSelectionDetailsEnd { - /// End character offset within the line (0-based) - pub character: i64, - /// End line number (0-based) - pub line: i64, +pub struct PluginsInstallRequest { + /// Plugin install spec. Accepts the same forms as the CLI: "plugin@marketplace" (marketplace install), "owner/repo" or "owner/repo:subpath" (GitHub direct), an http/https/ssh URL, or a local path. Direct (non-marketplace) installs are deprecated and will produce a deprecationWarning in the result. + pub source: String, + /// Working directory used to resolve relative local paths in `source`. Defaults to the server's current working directory. + #[serde(skip_serializing_if = "Option::is_none")] + pub working_directory: Option, } -/// Start position of the selection +/// Marketplace source and optional working directory for relative-path resolution. /// ///
/// @@ -10167,14 +10575,15 @@ pub struct PushAttachmentSelectionDetailsEnd { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PushAttachmentSelectionDetailsStart { - /// Start character offset within the line (0-based) - pub character: i64, - /// Start line number (0-based) - pub line: i64, +pub struct PluginsMarketplacesAddRequest { + /// Marketplace source. Accepts the same forms as the CLI: "owner/repo" or "owner/repo#ref" (GitHub), an http/https/ssh URL (optionally with #ref), a git scp-style URL (user@host:path), or a local path. The marketplace's own name (from its manifest) is used as the registration key. + pub source: String, + /// Working directory used to resolve relative local paths in `source`. Defaults to the server's current working directory. + #[serde(skip_serializing_if = "Option::is_none")] + pub working_directory: Option, } -/// Position range of the selection within the file +/// Name of the marketplace whose plugin catalog to fetch. /// ///
/// @@ -10184,14 +10593,12 @@ pub struct PushAttachmentSelectionDetailsStart { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PushAttachmentSelectionDetails { - /// End position of the selection - pub end: PushAttachmentSelectionDetailsEnd, - /// Start position of the selection - pub start: PushAttachmentSelectionDetailsStart, +pub struct PluginsMarketplacesBrowseRequest { + /// Marketplace name to browse + pub name: String, } -/// Code selection attachment from an editor +/// Optional marketplace name; omit to refresh all. /// ///
/// @@ -10201,20 +10608,13 @@ pub struct PushAttachmentSelectionDetails { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PushAttachmentSelection { - /// User-facing display name for the selection - pub display_name: String, - /// Absolute path to the file containing the selection - pub file_path: String, - /// Position range of the selection within the file - pub selection: PushAttachmentSelectionDetails, - /// The selected text content - pub text: String, - /// Attachment type discriminator - pub r#type: PushAttachmentSelectionType, +pub struct PluginsMarketplacesRefreshRequest { + /// Marketplace name to refresh. When omitted, every registered marketplace is refreshed. + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, } -/// Queued-command response indicating the host executed the command, with an optional flag to stop queue processing. +/// Name of the marketplace to remove and an optional force flag. /// ///
/// @@ -10224,15 +10624,15 @@ pub struct PushAttachmentSelection { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct QueuedCommandHandled { - /// The host actually executed the queued command. - pub handled: bool, - /// When true, the runtime will not process subsequent queued commands until a new request comes in. +pub struct PluginsMarketplacesRemoveRequest { + /// When true, also uninstall every plugin sourced from this marketplace. When false (default), removal is a no-op if any plugin from this marketplace is installed and the dependent plugin names are returned in the result. #[serde(skip_serializing_if = "Option::is_none")] - pub stop_processing_queue: Option, + pub force: Option, + /// Marketplace name to remove + pub name: String, } -/// Queued-command response indicating the host did not execute the command and the queue may continue. +/// Optional flags controlling which side effects the reload performs. /// ///
/// @@ -10242,12 +10642,25 @@ pub struct QueuedCommandHandled { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct QueuedCommandNotHandled { - /// The host did not execute the queued command. Unblocks the queue without claiming the command was processed (e.g. when the handler threw before completing). - pub handled: bool, +pub struct PluginsReloadRequest { + /// When true, skip repo-level hooks during the hook reload. Use before folder trust is confirmed; load them post-trust via `sessions.loadDeferredRepoHooks`. + #[serde(skip_serializing_if = "Option::is_none")] + pub defer_repo_hooks: Option, + /// Re-run custom-agent discovery after refreshing plugins. Defaults to true. + #[serde(skip_serializing_if = "Option::is_none")] + pub reload_custom_agents: Option, + /// Re-discover and relaunch subprocess extensions (including plugin-shipped extensions) after refreshing plugins. Defaults to true. Has no effect when the session has no active extension controller (e.g. extensions were not requested for the session). + #[serde(skip_serializing_if = "Option::is_none")] + pub reload_extensions: Option, + /// Re-load user, plugin, and (subject to `deferRepoHooks`) repo hooks. Defaults to true. Has no effect when the host has not registered a hook reloader (e.g. remote sessions). + #[serde(skip_serializing_if = "Option::is_none")] + pub reload_hooks: Option, + /// Reload MCP server connections after refreshing plugins. Defaults to true. + #[serde(skip_serializing_if = "Option::is_none")] + pub reload_mcp: Option, } -/// User-facing pending queue entry, with kind and display text for a queued message, slash command, or model change. +/// Name (or spec) of the plugin to uninstall. /// ///
/// @@ -10257,14 +10670,15 @@ pub struct QueuedCommandNotHandled { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct QueuePendingItems { - /// Human-readable text to display for this queue entry in the UI - pub display_text: String, - /// Whether this item is a queued user message or a queued slash command / model change - pub kind: QueuePendingItemsKind, +pub struct PluginsUninstallRequest { + /// Stable source identity for a direct (non-marketplace) install. Disambiguates uninstall when multiple installed plugins share the same name. + #[serde(skip_serializing_if = "Option::is_none")] + pub direct_source_id: Option, + /// Plugin name or "plugin@marketplace" spec to uninstall. When ambiguous, prefer the fully-qualified spec. + pub name: String, } -/// Snapshot of the session's pending queued items and immediate-steering messages. +/// Name (or spec) of the plugin to update. /// ///
/// @@ -10274,14 +10688,12 @@ pub struct QueuePendingItems { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct QueuePendingItemsResult { - /// Pending queued items in submission order. Includes user messages, queued slash commands, and queued model changes; omits internal system items. - pub items: Vec, - /// Display text for messages currently in the immediate steering queue (interjections sent during a running turn). - pub steering_messages: Vec, +pub struct PluginsUpdateRequest { + /// Plugin name or "plugin@marketplace" spec to update. + pub name: String, } -/// Indicates whether a user-facing pending item was removed. +/// Per-plugin result from updating all plugins, with versions, skills installed, success flag, and optional error. /// ///
/// @@ -10291,12 +10703,28 @@ pub struct QueuePendingItemsResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct QueueRemoveMostRecentResult { - /// True if a user-facing pending item was removed (LIFO across both queues); false when no removable items remained. - pub removed: bool, +pub struct PluginUpdateAllEntry { + /// Error message (failure only) + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + /// Marketplace the plugin came from. Empty string ("") for direct installs. + pub marketplace: String, + /// Plugin name that was updated + pub name: String, + /// Version after the update, when available + #[serde(skip_serializing_if = "Option::is_none")] + pub new_version: Option, + /// Previously installed version, when available + #[serde(skip_serializing_if = "Option::is_none")] + pub previous_version: Option, + /// Number of skills installed after the update (success only) + #[serde(skip_serializing_if = "Option::is_none")] + pub skills_installed: Option, + /// Whether the update succeeded for this plugin + pub success: bool, } -/// Event type to register consumer interest for, used by runtime gating logic. +/// Result of updating all installed plugins. /// ///
/// @@ -10306,12 +10734,12 @@ pub struct QueueRemoveMostRecentResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct RegisterEventInterestParams { - /// The event type the consumer wants the runtime to treat as 'observed' for behavior-switching gating. Some runtime code paths inspect whether any consumer is interested in a specific event type and choose a different implementation accordingly (e.g. `mcp.oauth_required`: when interest is registered the runtime delegates interactive OAuth token acquisition to the consumer via `mcp.oauth_required` events; when no interest is registered the runtime still attempts non-interactive reconnect from cached or refreshable tokens, and only marks the server `needs-auth` if usable credentials are unavailable — it does not open a browser or start interactive OAuth without a consumer). SDK clients that long-poll events do NOT automatically appear as listeners to these gating checks — they must explicitly call `registerInterest` for each event type they want the runtime to count as having a consumer. Multiple registrations for the same event type from the same or different consumers are tracked independently and must each be released. See: `mcp.oauth_required`, `sampling.requested`, `auto_mode_switch.requested`, `session_limits_exhausted.requested`, `user_input.requested`, `elicitation.requested`, `command.queued`, `exit_plan_mode.requested`. - pub event_type: String, +pub struct PluginUpdateAllResult { + /// Per-plugin update results in deterministic order. + pub results: Vec, } -/// Opaque handle representing an event-type interest registration. +/// Result of updating a single plugin. /// ///
/// @@ -10321,12 +10749,18 @@ pub struct RegisterEventInterestParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct RegisterEventInterestResult { - /// Opaque handle for this registration. Pass to releaseInterest to release. Each call to registerInterest produces a fresh handle, even when the same eventType is registered multiple times. - pub handle: String, +pub struct PluginUpdateResult { + /// Version after the update, when reported by the plugin manifest + #[serde(skip_serializing_if = "Option::is_none")] + pub new_version: Option, + /// Version that was previously installed, when available + #[serde(skip_serializing_if = "Option::is_none")] + pub previous_version: Option, + /// Number of skills discovered and installed after the update + pub skills_installed: i64, } -/// Optional registration options. +/// A BYOK model definition referencing a named provider. /// ///
/// @@ -10336,14 +10770,35 @@ pub struct RegisterEventInterestResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsRegisterExtensionToolsOnSessionOptions { - /// In-process `() => boolean` gating callback (CLI-only optimization). Marked internal: replaced by runtime-side enable/disable RPCs in the SDK migration. - #[doc(hidden)] +pub struct ProviderModelConfig { + /// Optional capability overrides (vision, tool_calls, reasoning, etc.). #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) enabled: Option, + pub capabilities: Option, + /// Provider-local model id, unique within its provider. The session-wide selection id (shown in the model list and passed to switchTo) is the provider-qualified `provider/id`. + pub id: String, + /// Maximum context window tokens for the model. + #[serde(skip_serializing_if = "Option::is_none")] + pub max_context_window_tokens: Option, + /// Maximum output tokens for the model. + #[serde(skip_serializing_if = "Option::is_none")] + pub max_output_tokens: Option, + /// Maximum prompt/input tokens for the model. + #[serde(skip_serializing_if = "Option::is_none")] + pub max_prompt_tokens: Option, + /// Well-known base model id used for behavior/capability/config lookup. Defaults to `id`. + #[serde(skip_serializing_if = "Option::is_none")] + pub model_id: Option, + /// Display name for model pickers. Defaults to the provider-qualified selection id (`provider/id`). + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + /// Name of the NamedProviderConfig that serves this model. + pub provider: String, + /// The model name sent to the provider API for inference. Defaults to `id`. + #[serde(skip_serializing_if = "Option::is_none")] + pub wire_model: Option, } -/// Params to attach an extension loader's tools to a session. +/// BYOK providers and/or models to add to the session's registry at runtime. Both fields are optional; provide providers, models, or both. /// ///
/// @@ -10353,18 +10808,16 @@ pub struct SessionsRegisterExtensionToolsOnSessionOptions { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub(crate) struct RegisterExtensionToolsParams { - /// In-process ExtensionLoader handle (CLI-only optimization). Marked internal: this field is excluded from the public SDK surface. When the CLI migrates to a process-separated SDK, extension discovery/launch moves entirely into the runtime — the CLI passes pure config (search paths, disabled ids) via SessionOptions instead. - #[doc(hidden)] - pub(crate) loader: serde_json::Value, - /// Optional registration options. +pub struct ProviderAddRequest { + /// BYOK model definitions to register. Each must reference a provider that is already registered or included in this same call. Selection ids (`provider/id`) must be unique across the registry. #[serde(skip_serializing_if = "Option::is_none")] - pub options: Option, - /// Session to register extension tools on. - pub session_id: SessionId, + pub models: Option>, + /// Named BYOK provider connections to register, additive to any providers already in the registry. Each name must be unique across the registry and must not contain '/'. + #[serde(skip_serializing_if = "Option::is_none")] + pub providers: Option>, } -/// Handle for releasing the extension tool registration. +/// The selectable model entries synthesized for the models added by this call. /// ///
/// @@ -10374,13 +10827,12 @@ pub(crate) struct RegisterExtensionToolsParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub(crate) struct RegisterExtensionToolsResult { - /// In-process unsubscribe function (CLI-only optimization). Marked internal: replaced by an explicit `extensions.unregister` RPC in the SDK migration. - #[doc(hidden)] - pub(crate) unsubscribe: serde_json::Value, +pub struct ProviderAddResult { + /// Synthesized selectable model entries for the newly added BYOK models, each under its provider-qualified selection id (`provider/id`). Empty when only providers were added. + pub models: Vec, } -/// Opaque handle previously returned by `registerInterest` to release. +/// Custom model-provider configuration (BYOK). /// ///
/// @@ -10390,12 +10842,51 @@ pub(crate) struct RegisterExtensionToolsResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ReleaseEventInterestParams { - /// Handle returned by a previous `registerInterest` call. Idempotent: releasing an unknown or already-released handle is a no-op (returns success). When the last outstanding handle for an event type is released, the runtime reverts to its 'no consumer' code path for that event type. - pub handle: String, +pub struct ProviderConfig { + /// API key. Optional for local providers like Ollama. + #[serde(skip_serializing_if = "Option::is_none")] + pub api_key: Option, + /// Azure-specific provider options. + #[serde(skip_serializing_if = "Option::is_none")] + pub azure: Option, + /// API endpoint URL. + pub base_url: String, + /// Bearer token for authentication. Sets the Authorization header directly. Takes precedence over apiKey when both are set. + #[serde(skip_serializing_if = "Option::is_none")] + pub bearer_token: Option, + /// When true, the SDK client supplies bearer tokens on demand: the runtime calls the client-session `providerToken.getToken` callback before each request and applies the returned token as an `Authorization: Bearer ` header. This is the bearer/OAuth scheme used by Azure AD / managed-identity tokens and provider OAuth access tokens (including Anthropic's), not a provider-specific API-key header such as Anthropic's `x-api-key`. The token-acquiring function itself stays on the SDK side and is never serialized; only this flag crosses the wire. When set alongside `apiKey`/`bearerToken`, the callback takes precedence: the runtime applies the token returned by `providerToken.getToken` as the `Authorization: Bearer` header for each request and does not send the static credential. + #[serde(skip_serializing_if = "Option::is_none")] + pub has_bearer_token_provider: Option, + /// Custom HTTP headers to include in all outbound requests to the provider. + #[serde(skip_serializing_if = "Option::is_none")] + pub headers: Option>, + /// Maximum context window tokens for the model. + #[serde(skip_serializing_if = "Option::is_none")] + pub max_context_window_tokens: Option, + /// Maximum output tokens for the model. + #[serde(skip_serializing_if = "Option::is_none")] + pub max_output_tokens: Option, + /// Maximum prompt/input tokens for the model. + #[serde(skip_serializing_if = "Option::is_none")] + pub max_prompt_tokens: Option, + /// Well-known model ID used for capability lookup. When set, agent behavior config and token limits are inferred from this model. + #[serde(skip_serializing_if = "Option::is_none")] + pub model_id: Option, + /// Provider transport. Defaults to "http". + #[serde(skip_serializing_if = "Option::is_none")] + pub transport: Option, + /// Provider type. Defaults to "openai" for generic OpenAI-compatible APIs. + #[serde(skip_serializing_if = "Option::is_none")] + pub r#type: Option, + /// Wire API format (openai/azure only). Defaults to "completions". + #[serde(skip_serializing_if = "Option::is_none")] + pub wire_api: Option, + /// The model identifier sent to the provider API for inference (the "wire" model), as opposed to modelId which is the well-known base. + #[serde(skip_serializing_if = "Option::is_none")] + pub wire_model: Option, } -/// Reattach to an existing MC session without creating a new one. +/// Short-lived, rotating credential the caller must send on every request, in addition to `apiKey` if one is present. Omitted when the endpoint does not require one. /// ///
/// @@ -10405,14 +10896,20 @@ pub struct ReleaseEventInterestParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct RemoteControlConfigExistingMcSession { - /// Existing MC session ID to reattach to. - pub mc_session_id: String, - /// Existing MC task ID for the reattached session. - pub mc_task_id: String, +pub struct ProviderSessionToken { + /// When the token expires, if known. Callers should refresh by calling `getEndpoint` again before this time, or reactively on any 401/403 response from `baseUrl`. + #[serde(skip_serializing_if = "Option::is_none")] + pub expires_at: Option, + /// HTTP header name the token must be sent under. + pub header: String, + /// The model the token is bound to, when applicable. When set, the token is only valid for requests against this model. + #[serde(skip_serializing_if = "Option::is_none")] + pub model: Option, + /// The short-lived token value. + pub token: String, } -/// Configuration for the runtime-managed remote-control singleton. +/// A snapshot of the provider endpoint the session is currently configured to talk to. /// ///
/// @@ -10422,24 +10919,28 @@ pub struct RemoteControlConfigExistingMcSession { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct RemoteControlConfig { - /// Reattach to an existing MC session without creating a new one. +pub struct ProviderEndpoint { + /// A credential the caller should use with this endpoint. Omitted only when the endpoint accepts unauthenticated requests. #[serde(skip_serializing_if = "Option::is_none")] - pub existing_mc_session: Option, - /// Whether the user explicitly requested remote (vs. implicit session-sync). Controls warning surfacing for missing-repo cases. - pub explicit: bool, - /// Whether remote export should be enabled. - pub remote: bool, - /// When true, suppresses timeline messages on successful setup. - pub silent: bool, - /// Whether the MC session may steer the local session (write mode). - pub steerable: bool, - /// Existing Mission Control task ID to attach the exported session to. + pub api_key: Option, + /// Base URL to pass to the LLM client library. + pub base_url: String, + /// HTTP headers the caller must include on every outbound request. + pub headers: HashMap, + /// Short-lived, rotating credential the caller must send on every request, in addition to `apiKey` if one is present. Omitted when the endpoint does not require one. #[serde(skip_serializing_if = "Option::is_none")] - pub task_id: Option, + pub session_token: Option, + /// Transport to be used for provider requests. + #[serde(skip_serializing_if = "Option::is_none")] + pub transport: Option, + /// Provider family. Matches the `type` field of a BYOK provider config. + pub r#type: ProviderEndpointType, + /// Wire API to be used, when required for the provider type. + #[serde(skip_serializing_if = "Option::is_none")] + pub wire_api: Option, } -/// Remote control is connected to a local session. +/// Optional model identifier to scope the endpoint snapshot to. /// ///
/// @@ -10449,27 +10950,13 @@ pub struct RemoteControlConfig { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct RemoteControlStatusActive { - /// Session id remote control is pointed at. - pub attached_session_id: String, - /// True while a read-only/session-sync export is deferred, awaiting the first `user.message` before its MC session exists. Marked internal: this field is excluded from the public SDK surface and is populated only on the CLI in-process path. - #[doc(hidden)] - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) awaiting_first_message: Option, - /// MC frontend URL for this session, when known. - #[serde(skip_serializing_if = "Option::is_none")] - pub frontend_url: Option, - /// Whether the MC session may steer this session. - pub is_steerable: bool, - /// In-process prompt-manager handle (CLI-only optimization). Marked internal: this field is excluded from the public SDK surface. When the CLI migrates to a process-separated SDK, the same bidirectional prompt-routing handshake is expressed via dedicated remote-control RPCs (register/resolve) rather than a shared in-process object. - #[doc(hidden)] +pub struct ProviderGetEndpointRequest { + /// Model identifier the caller intends to use against the returned endpoint. Used to pick the correct wire shape. Omit to use whichever model the session is currently using. #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) prompt_manager: Option, - /// Remote control state tag: active. - pub state: RemoteControlStatusActiveState, + pub model_id: Option, } -/// Remote control is in the middle of initial setup. +/// Asks the SDK client to acquire a bearer token for a BYOK provider whose config set `hasBearerTokenProvider: true`. Issued by the runtime before each outbound model request; the runtime does no caching, so this is sent once per request. /// ///
/// @@ -10479,14 +10966,14 @@ pub struct RemoteControlStatusActive { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct RemoteControlStatusConnecting { - /// Session id the connection is attaching to. - pub attached_session_id: String, - /// Remote control state tag: connecting. - pub state: RemoteControlStatusConnectingState, +pub struct ProviderTokenAcquireRequest { + /// Target session identifier + pub session_id: SessionId, + /// Name of the BYOK provider needing a token. For the legacy whole-session `provider` this is the implicit provider name; for named providers it is `NamedProviderConfig.name`. + pub provider_name: String, } -/// The last setup attempt failed. The singleton is otherwise off. +/// A bearer token supplied by the SDK client for a BYOK provider. The runtime sets it as `Authorization: Bearer ` on the outbound request and does no caching; the SDK consumer owns token caching and refresh. /// ///
/// @@ -10496,17 +10983,12 @@ pub struct RemoteControlStatusConnecting { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct RemoteControlStatusError { - /// Session id the failing setup attempt targeted, when known. - #[serde(skip_serializing_if = "Option::is_none")] - pub attached_session_id: Option, - /// Human-readable error message from the last setup attempt. - pub error: String, - /// Remote control state tag: setup failed. - pub state: RemoteControlStatusErrorState, +pub struct ProviderTokenAcquireResult { + /// The bearer token value (without the `Bearer ` prefix). + pub token: String, } -/// Remote control is not connected. +/// Blob attachment with inline base64-encoded data /// ///
/// @@ -10516,12 +10998,19 @@ pub struct RemoteControlStatusError { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct RemoteControlStatusOff { - /// Remote control state tag: not connected. - pub state: RemoteControlStatusOffState, +pub struct PushAttachmentBlob { + /// Base64-encoded content + pub data: String, + /// User-facing display name for the attachment + #[serde(skip_serializing_if = "Option::is_none")] + pub display_name: Option, + /// MIME type of the inline data + pub mime_type: String, + /// Attachment type discriminator + pub r#type: PushAttachmentBlobType, } -/// Wrapper for the singleton's current status. +/// Directory attachment /// ///
/// @@ -10531,12 +11020,16 @@ pub struct RemoteControlStatusOff { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct RemoteControlStatusResult { - /// State of the runtime-managed remote-control singleton. - pub status: serde_json::Value, +pub struct PushAttachmentDirectory { + /// User-facing display name for the attachment + pub display_name: String, + /// Absolute directory path + pub path: String, + /// Attachment type discriminator + pub r#type: PushAttachmentDirectoryType, } -/// Outcome of a stopRemoteControl call. +/// Optional line range to scope the attachment to a specific section of the file /// ///
/// @@ -10546,14 +11039,14 @@ pub struct RemoteControlStatusResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct RemoteControlStopResult { - /// State of the runtime-managed remote-control singleton. - pub status: serde_json::Value, - /// Whether the singleton was actually torn down by this call. - pub stopped: bool, +pub struct PushAttachmentFileLineRange { + /// End line number (1-based, inclusive) + pub end: i64, + /// Start line number (1-based) + pub start: i64, } -/// Outcome of a transferRemoteControl call. +/// File attachment /// ///
/// @@ -10563,14 +11056,19 @@ pub struct RemoteControlStopResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct RemoteControlTransferResult { - /// State of the runtime-managed remote-control singleton. - pub status: serde_json::Value, - /// Whether the rebinding actually happened. - pub transferred: bool, +pub struct PushAttachmentFile { + /// User-facing display name for the attachment + pub display_name: String, + /// Optional line range to scope the attachment to a specific section of the file + #[serde(skip_serializing_if = "Option::is_none")] + pub line_range: Option, + /// Absolute file path + pub path: String, + /// Attachment type discriminator + pub r#type: PushAttachmentFileType, } -/// Optional remote session mode ("off", "export", or "on"); defaults to enabling both export and remote steering. +/// Pointer to a GitHub repository. /// ///
/// @@ -10580,13 +11078,17 @@ pub struct RemoteControlTransferResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct RemoteEnableRequest { - /// Per-session remote mode. "off" disables remote, "export" exports session events to GitHub without enabling remote steering, "on" enables both export and remote steering. +pub struct PushGitHubRepoRef { + /// Numeric GitHub repository id #[serde(skip_serializing_if = "Option::is_none")] - pub mode: Option, + pub id: Option, + /// Repository name (without owner) + pub name: String, + /// Repository owner login (user or organization) + pub owner: String, } -/// GitHub URL for the session and a flag indicating whether remote steering is enabled. +/// Pointer to a GitHub Actions job. /// ///
/// @@ -10596,15 +11098,25 @@ pub struct RemoteEnableRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct RemoteEnableResult { - /// Whether remote steering is enabled - pub remote_steerable: bool, - /// GitHub frontend URL for this session +pub struct PushAttachmentGitHubActionsJob { + /// Terminal conclusion of the job when finished (e.g., success, failure, cancelled). Absent for in-progress jobs. #[serde(skip_serializing_if = "Option::is_none")] - pub url: Option, + pub conclusion: Option, + /// Job id within the workflow run + pub job_id: i64, + /// Display name of the job + pub job_name: String, + /// Repository the workflow run belongs to + pub repo: PushGitHubRepoRef, + /// Attachment type discriminator + pub r#type: PushAttachmentGitHubActionsJobType, + /// URL to the job on GitHub + pub url: String, + /// Display name of the workflow the job ran in + pub workflow_name: String, } -/// New remote-steerability state to persist as a `session.remote_steerable_changed` event. +/// Pointer to a GitHub commit. /// ///
/// @@ -10614,12 +11126,20 @@ pub struct RemoteEnableResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct RemoteNotifySteerableChangedRequest { - /// Whether the session now supports remote steering via GitHub. The runtime persists this as a `session.remote_steerable_changed` event so resume/replay sees the up-to-date capability. - pub remote_steerable: bool, -} - -/// Persist a steerability change as a `session.remote_steerable_changed` event. Used by the host (CLI / SDK consumer) when it has just finished enabling or disabling steering on a remote exporter that the runtime does not directly own. +pub struct PushAttachmentGitHubCommit { + /// First line of the commit message + pub message: String, + /// Full commit SHA + pub oid: String, + /// Repository the commit belongs to + pub repo: PushGitHubRepoRef, + /// Attachment type discriminator + pub r#type: PushAttachmentGitHubCommitType, + /// URL to the commit on GitHub + pub url: String, +} + +/// Pointer to a file in a GitHub repository at a specific ref. /// ///
/// @@ -10629,9 +11149,20 @@ pub struct RemoteNotifySteerableChangedRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct RemoteNotifySteerableChangedResult {} +pub struct PushAttachmentGitHubFile { + /// Repository-relative path to the file + pub path: String, + /// Git ref the file is read at (branch, tag, or commit SHA) + pub r#ref: String, + /// Repository the file lives in + pub repo: PushGitHubRepoRef, + /// Attachment type discriminator + pub r#type: PushAttachmentGitHubFileType, + /// URL to the file on GitHub + pub url: String, +} -/// Remote session connection result. +/// One side of a file diff (head or base) /// ///
/// @@ -10641,14 +11172,16 @@ pub struct RemoteNotifySteerableChangedResult {} ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct RemoteSessionConnectionResult { - /// Metadata for a connected remote session. - pub metadata: ConnectedRemoteSessionMetadata, - /// SDK session ID for the connected remote session. - pub session_id: SessionId, +pub struct PushAttachmentGitHubFileDiffSide { + /// Repository-relative path to the file + pub path: String, + /// Git ref (branch, tag, or commit SHA) the file is read at + pub r#ref: String, + /// Repository the file lives in + pub repo: PushGitHubRepoRef, } -/// GitHub repository the remote session belongs to. +/// Pointer to a single-file diff. At least one of `head` and `base` must be present. /// ///
/// @@ -10658,16 +11191,20 @@ pub struct RemoteSessionConnectionResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct RemoteSessionMetadataRepository { - /// Branch associated with the remote session. - pub branch: String, - /// Repository name. - pub name: String, - /// Repository owner. - pub owner: String, +pub struct PushAttachmentGitHubFileDiff { + /// File location on the base side of the diff. Absent for additions. + #[serde(skip_serializing_if = "Option::is_none")] + pub base: Option, + /// File location on the head side of the diff. Absent for deletions. + #[serde(skip_serializing_if = "Option::is_none")] + pub head: Option, + /// Attachment type discriminator + pub r#type: PushAttachmentGitHubFileDiffType, + /// URL to the diff on GitHub (e.g., a commit, compare, or PR-file URL) + pub url: String, } -/// Remote session metadata for the session to hand off (typically obtained from `sessions.list` with `source: "remote"`). +/// GitHub issue, pull request, or discussion reference /// ///
/// @@ -10677,46 +11214,22 @@ pub struct RemoteSessionMetadataRepository { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct RemoteSessionMetadataValue { - /// Most recent working directory context. - #[serde(skip_serializing_if = "Option::is_none")] - pub context: Option, - /// Always true for remote sessions. - pub is_remote: bool, - /// Last-modified time as an ISO 8601 timestamp. - pub modified_time: String, - /// Optional human-friendly name set via /rename. - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, - /// Pull request number associated with the session. - #[serde(skip_serializing_if = "Option::is_none")] - pub pull_request_number: Option, - /// Backing remote session IDs (most recent first). - pub remote_session_ids: Vec, - /// GitHub repository the remote session belongs to. - pub repository: RemoteSessionMetadataRepository, - /// Original remote resource identifier (task ID or PR node ID). - #[serde(skip_serializing_if = "Option::is_none")] - pub resource_id: Option, - /// Stable session identifier. - pub session_id: SessionId, - /// Deadline (ISO 8601) at which a CLI remote session becomes stale without further heartbeats. - #[serde(skip_serializing_if = "Option::is_none")] - pub stale_at: Option, - /// Session creation time as an ISO 8601 timestamp. - pub start_time: String, - /// Server-side task state returned by GitHub. - #[serde(skip_serializing_if = "Option::is_none")] - pub state: Option, - /// Short summary of the session, when one has been derived. - #[serde(skip_serializing_if = "Option::is_none")] - pub summary: Option, - /// Whether the remote task originated from CCA or CLI `--remote`. - #[serde(skip_serializing_if = "Option::is_none")] - pub task_type: Option, +pub struct PushAttachmentGitHubReference { + /// Issue, pull request, or discussion number + pub number: i64, + /// Type of GitHub reference + pub reference_type: PushAttachmentGitHubReferenceType, + /// Current state of the referenced item (e.g., open, closed, merged) + pub state: String, + /// Title of the referenced item + pub title: String, + /// Attachment type discriminator + pub r#type: PushAttachmentGitHubReferenceType, + /// URL to the referenced item on GitHub + pub url: String, } -/// Repository context for the remote session. +/// Pointer to a GitHub release. /// ///
/// @@ -10726,17 +11239,20 @@ pub struct RemoteSessionMetadataValue { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct RemoteSessionRepository { - /// Optional branch associated with the remote session. - #[serde(skip_serializing_if = "Option::is_none")] - pub branch: Option, - /// Repository name. +pub struct PushAttachmentGitHubRelease { + /// Human-readable release name pub name: String, - /// Repository owner or organization login. - pub owner: String, + /// Repository the release belongs to + pub repo: PushGitHubRepoRef, + /// Git tag the release is anchored to + pub tag_name: String, + /// Attachment type discriminator + pub r#type: PushAttachmentGitHubReleaseType, + /// URL to the release on GitHub + pub url: String, } -/// macOS seatbelt experimental options. +/// Pointer to a GitHub repository. /// ///
/// @@ -10746,13 +11262,22 @@ pub struct RemoteSessionRepository { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SandboxConfigUserPolicyExperimentalSeatbelt { - /// Whether the macOS seatbelt profile may access the keychain. +pub struct PushAttachmentGitHubRepository { + /// Short description of the repository #[serde(skip_serializing_if = "Option::is_none")] - pub keychain_access: Option, + pub description: Option, + /// Git ref this attachment is anchored at (branch, tag, or commit). When absent the default branch is implied. + #[serde(skip_serializing_if = "Option::is_none")] + pub r#ref: Option, + /// Repository pointer + pub repo: PushGitHubRepoRef, + /// Attachment type discriminator + pub r#type: PushAttachmentGitHubRepositoryType, + /// URL to the repository on GitHub + pub url: String, } -/// Platform-specific experimental policy fields. +/// Pointer to a line range inside a file in a GitHub repository. /// ///
/// @@ -10762,13 +11287,22 @@ pub struct SandboxConfigUserPolicyExperimentalSeatbelt { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SandboxConfigUserPolicyExperimental { - /// macOS seatbelt experimental options. - #[serde(skip_serializing_if = "Option::is_none")] - pub seatbelt: Option, +pub struct PushAttachmentGitHubSnippet { + /// Line range the snippet covers + pub line_range: PushAttachmentFileLineRange, + /// Repository-relative path to the file + pub path: String, + /// Git ref the file is read at (branch, tag, or commit SHA) + pub r#ref: String, + /// Repository the file lives in + pub repo: PushGitHubRepoRef, + /// Attachment type discriminator + pub r#type: PushAttachmentGitHubSnippetType, + /// URL to the snippet on GitHub (with line anchor) + pub url: String, } -/// Filesystem rules to merge into the base policy. +/// One side of a tree comparison (head or base) /// ///
/// @@ -10778,22 +11312,14 @@ pub struct SandboxConfigUserPolicyExperimental { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SandboxConfigUserPolicyFilesystem { - /// Whether to clear the policy when the session exits. - #[serde(skip_serializing_if = "Option::is_none")] - pub clear_policy_on_exit: Option, - /// Paths explicitly denied. - #[serde(skip_serializing_if = "Option::is_none")] - pub denied_paths: Option>, - /// Paths granted read-only access. - #[serde(skip_serializing_if = "Option::is_none")] - pub readonly_paths: Option>, - /// Paths granted read/write access. - #[serde(skip_serializing_if = "Option::is_none")] - pub readwrite_paths: Option>, +pub struct PushAttachmentGitHubTreeComparisonSide { + /// Repository the revision belongs to + pub repo: PushGitHubRepoRef, + /// Git revision (branch, tag, or commit SHA) + pub revision: String, } -/// Network rules to merge into the base policy. +/// Pointer to a comparison between two git revisions. /// ///
/// @@ -10803,16 +11329,18 @@ pub struct SandboxConfigUserPolicyFilesystem { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SandboxConfigUserPolicyNetwork { - /// Whether traffic to local/loopback addresses is allowed. - #[serde(skip_serializing_if = "Option::is_none")] - pub allow_local_network: Option, - /// Whether outbound network traffic is allowed at all. - #[serde(skip_serializing_if = "Option::is_none")] - pub allow_outbound: Option, +pub struct PushAttachmentGitHubTreeComparison { + /// Base side of the comparison + pub base: PushAttachmentGitHubTreeComparisonSide, + /// Head side of the comparison + pub head: PushAttachmentGitHubTreeComparisonSide, + /// Attachment type discriminator + pub r#type: PushAttachmentGitHubTreeComparisonType, + /// URL to the comparison on GitHub + pub url: String, } -/// macOS seatbelt-specific options. +/// Generic GitHub URL reference. /// ///
/// @@ -10822,13 +11350,14 @@ pub struct SandboxConfigUserPolicyNetwork { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SandboxConfigUserPolicySeatbelt { - /// Whether the macOS seatbelt profile may access the keychain. - #[serde(skip_serializing_if = "Option::is_none")] - pub keychain_access: Option, +pub struct PushAttachmentGitHubUrl { + /// Attachment type discriminator + pub r#type: PushAttachmentGitHubUrlType, + /// URL to the GitHub resource + pub url: String, } -/// User-managed sandbox policy fragment merged into the auto-discovered base policy. +/// End position of the selection /// ///
/// @@ -10838,22 +11367,14 @@ pub struct SandboxConfigUserPolicySeatbelt { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SandboxConfigUserPolicy { - /// Deprecated legacy location for `seatbelt`; read only when the top-level `seatbelt` is absent. - #[serde(skip_serializing_if = "Option::is_none")] - pub experimental: Option, - /// Filesystem rules to merge into the base policy. - #[serde(skip_serializing_if = "Option::is_none")] - pub filesystem: Option, - /// Network rules to merge into the base policy. - #[serde(skip_serializing_if = "Option::is_none")] - pub network: Option, - /// macOS seatbelt options to merge into the base policy. - #[serde(skip_serializing_if = "Option::is_none")] - pub seatbelt: Option, +pub struct PushAttachmentSelectionDetailsEnd { + /// End character offset within the line (0-based) + pub character: i64, + /// End line number (0-based) + pub line: i64, } -/// Resolved sandbox configuration. +/// Start position of the selection /// ///
/// @@ -10863,25 +11384,15 @@ pub struct SandboxConfigUserPolicy { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SandboxConfig { - /// Whether to auto-add the current working directory to readwritePaths. Default: true. - #[serde(skip_serializing_if = "Option::is_none")] - pub add_current_working_directory: Option, - /// Whether sandboxing is enabled for the session. - pub enabled: bool, - /// Whether to export `GH_TOKEN` so the `gh` CLI authenticates inside the sandbox without the OS keyring the sandbox blocks. Default: false (opt-in). - #[serde(skip_serializing_if = "Option::is_none")] - pub gh_auth: Option, - /// Whether to inject the Copilot GitHub token as an `http..extraheader` so authenticated HTTPS git works inside the sandbox without the shell-based credential helper the sandbox blocks. Default: false (opt-in). - #[serde(skip_serializing_if = "Option::is_none")] - pub git_auth: Option, - /// User-managed sandbox policy fragment merged into the auto-discovered base policy. - #[serde(skip_serializing_if = "Option::is_none")] - pub user_policy: Option, -} - -/// Scheduled prompt entry with ID, timing (`intervalMs`, `cron`, or `at`), prompt text, recurrence, and next run time. -/// +pub struct PushAttachmentSelectionDetailsStart { + /// Start character offset within the line (0-based) + pub character: i64, + /// Start line number (0-based) + pub line: i64, +} + +/// Position range of the selection within the file +/// ///
/// /// **Experimental.** This type is part of an experimental wire-protocol surface @@ -10890,36 +11401,14 @@ pub struct SandboxConfig { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ScheduleEntry { - /// Absolute fire time (epoch milliseconds) for a one-shot calendar schedule. - #[serde(skip_serializing_if = "Option::is_none")] - pub at: Option, - /// 5-field cron expression for a recurring calendar schedule, evaluated in `tz`. - #[serde(skip_serializing_if = "Option::is_none")] - pub cron: Option, - /// Display-only label for the prompt as shown in the UI (e.g. `/skill-name` for a skill-invocation schedule). The actual enqueued prompt is `prompt`. - #[serde(skip_serializing_if = "Option::is_none")] - pub display_prompt: Option, - /// Sequential id assigned by the runtime within the session. Stable across resumes (rebuilt from the event log). - pub id: i64, - /// Interval between scheduled ticks, in milliseconds (relative-interval schedules). - #[serde(skip_serializing_if = "Option::is_none")] - pub interval_ms: Option, - /// ISO 8601 timestamp when the next tick is scheduled to fire. - pub next_run_at: String, - /// Prompt text that gets enqueued on every tick. - pub prompt: String, - /// Whether the schedule re-arms after each tick (`/every`) or fires once (`/after`). - pub recurring: bool, - /// True for a self-paced (`dynamic`) schedule: no fixed cadence; the model arms each next run via the `manage_schedule` `wakeup` action. `nextRunAt` is model-controlled. - #[serde(skip_serializing_if = "Option::is_none")] - pub self_paced: Option, - /// IANA timezone the `cron` expression is evaluated in. - #[serde(skip_serializing_if = "Option::is_none")] - pub tz: Option, +pub struct PushAttachmentSelectionDetails { + /// End position of the selection + pub end: PushAttachmentSelectionDetailsEnd, + /// Start position of the selection + pub start: PushAttachmentSelectionDetailsStart, } -/// Snapshot of the currently active recurring prompts for this session. +/// Code selection attachment from an editor /// ///
/// @@ -10929,12 +11418,20 @@ pub struct ScheduleEntry { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ScheduleList { - /// Active scheduled prompts, ordered by id. - pub entries: Vec, +pub struct PushAttachmentSelection { + /// User-facing display name for the selection + pub display_name: String, + /// Absolute path to the file containing the selection + pub file_path: String, + /// Position range of the selection within the file + pub selection: PushAttachmentSelectionDetails, + /// The selected text content + pub text: String, + /// Attachment type discriminator + pub r#type: PushAttachmentSelectionType, } -/// Identifier of the scheduled prompt to remove. +/// Inputs for starting a deferred-idle drain. /// ///
/// @@ -10944,12 +11441,12 @@ pub struct ScheduleList { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ScheduleStopRequest { - /// Id of the scheduled prompt to remove. - pub id: i64, +pub struct QueueBeginDeferredIdleDrainRequest { + /// Whether the host still has active background work. + pub active_background_work: bool, } -/// Remove a scheduled prompt by id. The result entry is omitted if the id was unknown. +/// Whether a deferred-idle drain should run. /// ///
/// @@ -10959,13 +11456,12 @@ pub struct ScheduleStopRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ScheduleStopResult { - /// The removed entry, or omitted if no entry matched. - #[serde(skip_serializing_if = "Option::is_none")] - pub entry: Option, +pub struct QueueBeginDeferredIdleDrainResult { + /// True when the host should run finishDeferredIdleDrain asynchronously. + pub should_drain: bool, } -/// Secret values to add to the redaction filter. +/// Internal filter for consuming queued system notifications. /// ///
/// @@ -10975,12 +11471,12 @@ pub struct ScheduleStopResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SecretsAddFilterValuesRequest { - /// Raw secret values to register for redaction - pub values: Vec, +pub struct QueueConsumeSystemNotificationsRequest { + /// Opaque runtime-owned filter object. + pub filter: serde_json::Value, } -/// Confirmation that the secret values were registered. +/// Queued-command response indicating the host executed the command, with an optional flag to stop queue processing. /// ///
/// @@ -10990,12 +11486,15 @@ pub struct SecretsAddFilterValuesRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SecretsAddFilterValuesResult { - /// Whether the values were successfully registered - pub ok: bool, +pub struct QueuedCommandHandled { + /// The host actually executed the queued command. + pub handled: bool, + /// When true, the runtime will not process subsequent queued commands until a new request comes in. + #[serde(skip_serializing_if = "Option::is_none")] + pub stop_processing_queue: Option, } -/// Parameters for session.extensions.sendAttachmentsToMessage. +/// Queued-command response indicating the host did not execute the command and the queue may continue. /// ///
/// @@ -11005,15 +11504,12 @@ pub struct SecretsAddFilterValuesResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SendAttachmentsToMessageParams { - /// Attachments to push into the next user-message turn. extension_context entries take the slim shape; standard variants take their full AttachmentSchema shape. - pub attachments: Vec, - /// Optional canvas instance binding the push for provenance. When supplied, the runtime resolves the canvas, verifies it is owned by the calling extension, and stamps canvasId/instanceId onto each extension_context entry. When omitted, no resolution runs and those fields stay unset on the attachment. - #[serde(skip_serializing_if = "Option::is_none")] - pub instance_id: Option, +pub struct QueuedCommandNotHandled { + /// The host did not execute the queued command. Unblocks the queue without claiming the command was processed (e.g. when the handler threw before completing). + pub handled: bool, } -/// A single user message to append to the session as part of a `session.sendMessages` turn +/// Inputs for marking session.idle deferred in native state. /// ///
/// @@ -11023,29 +11519,12 @@ pub struct SendAttachmentsToMessageParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SendMessageItem { - /// Optional attachments (files, directories, selections, blobs, GitHub references) to include with this message - #[serde(skip_serializing_if = "Option::is_none")] - pub attachments: Option>, - /// If false, this message will not trigger a Premium Request Unit charge. User messages default to billable. - #[doc(hidden)] - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) billable: Option, - /// If provided, this is shown in the timeline instead of `prompt` - #[serde(skip_serializing_if = "Option::is_none")] - pub display_prompt: Option, - /// The user message text - pub prompt: String, - /// If set, the request will fail if the named tool is not available when this message is among the user messages at the start of the current exchange - #[serde(skip_serializing_if = "Option::is_none")] - pub required_tool: Option, - /// Optional provenance tag copied to the resulting user.message event. Must match one of three forms: the literal `system`, `command-` for messages originating from a command (e.g. slash command, Mission Control command), or `schedule-` for messages originating from a scheduled job. - #[doc(hidden)] - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) source: Option, +pub struct QueueDeferSessionIdleRequest { + /// Whether the deferred idle was caused by an aborted foreground turn. + pub aborted: bool, } -/// Parameters for sending zero or more user messages to the session in a single turn. Remote-backed (Mission Control) sessions do not support this method and will return an error. +/// Parameters for duplicating a queued item. /// ///
/// @@ -11055,33 +11534,11 @@ pub struct SendMessageItem { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SendMessagesRequest { - /// The UI mode the agent was in when these messages were sent. Defaults to the session's current mode. - #[serde(skip_serializing_if = "Option::is_none")] - pub agent_mode: Option, - /// The user messages to append to the conversation, in order. May be empty, in which case a single turn runs over the existing history with no new user message. - pub messages: Vec, - /// How to deliver the messages. `enqueue` (default) appends to the message queue. `immediate` interjects during an in-progress turn. - #[serde(skip_serializing_if = "Option::is_none")] - pub mode: Option, - /// If true, adds the messages to the front of the queue instead of the end - #[serde(skip_serializing_if = "Option::is_none")] - pub prepend: Option, - /// Custom HTTP headers to include in outbound model requests for this turn. Merged with session-level provider headers; per-turn headers augment and overwrite session-level headers with the same key. - #[serde(skip_serializing_if = "Option::is_none")] - pub request_headers: Option>, - /// W3C Trace Context traceparent header for distributed tracing of this agent turn - #[serde(skip_serializing_if = "Option::is_none")] - pub traceparent: Option, - /// W3C Trace Context tracestate header for distributed tracing - #[serde(skip_serializing_if = "Option::is_none")] - pub tracestate: Option, - /// If true, await completion of the agentic loop for this turn before returning. Defaults to false (fire-and-forget). When true, the result still contains the same `messageIds`; the caller can rely on the agent having processed the messages before the call resolves. - #[serde(skip_serializing_if = "Option::is_none")] - pub wait: Option, +pub struct QueueDuplicateAtRequest { + pub id: String, } -/// Result of sending zero or more user messages +/// Result of duplicating a queued item. /// ///
/// @@ -11091,12 +11548,12 @@ pub struct SendMessagesRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SendMessagesResult { - /// Unique identifiers assigned to the messages, one per provided message in order. Empty when no messages were provided. - pub message_ids: Vec, +pub struct QueueDuplicateAtResult { + /// Fresh stable opaque id assigned to the duplicate. + pub id: String, } -/// Parameters for sending a user message to the session +/// Result of enqueueing the resume-pending wake item. /// ///
/// @@ -11106,49 +11563,12 @@ pub struct SendMessagesResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SendRequest { - /// The UI mode the agent was in when this message was sent. Defaults to the session's current mode. - #[serde(skip_serializing_if = "Option::is_none")] - pub agent_mode: Option, - /// Optional attachments (files, directories, selections, blobs, GitHub references) to include with the message - #[serde(skip_serializing_if = "Option::is_none")] - pub attachments: Option>, - /// If false, this message will not trigger a Premium Request Unit charge. User messages default to billable. - #[serde(skip_serializing_if = "Option::is_none")] - pub billable: Option, - /// If provided, this is shown in the timeline instead of `prompt` - #[serde(skip_serializing_if = "Option::is_none")] - pub display_prompt: Option, - /// How to deliver the message. `enqueue` (default) appends to the message queue. `immediate` interjects during an in-progress turn. - #[serde(skip_serializing_if = "Option::is_none")] - pub mode: Option, - /// If true, adds the message to the front of the queue instead of the end - #[serde(skip_serializing_if = "Option::is_none")] - pub prepend: Option, - /// The user message text - pub prompt: String, - /// Custom HTTP headers to include in outbound model requests for this turn. Merged with session-level provider headers; per-turn headers augment and overwrite session-level headers with the same key. - #[serde(skip_serializing_if = "Option::is_none")] - pub request_headers: Option>, - /// If set, the request will fail if the named tool is not available when this message is among the user messages at the start of the current exchange - #[serde(skip_serializing_if = "Option::is_none")] - pub required_tool: Option, - /// Optional provenance tag copied to the resulting user.message event. Must match one of three forms: the literal `system`, `command-` for messages originating from a command (e.g. slash command, Mission Control command), or `schedule-` for messages originating from a scheduled job. - #[doc(hidden)] - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) source: Option, - /// W3C Trace Context traceparent header for distributed tracing of this agent turn - #[serde(skip_serializing_if = "Option::is_none")] - pub traceparent: Option, - /// W3C Trace Context tracestate header for distributed tracing - #[serde(skip_serializing_if = "Option::is_none")] - pub tracestate: Option, - /// If true, await completion of the agentic loop for this message before returning. Defaults to false (fire-and-forget). When true, the result still contains the same `messageId`; the caller can rely on the agent having processed the message before the call resolves. - #[serde(skip_serializing_if = "Option::is_none")] - pub wait: Option, +pub struct QueueEnqueueResumePendingResult { + /// True when a wake item was newly queued. + pub queued: bool, } -/// Result of sending a user message +/// Inputs for completing a deferred-idle drain. /// ///
/// @@ -11158,12 +11578,14 @@ pub struct SendRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SendResult { - /// Unique identifier assigned to the message - pub message_id: String, +pub struct QueueFinishDeferredIdleDrainRequest { + /// Whether the host still has active background work. + pub active_background_work: bool, + /// Whether native queued work remains. + pub has_pending: bool, } -/// Agents discovered across user, project, plugin, and remote sources. +/// Action selected by the native deferred-idle drain. /// ///
/// @@ -11173,12 +11595,14 @@ pub struct SendResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ServerAgentList { - /// All discovered agents across all sources - pub agents: Vec, +pub struct QueueFinishDeferredIdleDrainResult { + /// Whether the deferred idle was caused by an aborted foreground turn. + pub aborted: bool, + /// One of none, processQueue, or emitSessionIdle. + pub action: String, } -/// Instruction sources discovered across user, repository, and plugin sources. +/// Whether the native queue has pending work. /// ///
/// @@ -11188,12 +11612,12 @@ pub struct ServerAgentList { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ServerInstructionSourceList { - /// All discovered instruction sources - pub sources: Vec, +pub struct QueueHasPendingResult { + /// True when queued or immediate native work is pending. + pub has_pending: bool, } -/// Server-side skill metadata, including name, description, source, enabled/invocable state, path, project path, and argument hint. +/// Serializable message fields accepted by queue.insertAt. /// ///
/// @@ -11203,29 +11627,45 @@ pub struct ServerInstructionSourceList { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ServerSkill { - /// Optional freeform hint describing the skill's expected arguments, from the `argument-hint` frontmatter field +pub struct QueueInsertMessage { + /// Optional explicit agent mode. When omitted, the session's current mode is assigned. #[serde(skip_serializing_if = "Option::is_none")] - pub argument_hint: Option, - /// Description of what the skill does - pub description: String, - /// Whether the skill is currently enabled (based on global config) - pub enabled: bool, - /// Unique identifier for the skill - pub name: String, - /// Absolute path to the skill file + pub agent_mode: Option, + /// Optional attachments for the message. #[serde(skip_serializing_if = "Option::is_none")] - pub path: Option, - /// The project path this skill belongs to (only for project/inherited skills) + pub attachments: Option>, + /// Whether the message is billable. #[serde(skip_serializing_if = "Option::is_none")] - pub project_path: Option, - /// Source location type (e.g., project, personal-copilot, plugin, builtin) - pub source: SkillSource, - /// Whether the skill can be invoked by the user as a slash command - pub user_invocable: bool, + pub billable: Option, + /// Accepted for internal SendOptions compatibility but ignored; delivery is derived from current session activity. + #[serde(skip_serializing_if = "Option::is_none")] + pub delivery: Option, + /// Optional user-facing display text. + #[serde(skip_serializing_if = "Option::is_none")] + pub display_prompt: Option, + /// Accepted for SendOptions compatibility but ignored; inserted items always use queued delivery semantics. + #[serde(skip_serializing_if = "Option::is_none")] + pub mode: Option, + /// Accepted for SendOptions compatibility but ignored; the requested public position controls placement. + #[serde(skip_serializing_if = "Option::is_none")] + pub prepend: Option, + /// The user message text. + pub prompt: String, + /// Per-turn request headers. + #[serde(skip_serializing_if = "Option::is_none")] + pub request_headers: Option>, + /// Required tool name for the turn, when any. + #[serde(skip_serializing_if = "Option::is_none")] + pub required_tool: Option, + /// Optional provenance source. `system` is rejected: it would hide the inserted row from `pendingItems` and make it unaddressable while still executing, so inserted items must stay visible. + #[serde(skip_serializing_if = "Option::is_none")] + pub source: Option, + /// Accepted for SendOptions compatibility but ignored; insertion scheduling is controlled by the queue drain state. + #[serde(skip_serializing_if = "Option::is_none")] + pub wait: Option, } -/// Skills discovered across global and project sources. +/// Parameters for inserting a queued message at a public visible position. /// ///
/// @@ -11235,15 +11675,13 @@ pub struct ServerSkill { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ServerSkillList { - /// Messages for skills that failed to load (e.g. malformed SKILL.md). Empty when host skills are excluded so host-local paths are not disclosed to multitenant callers. - #[serde(skip_serializing_if = "Option::is_none")] - pub errors: Option>, - /// All discovered skills across all sources - pub skills: Vec, +pub struct QueueInsertAtRequest { + pub message: QueueInsertMessage, + /// Zero-based position in the public visible queue. Values outside the queue clamp to an end. + pub position: i64, } -/// Current activity flags for the session. +/// Result of inserting a queued message. /// ///
/// @@ -11253,14 +11691,12 @@ pub struct ServerSkillList { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionActivity { - /// Whether an in-flight operation can currently be aborted. - pub abortable: bool, - /// Whether the session currently has active work, including running turns or tasks. - pub has_active_work: bool, +pub struct QueueInsertAtResult { + /// Fresh stable opaque id assigned to the inserted item. + pub id: String, } -/// Authentication status and account metadata for the session. +/// Parameters for moving a queued item by stable id. /// ///
/// @@ -11270,27 +11706,14 @@ pub struct SessionActivity { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionAuthStatus { - /// Authentication type - #[serde(skip_serializing_if = "Option::is_none")] - pub auth_type: Option, - /// Copilot plan tier (e.g., individual_pro, business) - #[serde(skip_serializing_if = "Option::is_none")] - pub copilot_plan: Option, - /// Authentication host URL - #[serde(skip_serializing_if = "Option::is_none")] - pub host: Option, - /// Whether the session has resolved authentication - pub is_authenticated: bool, - /// Authenticated login/username, if available - #[serde(skip_serializing_if = "Option::is_none")] - pub login: Option, - /// Human-readable authentication status description - #[serde(skip_serializing_if = "Option::is_none")] - pub status_message: Option, +pub struct QueueMoveItemRequest { + /// Stable opaque queued-item id. + pub id: String, + /// Zero-based target position in the public visible queue. Values outside the queue clamp to an end. + pub to_position: i64, } -/// Map of sessionId -> bytes freed by removing the session's workspace directory. +/// Result of moving a queued item. /// ///
/// @@ -11300,39 +11723,12 @@ pub struct SessionAuthStatus { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionBulkDeleteResult { - /// Map of sessionId -> bytes freed by removing the session's workspace directory. Sessions whose deletion failed are omitted from this map (failures are logged on the server but not surfaced per-id; check the map for absent IDs to detect them). - pub freed_bytes: HashMap, -} - -/// Successful compaction history for the session. -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionContextAttributionCompactions { - /// Number of successful compactions in this session. - pub count: i64, -} - -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionContextAttributionEntriesItem { - /// Supplementary per-entry metadata (e.g. `messageCount`, `role`, `evictable`, `pluginSource`). Values are stringified; parse as needed and ignore unrecognized keys. - #[serde(skip_serializing_if = "Option::is_none")] - pub attributes: Option>, - /// Identifier for this entry, formed by joining its `kind` and source name (e.g. `tool:bash`, `skill:tmux`, `toolDefinition:bash`); unique within the snapshot. Use it to match the same entry across snapshots, to correlate with other APIs (skill/agent/MCP registries), and as the `parentId` target for nesting. Distinct from the human-facing `label`. - pub id: String, - /// Source category for this entry. Not a closed set — tolerate unknown values. Known values today: `skill`, `subagent`, `mcpServer`, `tool`, `system`, `toolDefinition`, `plugin`. - pub kind: String, - /// Human-readable display label, e.g. `bash` or `skill: tmux`. Presentation-only; may be localized/reformatted without notice — do not key off it. - pub label: String, - /// Optional `id` of the parent entry: e.g. a `plugin` entry parenting its `skill`/`mcpServer` entries, or the `system` entry parenting `toolDefinition` entries. Omitted for top-level entries. - #[serde(skip_serializing_if = "Option::is_none")] - pub parent_id: Option, - /// Token count currently in context attributable to this entry. - pub tokens: i64, +pub struct QueueMoveItemResult { + /// True when the item changed position; false when it was already at the requested position. + pub changed: bool, } -/// Per-source context-window attribution, or null if the session has not yet been initialized (no system prompt or tool metadata cached). +/// User-facing pending queue entry, with kind and display text for a queued message, slash command, or model change. /// ///
/// @@ -11342,16 +11738,18 @@ pub struct SessionContextAttributionEntriesItem { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionContextAttribution { - /// Successful compaction history for the session. - pub compactions: SessionContextAttributionCompactions, - /// Flat list of per-source attribution entries. Group by `kind` and render unrecognized kinds generically. Nesting and rollups are expressed via `parentId`. - pub entries: Vec, - /// Total token count of the current context window the entries are measured against (system message + conversation messages + tool definitions — the same total reported by /context). Divide an entry's `tokens` by this to derive its share. - pub total_tokens: i64, +pub struct QueuePendingItems { + /// Agent mode stored on this queued entry, as stamped when it was enqueued. Items without an explicit mode report interactive. This is not necessarily the mode that will constrain the turn: a plan or autopilot session applies its own write gate, continuation loop and permission posture to every drained item regardless of the mode stored here. + pub agent_mode: SendAgentMode, + /// Human-readable text to display for this queue entry in the UI + pub display_text: String, + /// Stable opaque id for the canonical queued item. Batch rows share one id. + pub id: String, + /// Whether this item is a queued user message or a queued slash command / model change + pub kind: QueuePendingItemsKind, } -/// Token breakdown for the current context window, or null if the session has not yet been initialized (no system prompt or tool metadata cached). +/// Snapshot of the session's pending queued items and immediate-steering messages. /// ///
/// @@ -11361,30 +11759,14 @@ pub struct SessionContextAttribution { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionContextInfo { - /// Output reserve plus tokens after the buffer-exhaustion blocking threshold (default 95%) - pub buffer_tokens: i64, - /// Token count at which background compaction starts (configurable percentage of promptTokenLimit) - pub compaction_threshold: i64, - /// Tokens consumed by user/assistant/tool messages - pub conversation_tokens: i64, - /// Prompt token limit plus the model's full output token limit. - pub limit: i64, - /// Tokens consumed by MCP tool definitions (subset of toolDefinitionsTokens, excludes deferred tools) - pub mcp_tools_tokens: i64, - /// The model used for token counting - pub model_name: String, - /// Maximum prompt tokens allowed by the model (or DEFAULT_TOKEN_LIMIT if unspecified) - pub prompt_token_limit: i64, - /// Tokens consumed by the system prompt - pub system_tokens: i64, - /// Tokens consumed by tool definitions sent to the model (excludes deferred tools) - pub tool_definitions_tokens: i64, - /// Sum of system, conversation and tool-definition tokens - pub total_tokens: i64, +pub struct QueuePendingItemsResult { + /// Pending queued items in submission order. Includes user messages, queued slash commands, and queued model changes; omits internal system items. + pub items: Vec, + /// Display text for messages currently in the immediate steering queue (interjections sent during a running turn). + pub steering_messages: Vec, } -/// The enriched metadata records, with summary and context fields backfilled where available. Sessions confirmed empty and unnamed are omitted. +/// Parameters for removing a queued item by stable id. /// ///
/// @@ -11394,12 +11776,11 @@ pub struct SessionContextInfo { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionEnrichMetadataResult { - /// Enriched records, with summary and context backfilled. Sessions confirmed empty and unnamed may be omitted. - pub sessions: Vec, +pub struct QueueRemoveAtRequest { + pub id: String, } -/// File path, content to append, and optional mode for the client-provided session filesystem. +/// Result of removing a queued item. /// ///
/// @@ -11409,19 +11790,12 @@ pub struct SessionEnrichMetadataResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFsAppendFileRequest { - /// Target session identifier - pub session_id: SessionId, - /// Path using SessionFs conventions - pub path: String, - /// Content to append - pub content: String, - /// Optional POSIX-style mode for newly created files - #[serde(skip_serializing_if = "Option::is_none")] - pub mode: Option, +pub struct QueueRemoveAtResult { + /// True when the addressed item was removed. + pub removed: bool, } -/// Describes a filesystem error. +/// Indicates whether a user-facing pending item was removed. /// ///
/// @@ -11431,15 +11805,12 @@ pub struct SessionFsAppendFileRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFsError { - /// Error classification - pub code: SessionFsErrorCode, - /// Free-form detail about the error, for logging/diagnostics - #[serde(skip_serializing_if = "Option::is_none")] - pub message: Option, +pub struct QueueRemoveMostRecentResult { + /// True if a user-facing pending item was removed (LIFO across both queues); false when no removable items remained. + pub removed: bool, } -/// Path to test for existence in the client-provided session filesystem. +/// Parameters for steering a queued message into a live turn. /// ///
/// @@ -11449,14 +11820,11 @@ pub struct SessionFsError { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFsExistsRequest { - /// Target session identifier - pub session_id: SessionId, - /// Path using SessionFs conventions - pub path: String, +pub struct QueueSendNowRequest { + pub id: String, } -/// Indicates whether the requested path exists in the client-provided session filesystem. +/// Result of trying to steer a queued message into a live turn. /// ///
/// @@ -11466,12 +11834,12 @@ pub struct SessionFsExistsRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFsExistsResult { - /// Whether the path exists - pub exists: bool, +pub struct QueueSendNowResult { + /// True when the item was accepted into the steering lane; false when no main turn was live. + pub steered: bool, } -/// Directory path to create in the client-provided session filesystem, with options for recursive creation and POSIX mode. +/// Parameters for acquiring or releasing the queued-lane drain pause. Acquisition is exclusive and non-idempotent: `paused: true` against an already-paused session fails with `queue_already_paused`. The pause is never released automatically — it is not tied to the caller's lifetime, so a client that exits without sending `paused: false` leaves the lane frozen. Release is unowned: `paused: false` clears the pause for any caller, including one that never acquired it. /// ///
/// @@ -11481,20 +11849,11 @@ pub struct SessionFsExistsResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFsMkdirRequest { - /// Target session identifier - pub session_id: SessionId, - /// Path using SessionFs conventions - pub path: String, - /// Create parent directories as needed - #[serde(skip_serializing_if = "Option::is_none")] - pub recursive: Option, - /// Optional POSIX-style mode for newly created directories - #[serde(skip_serializing_if = "Option::is_none")] - pub mode: Option, +pub struct QueueSetDrainPausedRequest { + pub paused: bool, } -/// Directory path whose entries should be listed from the client-provided session filesystem. +/// Internal snapshot of native queue state for local session orchestration. /// ///
/// @@ -11504,14 +11863,20 @@ pub struct SessionFsMkdirRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFsReaddirRequest { - /// Target session identifier - pub session_id: SessionId, - /// Path using SessionFs conventions - pub path: String, +pub struct QueueSnapshotResult { + /// Insertion orders for queued items, aligned with `items`. + #[serde(skip_serializing_if = "Option::is_none")] + pub item_orders: Option>, + /// User-facing pending items in FIFO order. + pub items: Vec, + /// Insertion orders for immediate steering messages, aligned with `steeringMessages`. + #[serde(skip_serializing_if = "Option::is_none")] + pub steering_message_orders: Option>, + /// Immediate steering messages waiting for an active turn. + pub steering_messages: Vec, } -/// Names of entries in the requested directory, or a filesystem error if the read failed. +/// Parameters for editing a single queued message. /// ///
/// @@ -11521,15 +11886,14 @@ pub struct SessionFsReaddirRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFsReaddirResult { - /// Entry names in the directory - pub entries: Vec, - /// Describes a filesystem error. +pub struct QueueUpdateTextRequest { #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, + pub display_prompt: Option, + pub id: String, + pub prompt: String, } -/// Directory entry returned by session filesystem `readdirWithTypes`, with name and entry type. +/// Result of editing a queued message. /// ///
/// @@ -11539,14 +11903,12 @@ pub struct SessionFsReaddirResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFsReaddirWithTypesEntry { - /// Entry name - pub name: String, - /// Entry type - pub r#type: SessionFsReaddirWithTypesEntryType, +pub struct QueueUpdateTextResult { + /// True when the stored text changed. + pub updated: bool, } -/// Directory path whose entries (with type information) should be listed from the client-provided session filesystem. +/// Event type to register consumer interest for, used by runtime gating logic. /// ///
/// @@ -11556,14 +11918,12 @@ pub struct SessionFsReaddirWithTypesEntry { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFsReaddirWithTypesRequest { - /// Target session identifier - pub session_id: SessionId, - /// Path using SessionFs conventions - pub path: String, +pub struct RegisterEventInterestParams { + /// The event type the consumer wants the runtime to treat as 'observed' for behavior-switching gating. Some runtime code paths inspect whether any consumer is interested in a specific event type and choose a different implementation accordingly (e.g. `mcp.oauth_required`: when interest is registered the runtime delegates interactive OAuth token acquisition to the consumer via `mcp.oauth_required` events; when no interest is registered the runtime still attempts non-interactive reconnect from cached or refreshable tokens, and only marks the server `needs-auth` if usable credentials are unavailable — it does not open a browser or start interactive OAuth without a consumer). SDK clients that long-poll events do NOT automatically appear as listeners to these gating checks — they must explicitly call `registerInterest` for each event type they want the runtime to count as having a consumer. Multiple registrations for the same event type from the same or different consumers are tracked independently and must each be released. See: `mcp.oauth_required`, `sampling.requested`, `auto_mode_switch.requested`, `session_limits_exhausted.requested`, `user_input.requested`, `elicitation.requested`, `command.queued`, `exit_plan_mode.requested`. + pub event_type: String, } -/// Entries in the requested directory paired with file/directory type information, or a filesystem error if the read failed. +/// Opaque handle representing an event-type interest registration. /// ///
/// @@ -11573,15 +11933,12 @@ pub struct SessionFsReaddirWithTypesRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFsReaddirWithTypesResult { - /// Directory entries with type information - pub entries: Vec, - /// Describes a filesystem error. - #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, +pub struct RegisterEventInterestResult { + /// Opaque handle for this registration. Pass to releaseInterest to release. Each call to registerInterest produces a fresh handle, even when the same eventType is registered multiple times. + pub handle: String, } -/// Path of the file to read from the client-provided session filesystem. +/// Optional registration options. /// ///
/// @@ -11591,14 +11948,14 @@ pub struct SessionFsReaddirWithTypesResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFsReadFileRequest { - /// Target session identifier - pub session_id: SessionId, - /// Path using SessionFs conventions - pub path: String, +pub struct SessionsRegisterExtensionToolsOnSessionOptions { + /// In-process `() => boolean` gating callback (CLI-only optimization). Marked internal: replaced by runtime-side enable/disable RPCs in the SDK migration. + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) enabled: Option, } -/// File content as a UTF-8 string, or a filesystem error if the read failed. +/// Params to attach an extension loader's tools to a session. /// ///
/// @@ -11608,15 +11965,18 @@ pub struct SessionFsReadFileRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFsReadFileResult { - /// File content as UTF-8 string - pub content: String, - /// Describes a filesystem error. +pub(crate) struct RegisterExtensionToolsParams { + /// In-process ExtensionLoader handle (CLI-only optimization). Marked internal: this field is excluded from the public SDK surface. When the CLI migrates to a process-separated SDK, extension discovery/launch moves entirely into the runtime — the CLI passes pure config (search paths, disabled ids) via SessionOptions instead. + #[doc(hidden)] + pub(crate) loader: serde_json::Value, + /// Optional registration options. #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, + pub options: Option, + /// Session to register extension tools on. + pub session_id: SessionId, } -/// Source and destination paths for renaming or moving an entry in the client-provided session filesystem. +/// Handle for releasing the extension tool registration. /// ///
/// @@ -11626,16 +11986,13 @@ pub struct SessionFsReadFileResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFsRenameRequest { - /// Target session identifier - pub session_id: SessionId, - /// Source path using SessionFs conventions - pub src: String, - /// Destination path using SessionFs conventions - pub dest: String, +pub(crate) struct RegisterExtensionToolsResult { + /// In-process unsubscribe function (CLI-only optimization). Marked internal: replaced by an explicit `extensions.unregister` RPC in the SDK migration. + #[doc(hidden)] + pub(crate) unsubscribe: serde_json::Value, } -/// Path to remove from the client-provided session filesystem, with options for recursive removal and force. +/// Opaque handle previously returned by `registerInterest` to release. /// ///
/// @@ -11645,20 +12002,12 @@ pub struct SessionFsRenameRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFsRmRequest { - /// Target session identifier - pub session_id: SessionId, - /// Path using SessionFs conventions - pub path: String, - /// Remove directories and their contents recursively - #[serde(skip_serializing_if = "Option::is_none")] - pub recursive: Option, - /// Ignore errors if the path does not exist - #[serde(skip_serializing_if = "Option::is_none")] - pub force: Option, +pub struct ReleaseEventInterestParams { + /// Handle returned by a previous `registerInterest` call. Idempotent: releasing an unknown or already-released handle is a no-op (returns success). When the last outstanding handle for an event type is released, the runtime reverts to its 'no consumer' code path for that event type. + pub handle: String, } -/// Optional capabilities declared by the provider +/// Reattach to an existing MC session without creating a new one. /// ///
/// @@ -11668,13 +12017,14 @@ pub struct SessionFsRmRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFsSetProviderCapabilities { - /// Whether the provider supports SQLite query/exists operations - #[serde(skip_serializing_if = "Option::is_none")] - pub sqlite: Option, +pub struct RemoteControlConfigExistingMcSession { + /// Existing MC session ID to reattach to. + pub mc_session_id: String, + /// Existing MC task ID for the reattached session. + pub mc_task_id: String, } -/// Initial working directory, session-state path layout, and path conventions used to register the calling SDK client as the session filesystem provider. +/// Configuration for the runtime-managed remote-control singleton. /// ///
/// @@ -11684,19 +12034,24 @@ pub struct SessionFsSetProviderCapabilities { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFsSetProviderRequest { - /// Optional capabilities declared by the provider +pub struct RemoteControlConfig { + /// Reattach to an existing MC session without creating a new one. #[serde(skip_serializing_if = "Option::is_none")] - pub capabilities: Option, - /// Path conventions used by this filesystem - pub conventions: SessionFsSetProviderConventions, - /// Initial working directory for sessions - pub initial_cwd: String, - /// Path within each session's SessionFs where the runtime stores files for that session - pub session_state_path: String, + pub existing_mc_session: Option, + /// Whether the user explicitly requested remote (vs. implicit session-sync). Controls warning surfacing for missing-repo cases. + pub explicit: bool, + /// Whether remote export should be enabled. + pub remote: bool, + /// When true, suppresses timeline messages on successful setup. + pub silent: bool, + /// Whether the MC session may steer the local session (write mode). + pub steerable: bool, + /// Existing Mission Control task ID to attach the exported session to. + #[serde(skip_serializing_if = "Option::is_none")] + pub task_id: Option, } -/// Indicates whether the calling client was registered as the session filesystem provider. +/// Remote control is connected to a local session. /// ///
/// @@ -11706,12 +12061,27 @@ pub struct SessionFsSetProviderRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFsSetProviderResult { - /// Whether the provider was set successfully - pub success: bool, +pub struct RemoteControlStatusActive { + /// Session id remote control is pointed at. + pub attached_session_id: String, + /// True while a read-only/session-sync export is deferred, awaiting the first `user.message` before its MC session exists. Marked internal: this field is excluded from the public SDK surface and is populated only on the CLI in-process path. + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) awaiting_first_message: Option, + /// MC frontend URL for this session, when known. + #[serde(skip_serializing_if = "Option::is_none")] + pub frontend_url: Option, + /// Whether the MC session may steer this session. + pub is_steerable: bool, + /// In-process prompt-manager handle (CLI-only optimization). Marked internal: this field is excluded from the public SDK surface. When the CLI migrates to a process-separated SDK, the same bidirectional prompt-routing handshake is expressed via dedicated remote-control RPCs (register/resolve) rather than a shared in-process object. + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) prompt_manager: Option, + /// Remote control state tag: active. + pub state: RemoteControlStatusActiveState, } -/// Indicates whether the per-session SQLite database already exists. +/// Remote control is in the middle of initial setup. /// ///
/// @@ -11721,12 +12091,14 @@ pub struct SessionFsSetProviderResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFsSqliteExistsResult { - /// Whether the session database already exists - pub exists: bool, +pub struct RemoteControlStatusConnecting { + /// Session id the connection is attaching to. + pub attached_session_id: String, + /// Remote control state tag: connecting. + pub state: RemoteControlStatusConnectingState, } -/// SQL query, query type, and optional bind parameters for executing a SQLite query against the per-session database. +/// The last setup attempt failed. The singleton is otherwise off. /// ///
/// @@ -11736,19 +12108,17 @@ pub struct SessionFsSqliteExistsResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFsSqliteQueryRequest { - /// Target session identifier - pub session_id: SessionId, - /// SQL query to execute - pub query: String, - /// How to execute the query: 'exec' for DDL/multi-statement (no results), 'query' for SELECT (returns rows), 'run' for INSERT/UPDATE/DELETE (returns rowsAffected) - pub query_type: SessionFsSqliteQueryType, - /// Optional named bind parameters +pub struct RemoteControlStatusError { + /// Session id the failing setup attempt targeted, when known. #[serde(skip_serializing_if = "Option::is_none")] - pub params: Option>, + pub attached_session_id: Option, + /// Human-readable error message from the last setup attempt. + pub error: String, + /// Remote control state tag: setup failed. + pub state: RemoteControlStatusErrorState, } -/// Query results including rows, columns, and rows affected, or a filesystem error if execution failed. +/// Remote control is not connected. /// ///
/// @@ -11758,22 +12128,12 @@ pub struct SessionFsSqliteQueryRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFsSqliteQueryResult { - /// Column names from the result set - pub columns: Vec, - /// Describes a filesystem error. - #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, - /// SQLite last_insert_rowid() value for INSERT. - #[serde(skip_serializing_if = "Option::is_none")] - pub last_insert_rowid: Option, - /// For SELECT: array of row objects. For others: empty array. - pub rows: Vec>, - /// Number of rows affected (for INSERT/UPDATE/DELETE) - pub rows_affected: i64, +pub struct RemoteControlStatusOff { + /// Remote control state tag: not connected. + pub state: RemoteControlStatusOffState, } -/// Path whose metadata should be returned from the client-provided session filesystem. +/// Wrapper for the singleton's current status. /// ///
/// @@ -11783,14 +12143,12 @@ pub struct SessionFsSqliteQueryResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFsStatRequest { - /// Target session identifier - pub session_id: SessionId, - /// Path using SessionFs conventions - pub path: String, +pub struct RemoteControlStatusResult { + /// State of the runtime-managed remote-control singleton. + pub status: serde_json::Value, } -/// Filesystem metadata for the requested path, or a filesystem error if the stat failed. +/// Outcome of a stopRemoteControl call. /// ///
/// @@ -11800,23 +12158,14 @@ pub struct SessionFsStatRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFsStatResult { - /// ISO 8601 timestamp of creation - pub birthtime: String, - /// Describes a filesystem error. - #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, - /// Whether the path is a directory - pub is_directory: bool, - /// Whether the path is a file - pub is_file: bool, - /// ISO 8601 timestamp of last modification - pub mtime: String, - /// File size in bytes - pub size: i64, +pub struct RemoteControlStopResult { + /// State of the runtime-managed remote-control singleton. + pub status: serde_json::Value, + /// Whether the singleton was actually torn down by this call. + pub stopped: bool, } -/// File path, content to write, and optional mode for the client-provided session filesystem. +/// Outcome of a transferRemoteControl call. /// ///
/// @@ -11826,19 +12175,14 @@ pub struct SessionFsStatResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFsWriteFileRequest { - /// Target session identifier - pub session_id: SessionId, - /// Path using SessionFs conventions - pub path: String, - /// Content to write - pub content: String, - /// Optional POSIX-style mode for newly created files - #[serde(skip_serializing_if = "Option::is_none")] - pub mode: Option, +pub struct RemoteControlTransferResult { + /// State of the runtime-managed remote-control singleton. + pub status: serde_json::Value, + /// Whether the rebinding actually happened. + pub transferred: bool, } -/// Installed plugin record for a session, with marketplace, version, install time, enabled state, cache path, and source. +/// Optional remote session mode ("off", "export", or "on"); defaults to enabling both export and remote steering. /// ///
/// @@ -11848,28 +12192,13 @@ pub struct SessionFsWriteFileRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionInstalledPlugin { - /// Path where the plugin is cached locally - #[serde(rename = "cache_path", skip_serializing_if = "Option::is_none")] - pub cache_path: Option, - /// Whether the plugin is currently enabled - pub enabled: bool, - /// Installation timestamp (ISO-8601) - #[serde(rename = "installed_at")] - pub installed_at: String, - /// Marketplace the plugin came from (empty string for direct repo installs) - pub marketplace: String, - /// Plugin name - pub name: String, - /// Source descriptor for direct repo installs (when marketplace is empty) - #[serde(skip_serializing_if = "Option::is_none")] - pub source: Option, - /// Installed version, if known +pub struct RemoteEnableRequest { + /// Per-session remote mode. "off" disables remote, "export" exports session events to GitHub without enabling remote steering, "on" enables both export and remote steering. #[serde(skip_serializing_if = "Option::is_none")] - pub version: Option, + pub mode: Option, } -/// Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref, and optional subpath. +/// GitHub URL for the session and a flag indicating whether remote steering is enabled. /// ///
/// @@ -11879,17 +12208,15 @@ pub struct SessionInstalledPlugin { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionInstalledPluginSourceGitHub { - #[serde(skip_serializing_if = "Option::is_none")] - pub path: Option, +pub struct RemoteEnableResult { + /// Whether remote steering is enabled + pub remote_steerable: bool, + /// GitHub frontend URL for this session #[serde(skip_serializing_if = "Option::is_none")] - pub r#ref: Option, - pub repo: String, - /// Constant value. Always "github". - pub source: SessionInstalledPluginSourceGitHubSource, + pub url: Option, } -/// Source descriptor for a direct local plugin install, with a local filesystem path. +/// New remote-steerability state to persist as a `session.remote_steerable_changed` event. /// ///
/// @@ -11899,13 +12226,12 @@ pub struct SessionInstalledPluginSourceGitHub { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionInstalledPluginSourceLocal { - pub path: String, - /// Constant value. Always "local". - pub source: SessionInstalledPluginSourceLocalSource, +pub struct RemoteNotifySteerableChangedRequest { + /// Whether the session now supports remote steering via GitHub. The runtime persists this as a `session.remote_steerable_changed` event so resume/replay sees the up-to-date capability. + pub remote_steerable: bool, } -/// Source descriptor for a direct URL plugin install, with URL, optional ref, and optional subpath. +/// Persist a steerability change as a `session.remote_steerable_changed` event. Used by the host (CLI / SDK consumer) when it has just finished enabling or disabling steering on a remote exporter that the runtime does not directly own. /// ///
/// @@ -11915,17 +12241,9 @@ pub struct SessionInstalledPluginSourceLocal { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionInstalledPluginSourceUrl { - #[serde(skip_serializing_if = "Option::is_none")] - pub path: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub r#ref: Option, - /// Constant value. Always "url". - pub source: SessionInstalledPluginSourceUrlSource, - pub url: String, -} +pub struct RemoteNotifySteerableChangedResult {} -/// Sessions matching the filter, ordered most-recently-modified first. +/// Remote session connection result. /// ///
/// @@ -11935,12 +12253,14 @@ pub struct SessionInstalledPluginSourceUrl { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionList { - /// Sessions ordered most-recently-modified first. Discriminated by `isRemote`. - pub sessions: Vec, +pub struct RemoteSessionConnectionResult { + /// Metadata for a connected remote session. + pub metadata: ConnectedRemoteSessionMetadata, + /// SDK session ID for the connected remote session. + pub session_id: SessionId, } -/// Optional filter applied to the returned sessions +/// GitHub repository the remote session belongs to. /// ///
/// @@ -11950,22 +12270,16 @@ pub struct SessionList { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionListFilter { - /// Match sessions whose context.branch equals this value - #[serde(skip_serializing_if = "Option::is_none")] - pub branch: Option, - /// Match sessions whose context.cwd equals this value - #[serde(skip_serializing_if = "Option::is_none")] - pub cwd: Option, - /// Match sessions whose context.gitRoot equals this value - #[serde(skip_serializing_if = "Option::is_none")] - pub git_root: Option, - /// Match sessions whose context.repository equals this value - #[serde(skip_serializing_if = "Option::is_none")] - pub repository: Option, +pub struct RemoteSessionMetadataRepository { + /// Branch associated with the remote session. + pub branch: String, + /// Repository name. + pub name: String, + /// Repository owner. + pub owner: String, } -/// Queued repo-level startup prompts and the total hook command count after loading. +/// Remote session metadata for the session to hand off (typically obtained from `sessions.list` with `source: "remote"`). /// ///
/// @@ -11975,49 +12289,46 @@ pub struct SessionListFilter { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionLoadDeferredRepoHooksResult { - /// Total hook command count (user + plugin + repo) loaded for the session by this call. Captured atomically with startupPrompts so callers don't need to read a separate counter. - pub hook_count: i64, - /// Repo-level startup prompts queued from repo hook configs. Empty on resume, when no repo configs were pending, or when disableAllHooks is set. - pub startup_prompts: Vec, -} - -/// Public-facing projection of workspace metadata for SDK / TUI consumers -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionMetadataSnapshotWorkspace { - /// Branch checked out at session start, if any +pub struct RemoteSessionMetadataValue { + /// Most recent working directory context. #[serde(skip_serializing_if = "Option::is_none")] - pub branch: Option, - /// ISO 8601 timestamp when the workspace was created - #[serde(rename = "created_at", skip_serializing_if = "Option::is_none")] - pub created_at: Option, - /// Current working directory at session start - #[serde(skip_serializing_if = "Option::is_none")] - pub cwd: Option, - /// Resolved git root for cwd, if any - #[serde(rename = "git_root", skip_serializing_if = "Option::is_none")] - pub git_root: Option, - /// Repository host type, if known - #[serde(rename = "host_type", skip_serializing_if = "Option::is_none")] - pub host_type: Option, - /// Workspace identifier (1:1 with sessionId) - pub id: String, - /// Display name for the session, if set + pub context: Option, + /// Always true for remote sessions. + pub is_remote: bool, + /// Last-modified time as an ISO 8601 timestamp. + pub modified_time: String, + /// Optional human-friendly name set via /rename. #[serde(skip_serializing_if = "Option::is_none")] pub name: Option, - /// Repository identifier in 'owner/repo' or 'org/project/repo' format, if any + /// Pull request number associated with the session. #[serde(skip_serializing_if = "Option::is_none")] - pub repository: Option, - /// ISO 8601 timestamp when the workspace was last updated - #[serde(rename = "updated_at", skip_serializing_if = "Option::is_none")] - pub updated_at: Option, - /// Whether the display name was explicitly set by the user - #[serde(rename = "user_named", skip_serializing_if = "Option::is_none")] - pub user_named: Option, + pub pull_request_number: Option, + /// Backing remote session IDs (most recent first). + pub remote_session_ids: Vec, + /// GitHub repository the remote session belongs to. + pub repository: RemoteSessionMetadataRepository, + /// Original remote resource identifier (task ID or PR node ID). + #[serde(skip_serializing_if = "Option::is_none")] + pub resource_id: Option, + /// Stable session identifier. + pub session_id: SessionId, + /// Deadline (ISO 8601) at which a CLI remote session becomes stale without further heartbeats. + #[serde(skip_serializing_if = "Option::is_none")] + pub stale_at: Option, + /// Session creation time as an ISO 8601 timestamp. + pub start_time: String, + /// Server-side task state returned by GitHub. + #[serde(skip_serializing_if = "Option::is_none")] + pub state: Option, + /// Short summary of the session, when one has been derived. + #[serde(skip_serializing_if = "Option::is_none")] + pub summary: Option, + /// Whether the remote task originated from CCA or CLI `--remote`. + #[serde(skip_serializing_if = "Option::is_none")] + pub task_type: Option, } -/// Point-in-time snapshot of slow-changing session identifier and state fields +/// Repository context for the remote session. /// ///
/// @@ -12027,45 +12338,52 @@ pub struct SessionMetadataSnapshotWorkspace { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMetadataSnapshot { - /// True when the session was detected to be in use by another process at construction time. Local consumers may surface a confirmation prompt before fully attaching. Always false for new sessions. - pub already_in_use: bool, - /// Runtime client name associated with the session (telemetry identifier). - #[serde(skip_serializing_if = "Option::is_none")] - pub client_name: Option, - /// The current agent mode for this session (e.g., 'interactive', 'plan', 'autopilot') - pub current_mode: MetadataSnapshotCurrentMode, - /// User-provided name supplied at session construction (via `--name`), if any. Immutable after construction. +pub struct RemoteSessionRepository { + /// Optional branch associated with the remote session. #[serde(skip_serializing_if = "Option::is_none")] - pub initial_name: Option, - /// Whether this is a remote session (i.e., one whose runtime executes elsewhere and is steered through this process) - pub is_remote: bool, - /// ISO 8601 timestamp of when the session's persisted state was last modified on disk. For new sessions, equals startTime. For resumed sessions, reflects the previous modification time at construction. - pub modified_time: String, - /// Remote-session-specific metadata. Populated only when `isRemote` is true. Fields are immutable for the lifetime of the session. + pub branch: Option, + /// Repository name. + pub name: String, + /// Repository owner or organization login. + pub owner: String, +} + +/// Credential-injection capability flags applied while the sandbox is enabled. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SandboxConfigAuth { + /// Whether to export `GH_TOKEN` so the `gh` CLI authenticates inside the sandbox without the OS keyring the sandbox blocks. Default: false (opt-in). #[serde(skip_serializing_if = "Option::is_none")] - pub remote_metadata: Option, - /// Currently selected model identifier, if any + pub gh: Option, + /// Whether to inject git credentials as an `http..extraheader` so authenticated HTTPS git works inside the sandbox without the shell-based credential helper the sandbox blocks. github.com is served by the Copilot token; every other forge (Azure DevOps, GitHub Enterprise Server, GitLab, ...) by a credential the host resolves from the user's own helper before the sandbox is applied. Default: false (opt-in). #[serde(skip_serializing_if = "Option::is_none")] - pub selected_model: Option, - /// The unique identifier of the session - pub session_id: SessionId, - /// Current session limits, or null when no limits are active - pub session_limits: Option, - /// ISO 8601 timestamp of when the session started - pub start_time: String, - /// Short human-readable summary of the session, if known. Omitted when no summary has been generated. + pub git: Option, +} + +/// macOS seatbelt experimental options. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SandboxConfigUserPolicyExperimentalSeatbelt { + /// Whether the macOS seatbelt profile may access the keychain. #[serde(skip_serializing_if = "Option::is_none")] - pub summary: Option, - /// Absolute path to the session's current working directory - pub working_directory: String, - /// Public-facing workspace metadata for this session, or null if the session has no associated workspace. Excludes runtime-internal fields (GitHub IDs, summary count, internal flags). - pub workspace: Option, - /// Absolute path to the session's workspace directory on disk, or null if the session has no associated workspace - pub workspace_path: Option, + pub keychain_access: Option, } -/// Cost-category metadata for a CAPI model. +/// Platform-specific experimental policy fields. /// ///
/// @@ -12075,12 +12393,13 @@ pub struct SessionMetadataSnapshot { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionModelPriceCategory { - pub id: String, - pub price_category: ModelPickerPriceCategory, +pub struct SandboxConfigUserPolicyExperimental { + /// macOS seatbelt experimental options. + #[serde(skip_serializing_if = "Option::is_none")] + pub seatbelt: Option, } -/// The list of models available to this session. +/// Filesystem rules to merge into the base policy. /// ///
/// @@ -12090,18 +12409,22 @@ pub struct SessionModelPriceCategory { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionModelList { - /// Available models, ordered with the most preferred default first. Includes both Copilot (CAPI) models and any registry BYOK models; a BYOK model appears under its provider-qualified selection id (`provider/id`). - pub list: Vec, - /// Cost categories for the full CAPI catalog, including picker-disabled models that Auto may select. Metadata only; entries absent from `list` are not manually selectable. +pub struct SandboxConfigUserPolicyFilesystem { + /// Whether to clear the policy when the session exits. #[serde(skip_serializing_if = "Option::is_none")] - pub model_price_categories: Option>, - /// Per-quota snapshots returned alongside the model list, keyed by quota type. + pub clear_policy_on_exit: Option, + /// Paths explicitly denied. #[serde(skip_serializing_if = "Option::is_none")] - pub quota_snapshots: Option>, + pub denied_paths: Option>, + /// Paths granted read-only access. + #[serde(skip_serializing_if = "Option::is_none")] + pub readonly_paths: Option>, + /// Paths granted read/write access. + #[serde(skip_serializing_if = "Option::is_none")] + pub readwrite_paths: Option>, } -/// Source descriptor for a `sessions.open` content-exclusion rule, with source name and type. +/// HTTP proxy configuration for sandboxed traffic. /// ///
/// @@ -12111,12 +12434,18 @@ pub struct SessionModelList { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource { - pub name: String, - pub r#type: String, +pub struct SandboxConfigUserPolicyNetworkProxy { + /// Optional password for proxy authentication, combined with the URL at spawn time. The persisted value may be a literal password, a `${secret:…}` reference resolved from the OS keychain, or a `${VAR}`/`$VAR` environment reference; it is resolved just before the sandboxed process routes through the proxy. The /sandbox dialog stores a real password in the OS keychain and persists only a `${secret:…}` placeholder (never plaintext in settings.json); the field is masked in the dialog and redacted by /settings show. + #[serde(skip_serializing_if = "Option::is_none")] + pub password: Option, + /// Proxy URL (e.g. http://proxy.example.com:8080). The port is optional and defaults to the scheme's standard port when omitted. Credentials must not be embedded here — a `user:pass@` authority is rejected; put them in the separate `username`/`password` fields. A credential-free http:// loopback URL is routed through the localhost proxy automatically; loopback covers localhost and any *.localhost subdomain, the whole 127.0.0.0/8 range, ::1, and IPv4-mapped loopback (::ffff:127.0.0.1). An https:// URL, or one with a username/password set, is used as-is. + pub url: String, + /// Optional username for proxy authentication. Combined with the URL (and `password`) into `user:pass@host` when the sandboxed process routes through the proxy. + #[serde(skip_serializing_if = "Option::is_none")] + pub username: Option, } -/// Single content-exclusion rule supplied to `sessions.open` options, with paths, match conditions, and source. +/// Network rules to merge into the base policy. /// ///
/// @@ -12126,17 +12455,19 @@ pub struct SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionOpenOptionsAdditionalContentExclusionPolicyRule { +pub struct SandboxConfigUserPolicyNetwork { + /// Whether traffic to local/loopback addresses is allowed. #[serde(skip_serializing_if = "Option::is_none")] - pub if_any_match: Option>, + pub allow_local_network: Option, + /// Whether outbound network traffic is allowed at all. #[serde(skip_serializing_if = "Option::is_none")] - pub if_none_match: Option>, - pub paths: Vec, - /// Source descriptor for a `sessions.open` content-exclusion rule, with source name and type. - pub source: SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource, + pub allow_outbound: Option, + /// HTTP proxy the sandboxed process routes traffic through. Enforced on Windows and cooperative (honored by well-behaved tools, not strictly enforced) on Linux and macOS. Credentials go in the separate `username`/`password` fields. A credential-free http:// loopback proxy URL is routed through the localhost proxy automatically; an https:// or authenticated loopback URL is used as-is. + #[serde(skip_serializing_if = "Option::is_none")] + pub proxy: Option, } -/// Content-exclusion policy supplied to `sessions.open` options, with rules, last-updated data, and scope. +/// macOS seatbelt-specific options. /// ///
/// @@ -12146,15 +12477,13 @@ pub struct SessionOpenOptionsAdditionalContentExclusionPolicyRule { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionOpenOptionsAdditionalContentExclusionPolicy { - #[serde(rename = "last_updated_at")] - pub last_updated_at: serde_json::Value, - pub rules: Vec, - /// Allowed values for the `SessionOpenOptionsAdditionalContentExclusionPolicyScope` enumeration. - pub scope: SessionOpenOptionsAdditionalContentExclusionPolicyScope, +pub struct SandboxConfigUserPolicySeatbelt { + /// Whether the macOS seatbelt profile may access the keychain. + #[serde(skip_serializing_if = "Option::is_none")] + pub keychain_access: Option, } -/// Session construction options. +/// User-managed sandbox policy fragment merged into the auto-discovered base policy. /// ///
/// @@ -12164,223 +12493,2921 @@ pub struct SessionOpenOptionsAdditionalContentExclusionPolicy { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionOpenOptions { - /// Additional content-exclusion policies to merge into the session policy set. - /// - ///
- /// - /// **Experimental.** This type is part of an experimental wire-protocol surface - /// and may change or be removed in future SDK or CLI releases. - /// - ///
+pub struct SandboxConfigUserPolicy { + /// Deprecated legacy location for `seatbelt`; read only when the top-level `seatbelt` is absent. #[serde(skip_serializing_if = "Option::is_none")] - pub additional_content_exclusion_policies: - Option>, - /// Runtime context discriminator for agent filtering. + pub experimental: Option, + /// Filesystem rules to merge into the base policy. #[serde(skip_serializing_if = "Option::is_none")] - pub agent_context: Option, - /// Whether to include instructions from every MCP server in the system prompt instead of only allowlisted servers. + pub filesystem: Option, + /// Network rules to merge into the base policy. #[serde(skip_serializing_if = "Option::is_none")] - pub allow_all_mcp_server_instructions: Option, - /// Whether ask_user is explicitly disabled. + pub network: Option, + /// macOS seatbelt options to merge into the base policy. #[serde(skip_serializing_if = "Option::is_none")] - pub ask_user_disabled: Option, - /// Initial authentication info for the session. + pub seatbelt: Option, +} + +/// Resolved sandbox configuration. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SandboxConfig { + /// Whether to auto-add the current working directory to readwritePaths. Default: true. #[serde(skip_serializing_if = "Option::is_none")] - pub auth_info: Option, - /// Allowlist of available tool names. + pub add_current_working_directory: Option, + /// Whether to auto-grant read access to common developer-tool caches, registries, and toolchains in their default home locations (cargo, go, npm, Maven, and more), plus read-write access to (and, on Unix, up-front creation of) the scratch caches builds write on every run (go-build, ccache, sccache, Gradle caches, Cargo lock/tracker files), so builds work without extra configuration; a relocated CARGO_HOME additionally gets its Cargo lock files granted read-write. Default: true (enabled by default; set to false to opt out). #[serde(skip_serializing_if = "Option::is_none")] - pub available_tools: Option>, - /// Options scoped to the built-in CAPI (Copilot API) provider. + pub allow_dev_tool_access: Option, + /// Credential-injection capability flags. #[serde(skip_serializing_if = "Option::is_none")] - pub capi: Option, - /// Structured client kind used for runtime behavior gates. + pub auth: Option, + /// Whether sandboxing is enabled for the session. + pub enabled: bool, + /// User-managed sandbox policy fragment merged into the auto-discovered base policy. #[serde(skip_serializing_if = "Option::is_none")] - pub client_kind: Option, - /// Identifier of the client driving the session. + pub user_policy: Option, +} + +/// Register an absolute-time scheduled prompt. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ScheduleAddAtRequest { + /// Epoch milliseconds when the prompt should fire. + pub at: i64, + /// Optional display-only prompt label. #[serde(skip_serializing_if = "Option::is_none")] - pub client_name: Option, - /// Whether commit-message coauthor trailers are enabled. + pub display_prompt: Option, + /// Prompt text to enqueue when the schedule fires. + pub prompt: String, + /// Whether the schedule should re-arm after each tick. Defaults to false. #[serde(skip_serializing_if = "Option::is_none")] - pub coauthor_enabled: Option, - /// Override Copilot configuration directory. + pub recurring: Option, +} + +/// Register a cron scheduled prompt. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ScheduleAddCronRequest { + /// 5-field cron expression. + pub cron: String, + /// Optional display-only prompt label. #[serde(skip_serializing_if = "Option::is_none")] - pub config_dir: Option, - /// Whether auto-mode continuation is enabled. + pub display_prompt: Option, + /// Prompt text to enqueue when the schedule fires. + pub prompt: String, + /// Whether the schedule should re-arm after each tick. Defaults to true. #[serde(skip_serializing_if = "Option::is_none")] - pub continue_on_auto_mode: Option, - /// Override URL for the Copilot API endpoint. + pub recurring: Option, + /// IANA timezone for evaluating the cron expression. #[serde(skip_serializing_if = "Option::is_none")] - pub copilot_url: Option, - /// Whether custom agents default to local-only execution. + pub tz: Option, +} + +/// Register a relative-interval scheduled prompt. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ScheduleAddRequest { + /// Optional display-only prompt label. #[serde(skip_serializing_if = "Option::is_none")] - pub custom_agents_local_only: Option, - /// Parent engagement ID for detached child telemetry rollup. + pub display_prompt: Option, + /// Human-readable interval such as `30s`, `5m`, or `2h`. + pub interval: String, + /// Prompt text to enqueue when the schedule fires. + pub prompt: String, + /// Whether the schedule should re-arm after each tick. Defaults to true. #[serde(skip_serializing_if = "Option::is_none")] - pub detached_from_spawning_parent_engagement_id: Option, - /// Parent session ID for detached child telemetry rollup. + pub recurring: Option, +} + +/// Scheduled prompt entry with ID, timing (`intervalMs`, `cron`, or `at`), prompt text, recurrence, and next run time. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ScheduleEntry { + /// Absolute fire time (epoch milliseconds) for a one-shot calendar schedule. #[serde(skip_serializing_if = "Option::is_none")] - pub detached_from_spawning_parent_session_id: Option, - /// Instruction source IDs disabled for this session. + pub at: Option, + /// 5-field cron expression for a recurring calendar schedule, evaluated in `tz`. #[serde(skip_serializing_if = "Option::is_none")] - pub disabled_instruction_sources: Option>, - /// Skill IDs disabled for this session. + pub cron: Option, + /// Display-only label for the prompt as shown in the UI (e.g. `/skill-name` for a skill-invocation schedule). The actual enqueued prompt is `prompt`. #[serde(skip_serializing_if = "Option::is_none")] - pub disabled_skills: Option>, - /// Experimental: enable native model citations (Anthropic models today), normalized onto the `assistant.message` event. Off by default; may change or be removed while the citations surface is experimental. - /// - ///
- /// - /// **Experimental.** This type is part of an experimental wire-protocol surface - /// and may change or be removed in future SDK or CLI releases. - /// - ///
+ pub display_prompt: Option, + /// Sequential id assigned by the runtime within the session. Stable across resumes (rebuilt from the event log). + pub id: i64, + /// Interval between scheduled ticks, in milliseconds (relative-interval schedules). #[serde(skip_serializing_if = "Option::is_none")] - pub enable_citations: Option, - /// Opt-in: self-fetch and enforce enterprise managed settings at session bootstrap. + pub interval_ms: Option, + /// ISO 8601 timestamp when the next tick is scheduled to fire. + pub next_run_at: String, + /// Prompt text that gets enqueued on every tick. + pub prompt: String, + /// Whether the schedule re-arms after each tick (`/every`) or fires once (`/after`). + pub recurring: bool, + /// True for a self-paced (`dynamic`) schedule: no fixed cadence; the model arms each next run via the `manage_schedule` `wakeup` action. `nextRunAt` is model-controlled. #[serde(skip_serializing_if = "Option::is_none")] - pub enable_managed_settings: Option, - /// Whether on-demand custom instruction discovery is enabled. + pub self_paced: Option, + /// IANA timezone the `cron` expression is evaluated in. #[serde(skip_serializing_if = "Option::is_none")] - pub enable_on_demand_instruction_discovery: Option, - /// Whether shell-script safety heuristics are enabled. + pub tz: Option, +} + +/// Result of registering or re-arming a scheduled prompt. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ScheduleAddResult { + /// The registered or updated schedule entry. #[serde(skip_serializing_if = "Option::is_none")] - pub enable_script_safety: Option, - /// Whether model responses stream as delta events. + pub entry: Option, + /// User-facing validation error, when registration failed. #[serde(skip_serializing_if = "Option::is_none")] - pub enable_streaming: Option, - /// How MCP server environment values are interpreted. + pub error: Option, +} + +/// Register a self-paced scheduled prompt. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ScheduleAddSelfPacedRequest { + /// Optional display-only prompt label. #[serde(skip_serializing_if = "Option::is_none")] - pub env_value_mode: Option, - /// Override directory for session event logs. + pub display_prompt: Option, + /// Prompt text to enqueue when the schedule fires. + pub prompt: String, +} + +/// Whether the session currently has an active self-paced schedule. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ScheduleHasSelfPacedResult { + /// True when at least one active schedule is self-paced. + pub has_self_paced: bool, +} + +/// Snapshot of the currently active recurring prompts for this session. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ScheduleList { + /// Active scheduled prompts, ordered by id. + pub entries: Vec, +} + +/// Re-arm a self-paced scheduled prompt. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ScheduleRearmSelfPacedRequest { + /// Epoch milliseconds when the prompt should next fire. + pub at: i64, + /// Id of the self-paced scheduled prompt. + pub id: i64, +} + +/// Identifier of the scheduled prompt to remove. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ScheduleStopRequest { + /// Id of the scheduled prompt to remove. + pub id: i64, +} + +/// Remove a scheduled prompt by id. The result entry is omitted if the id was unknown. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ScheduleStopResult { + /// The removed entry, or omitted if no entry matched. #[serde(skip_serializing_if = "Option::is_none")] - pub events_log_directory: Option, - /// Built-in subagent names to exclude from this session. Excluded built-ins are hidden from agent discovery and cannot be dispatched unless a custom agent with the same name is available. + pub entry: Option, +} + +/// Secret values to add to the redaction filter. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SecretsAddFilterValuesRequest { + /// Raw secret values to register for redaction + pub values: Vec, +} + +/// Confirmation that the secret values were registered. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SecretsAddFilterValuesResult { + /// Whether the values were successfully registered + pub ok: bool, +} + +/// Parameters for session.extensions.sendAttachmentsToMessage. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SendAttachmentsToMessageParams { + /// Attachments to push into the next user-message turn. extension_context entries take the slim shape; standard variants take their full AttachmentSchema shape. + pub attachments: Vec, + /// Optional canvas instance binding the push for provenance. When supplied, the runtime resolves the canvas, verifies it is owned by the calling extension, and stamps canvasId/instanceId onto each extension_context entry. When omitted, no resolution runs and those fields stay unset on the attachment. #[serde(skip_serializing_if = "Option::is_none")] - pub excluded_builtin_agents: Option>, - /// Denylist of tool names. + pub instance_id: Option, +} + +/// A single user message to append to the session as part of a `session.sendMessages` turn +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SendMessageItem { + /// Optional attachments (files, directories, selections, blobs, GitHub references) to include with this message #[serde(skip_serializing_if = "Option::is_none")] - pub excluded_tools: Option>, - /// ExP assignment ('flight') data injected by an SDK integrator, in the same JSON shape the Copilot CLI fetches from the experimentation service (CopilotExpAssignmentResponse). When supplied this is fed into the FeatureFlagService exactly like CLI-fetched assignments and ExP-backed flags wait for it. When absent the session does not block on ExP. + pub attachments: Option>, + /// If false, this message will not trigger a Premium Request Unit charge. User messages default to billable. #[doc(hidden)] #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) exp_assignments: Option, - /// Feature-flag values resolved by the host. + pub(crate) billable: Option, + /// If provided, this is shown in the timeline instead of `prompt` #[serde(skip_serializing_if = "Option::is_none")] - pub feature_flags: Option>, - /// Built-in subagent names to include in this session. When specified, only these built-ins are available, subject to runtime availability and exclusions. Custom agents with the same name remain available. + pub display_prompt: Option, + /// The user message text + pub prompt: String, + /// If set, the request will fail if the named tool is not available when this message is among the user messages at the start of the current exchange #[serde(skip_serializing_if = "Option::is_none")] - pub included_builtin_agents: Option>, - /// Installed plugins visible to the session. + pub required_tool: Option, + /// Optional provenance tag copied to the resulting user.message event. Must be `user`, `system`, `command-` for command-originated messages, `schedule-` for scheduled prompts, or `agent-` for prompts sent by another agent. + #[doc(hidden)] #[serde(skip_serializing_if = "Option::is_none")] - pub installed_plugins: Option>, - /// Stable integration identifier for analytics. - #[serde(skip_serializing_if = "Option::is_none")] - pub integration_id: Option, - /// Whether experimental behavior is enabled. - #[serde(skip_serializing_if = "Option::is_none")] - pub is_experimental_mode: Option, - /// Whether interactive shell sessions are logged. - #[serde(skip_serializing_if = "Option::is_none")] - pub log_interactive_shells: Option, - /// Identifier sent to LSP-style integrations. + pub(crate) source: Option, +} + +/// Parameters for sending zero or more user messages to the session in a single turn. Remote-backed (Mission Control) sessions do not support this method and will return an error. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SendMessagesRequest { + /// The UI mode the agent was in when these messages were sent. Defaults to the session's current mode. #[serde(skip_serializing_if = "Option::is_none")] - pub lsp_client_name: Option, - /// Maximum decoded byte size of a single inline model-facing binary tool result persisted in session events (default 10 MB). + pub agent_mode: Option, + /// The user messages to append to the conversation, in order. May be empty, in which case a single turn runs over the existing history with no new user message. + pub messages: Vec, + /// How to deliver the messages. `enqueue` (default) appends to the message queue. `immediate` interjects during an in-progress turn. #[serde(skip_serializing_if = "Option::is_none")] - pub max_inline_binary_bytes: Option, - /// Memory configuration for this session. + pub mode: Option, + /// If true, adds the messages to the front of the queue instead of the end #[serde(skip_serializing_if = "Option::is_none")] - pub memory: Option, - /// Initial model identifier. + pub prepend: Option, + /// Custom HTTP headers to include in outbound model requests for this turn. Merged with session-level provider headers; per-turn headers augment and overwrite session-level headers with the same key. #[serde(skip_serializing_if = "Option::is_none")] - pub model: Option, - /// Initial model capability overrides. + pub request_headers: Option>, + /// W3C Trace Context traceparent header for distributed tracing of this agent turn #[serde(skip_serializing_if = "Option::is_none")] - pub model_capabilities_overrides: Option, - /// BYOK model definitions added to the selectable model list, each referencing a provider name. - /// - ///
- /// - /// **Experimental.** This type is part of an experimental wire-protocol surface - /// and may change or be removed in future SDK or CLI releases. - /// - ///
+ pub traceparent: Option, + /// W3C Trace Context tracestate header for distributed tracing #[serde(skip_serializing_if = "Option::is_none")] - pub models: Option>, - /// Optional human-friendly session name. + pub tracestate: Option, + /// If true, await completion of the agentic loop for this turn before returning. Defaults to false (fire-and-forget). When true, the result still contains the same `messageIds`; the caller can rely on the agent having processed the messages before the call resolves. Transport-dependent tail semantics: on a LOCAL (in-process) session the wait additionally blocks until the completed turn's event tail has been dispatched to this session's in-process subscribers, so a subsequent read of subscriber state already reflects the turn; on a REMOTE session the wait resolves once the loop completes and mirrored delivery follows over the wire. Callers that need the stronger local guarantee on remote sessions should await the event stream explicitly. #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, - /// Custom model-provider configuration (BYOK). + pub wait: Option, +} + +/// Result of sending zero or more user messages +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SendMessagesResult { + /// Unique identifiers assigned to the messages, one per provided message in order. Empty when no messages were provided. + pub message_ids: Vec, +} + +/// Parameters for sending a user message to the session +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SendRequest { + /// The UI mode the agent was in when this message was sent. Defaults to the session's current mode. #[serde(skip_serializing_if = "Option::is_none")] - pub provider: Option, - /// Named BYOK provider connections, additive to CAPI auth. Combining with `provider` is rejected. - /// - ///
- /// - /// **Experimental.** This type is part of an experimental wire-protocol surface - /// and may change or be removed in future SDK or CLI releases. - /// - ///
+ pub agent_mode: Option, + /// Optional attachments (files, directories, selections, blobs, GitHub references) to include with the message #[serde(skip_serializing_if = "Option::is_none")] - pub providers: Option>, - /// Initial reasoning effort level. + pub attachments: Option>, + /// If false, this message will not trigger a Premium Request Unit charge. User messages default to billable. #[serde(skip_serializing_if = "Option::is_none")] - pub reasoning_effort: Option, - /// Initial reasoning summary mode for supported model clients. + pub billable: Option, + /// If provided, this is shown in the timeline instead of `prompt` #[serde(skip_serializing_if = "Option::is_none")] - pub reasoning_summary: Option, - /// Telemetry-only remote-defaulted flag. + pub display_prompt: Option, + /// How to deliver the message. `enqueue` (default) appends to the message queue. `immediate` interjects during an in-progress turn. #[serde(skip_serializing_if = "Option::is_none")] - pub remote_defaulted_on: Option, - /// Telemetry-only remote exporting flag. + pub mode: Option, + /// If true, adds the message to the front of the queue instead of the end #[serde(skip_serializing_if = "Option::is_none")] - pub remote_exporting: Option, - /// Whether this session supports remote steering. + pub prepend: Option, + /// The user message text + pub prompt: String, + /// Custom HTTP headers to include in outbound model requests for this turn. Merged with session-level provider headers; per-turn headers augment and overwrite session-level headers with the same key. #[serde(skip_serializing_if = "Option::is_none")] - pub remote_steerable: Option, - /// Whether the host is an interactive UI. + pub request_headers: Option>, + /// If set, the request will fail if the named tool is not available when this message is among the user messages at the start of the current exchange #[serde(skip_serializing_if = "Option::is_none")] - pub running_in_interactive_mode: Option, - /// Resolved sandbox configuration. + pub required_tool: Option, + /// Optional provenance tag copied to the resulting user.message event. Must be `user`, `system`, `command-` for command-originated messages, `schedule-` for scheduled prompts, or `agent-` for prompts sent by another agent. + #[doc(hidden)] #[serde(skip_serializing_if = "Option::is_none")] - pub sandbox_config: Option, - /// Capabilities enabled for this session. + pub(crate) source: Option, + /// W3C Trace Context traceparent header for distributed tracing of this agent turn #[serde(skip_serializing_if = "Option::is_none")] - pub session_capabilities: Option>, - /// Optional stable session identifier to use for a new session. + pub traceparent: Option, + /// W3C Trace Context tracestate header for distributed tracing #[serde(skip_serializing_if = "Option::is_none")] - pub session_id: Option, - /// Initial session limits. + pub tracestate: Option, + /// If true, await completion of the agentic loop for this message before returning. Defaults to false (fire-and-forget). When true, the result still contains the same `messageId`; the caller can rely on the agent having processed the message before the call resolves. Transport-dependent tail semantics: on a LOCAL (in-process) session the wait additionally blocks until the completed turn's event tail has been dispatched to this session's in-process subscribers, so a subsequent read of subscriber state already reflects the turn; on a REMOTE session the wait resolves once the loop completes and mirrored delivery follows over the wire. Callers that need the stronger local guarantee on remote sessions should await the event stream explicitly. #[serde(skip_serializing_if = "Option::is_none")] - pub session_limits: Option, - /// Shell init profile. + pub wait: Option, +} + +/// Result of sending a user message +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SendResult { + /// Unique identifier assigned to the message + pub message_id: String, +} + +/// Internal request for sending a system notification. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SendSystemNotificationRequest { + /// Optional structured notification kind. #[serde(skip_serializing_if = "Option::is_none")] - pub shell_init_profile: Option, - /// Per-shell process flags. + pub kind: Option, + /// Notification text to deliver to the model. + pub message: String, + /// Internal delivery options, including passive policy. #[serde(skip_serializing_if = "Option::is_none")] - pub shell_process_flags: Option>, - /// Additional directories to search for skills. + pub options: Option, +} + +/// Agents discovered across user, project, plugin, and remote sources. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ServerAgentList { + /// All discovered agents across all sources + pub agents: Vec, +} + +/// Instruction sources discovered across user, repository, and plugin sources. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ServerInstructionSourceList { + /// All discovered instruction sources + pub sources: Vec, +} + +/// Server-side skill metadata, including name, description, source, enabled/invocable state, path, project path, and argument hint. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ServerSkill { + /// Optional freeform hint describing the skill's expected arguments, from the `argument-hint` frontmatter field #[serde(skip_serializing_if = "Option::is_none")] - pub skill_directories: Option>, - /// Whether to skip custom instruction sources. + pub argument_hint: Option, + /// Canonical slash command name used to invoke the skill, without the leading '/' #[serde(skip_serializing_if = "Option::is_none")] - pub skip_custom_instructions: Option, - /// Optional trajectory output file path. + pub command_name: Option, + /// Description of what the skill does + pub description: String, + /// Whether the skill is currently enabled (based on global config) + pub enabled: bool, + /// Unique identifier for the skill + pub name: String, + /// Absolute path to the skill file #[serde(skip_serializing_if = "Option::is_none")] - pub trajectory_file: Option, - /// Initial output verbosity level for supported models. + pub path: Option, + /// The project path this skill belongs to (only for project/inherited skills) #[serde(skip_serializing_if = "Option::is_none")] - pub verbosity: Option, - /// Working directory to anchor the session. + pub project_path: Option, + /// Source location type (e.g., project, personal-copilot, plugin, builtin) + pub source: SkillSource, + /// Whether the skill can be invoked by the user as a slash command + pub user_invocable: bool, +} + +/// Skills discovered across global and project sources. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ServerSkillList { + /// Messages for skills that failed to load (e.g. malformed SKILL.md). Empty when host skills are excluded so host-local paths are not disclosed to multitenant callers. #[serde(skip_serializing_if = "Option::is_none")] - pub working_directory: Option, - /// Pre-resolved working-directory context for session startup. + pub errors: Option>, + /// All discovered skills across all sources + pub skills: Vec, +} + +/// Current activity flags for the session. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionActivity { + /// Whether an in-flight operation can currently be aborted. + pub abortable: bool, + /// Whether the session currently has active work, including running turns or tasks. + pub has_active_work: bool, +} + +/// Authentication status and account metadata for the session. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionAuthStatus { + /// Authentication type + #[serde(skip_serializing_if = "Option::is_none")] + pub auth_type: Option, + /// Copilot plan tier (e.g., individual_pro, business) + #[serde(skip_serializing_if = "Option::is_none")] + pub copilot_plan: Option, + /// Authentication host URL + #[serde(skip_serializing_if = "Option::is_none")] + pub host: Option, + /// Whether the session has resolved authentication + pub is_authenticated: bool, + /// Authenticated login/username, if available + #[serde(skip_serializing_if = "Option::is_none")] + pub login: Option, + /// Human-readable authentication status description + #[serde(skip_serializing_if = "Option::is_none")] + pub status_message: Option, +} + +/// Map of sessionId -> bytes freed by removing the session's workspace directory. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionBulkDeleteResult { + /// Map of sessionId -> bytes freed by removing the session's workspace directory. Sessions whose deletion failed are omitted from this map (failures are logged on the server but not surfaced per-id; check the map for absent IDs to detect them). + pub freed_bytes: HashMap, +} + +/// The six normalized `/context` header buckets, computed from the same tokenization as `entries` so the two never disagree. Convenience rollups: `freeSpace` and `buffer` describe window capacity rather than occupied context, so the values do not sum to `totalTokens`. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionContextAttributionCategories { + /// Output reserve plus post-blocking-threshold buffer. + pub buffer: i64, + /// Custom-instructions tokens (0 when none are configured). + pub custom_instructions: i64, + /// Remaining unused window capacity (clamped at 0). + pub free_space: i64, + /// MCP tool-definition tokens. + pub mcp_tools: i64, + /// Conversation (user/assistant/tool) message tokens. + pub messages: i64, + /// System prompt tokens, excluding custom instructions. + pub system_prompt: i64, + /// Non-MCP tool-definition tokens. + pub system_tools: i64, +} + +/// Successful compaction history for the session. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionContextAttributionCompactions { + /// Number of successful compactions in this session. + pub count: i64, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionContextAttributionEntriesItem { + /// Supplementary per-entry metadata (e.g. `messageCount`, `role`, `evictable`, `pluginSource`). Values are stringified; parse as needed and ignore unrecognized keys. + #[serde(skip_serializing_if = "Option::is_none")] + pub attributes: Option>, + /// Identifier for this entry, formed by joining its `kind` and source name (e.g. `tool:bash`, `skill:tmux`, `toolDefinition:bash`); unique within the snapshot. Use it to match the same entry across snapshots, to correlate with other APIs (skill/agent/MCP registries), and as the `parentId` target for nesting. Distinct from the human-facing `label`. + pub id: String, + /// Source category for this entry. Not a closed set — tolerate unknown values. Known values today: `skill`, `subagent`, `mcpServer`, `tool`, `system`, `toolDefinition`, `plugin`. + pub kind: String, + /// Human-readable display label, e.g. `bash` or `skill: tmux`. Presentation-only; may be localized/reformatted without notice — do not key off it. + pub label: String, + /// Optional `id` of the parent entry: e.g. a `plugin` entry parenting its `skill`/`mcpServer` entries, or the `system` entry parenting `toolDefinition` entries. Omitted for top-level entries. + #[serde(skip_serializing_if = "Option::is_none")] + pub parent_id: Option, + /// Token count currently in context attributable to this entry. + pub tokens: i64, +} + +/// Per-source context-window attribution, or null if the session has not yet been initialized (no system prompt or tool metadata cached). +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionContextAttribution { + /// Output reserve plus the tokens past the buffer-exhaustion blocking threshold. Mirrors `SessionContextInfo.bufferTokens`. + pub buffer_tokens: i64, + /// The six normalized `/context` header buckets, computed from the same tokenization as `entries` so the two never disagree. Convenience rollups: `freeSpace` and `buffer` describe window capacity rather than occupied context, so the values do not sum to `totalTokens`. + pub categories: SessionContextAttributionCategories, + /// Successful compaction history for the session. + pub compactions: SessionContextAttributionCompactions, + /// Token count at which background compaction starts. Mirrors `SessionContextInfo.compactionThreshold`. + pub compaction_threshold: i64, + /// Flat list of per-source attribution entries. Group by `kind` and render unrecognized kinds generically. Nesting and rollups are expressed via `parentId`. + pub entries: Vec, + /// Prompt limit plus the model's output reserve: the full context window `categories.freeSpace` and `categories.buffer` are measured against. Mirrors `SessionContextInfo.limit`. + pub limit: i64, + /// The concrete model id the entire breakdown was tokenized against (feeds the per-model token multiplier). Under `Auto` (Free/Student) this is the resolved model, not the literal `auto` sentinel, so totals are not undercounted. A single-model approximation of a potentially multi-model Auto session. + pub model_id: String, + /// How `modelId` was chosen. Not a closed set — tolerate unknown values. Known values today: `autoResolved` (the model Auto resolved to), `selected` (the user's explicitly selected model), `default` (a fallback before any model is known). + pub model_source: String, + /// Maximum prompt tokens the resolved model accepts — the denominator for a `##k/###k` context-usage display. Mirrors `SessionContextInfo.promptTokenLimit`. + pub prompt_token_limit: i64, + /// Total token count of the current context window the entries are measured against (system message + conversation messages + tool definitions — the same total reported by /context). Divide an entry's `tokens` by this to derive its share. + pub total_tokens: i64, +} + +/// Token breakdown for the current context window, or null if the session has not yet been initialized (no system prompt or tool metadata cached). +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionContextInfo { + /// Output reserve plus tokens after the buffer-exhaustion blocking threshold (default 95%) + pub buffer_tokens: i64, + /// Token count at which background compaction starts (configurable percentage of promptTokenLimit) + pub compaction_threshold: i64, + /// Tokens consumed by user/assistant/tool messages + pub conversation_tokens: i64, + /// Prompt token limit plus the model's full output token limit. + pub limit: i64, + /// Tokens consumed by MCP tool definitions (subset of toolDefinitionsTokens, excludes deferred tools) + pub mcp_tools_tokens: i64, + /// The model used for token counting + pub model_name: String, + /// Maximum prompt tokens allowed by the model (or DEFAULT_TOKEN_LIMIT if unspecified) + pub prompt_token_limit: i64, + /// Tokens consumed by the system prompt + pub system_tokens: i64, + /// Tokens consumed by tool definitions sent to the model (excludes deferred tools) + pub tool_definitions_tokens: i64, + /// Sum of system, conversation and tool-definition tokens + pub total_tokens: i64, +} + +/// The enriched metadata records, with summary and context fields backfilled where available. Sessions confirmed empty and unnamed are omitted. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionEnrichMetadataResult { + /// Enriched records, with summary and context backfilled. Sessions confirmed empty and unnamed may be omitted. + pub sessions: Vec, +} + +/// File path, content to append, and optional mode for the client-provided session filesystem. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFsAppendFileRequest { + /// Target session identifier + pub session_id: SessionId, + /// Path using SessionFs conventions + pub path: String, + /// Content to append + pub content: String, + /// Optional POSIX-style mode for newly created files + #[serde(skip_serializing_if = "Option::is_none")] + pub mode: Option, +} + +/// Describes a filesystem error. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFsError { + /// Error classification + pub code: SessionFsErrorCode, + /// Free-form detail about the error, for logging/diagnostics + #[serde(skip_serializing_if = "Option::is_none")] + pub message: Option, +} + +/// Path to test for existence in the client-provided session filesystem. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFsExistsRequest { + /// Target session identifier + pub session_id: SessionId, + /// Path using SessionFs conventions + pub path: String, +} + +/// Indicates whether the requested path exists in the client-provided session filesystem. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFsExistsResult { + /// Whether the path exists + pub exists: bool, +} + +/// Directory path to create in the client-provided session filesystem, with options for recursive creation and POSIX mode. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFsMkdirRequest { + /// Target session identifier + pub session_id: SessionId, + /// Path using SessionFs conventions + pub path: String, + /// Create parent directories as needed + #[serde(skip_serializing_if = "Option::is_none")] + pub recursive: Option, + /// Optional POSIX-style mode for newly created directories + #[serde(skip_serializing_if = "Option::is_none")] + pub mode: Option, +} + +/// Directory path whose entries should be listed from the client-provided session filesystem. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFsReaddirRequest { + /// Target session identifier + pub session_id: SessionId, + /// Path using SessionFs conventions + pub path: String, +} + +/// Names of entries in the requested directory, or a filesystem error if the read failed. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFsReaddirResult { + /// Entry names in the directory + pub entries: Vec, + /// Describes a filesystem error. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, +} + +/// Directory entry returned by session filesystem `readdirWithTypes`, with name and entry type. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFsReaddirWithTypesEntry { + /// Entry name + pub name: String, + /// Entry type + pub r#type: SessionFsReaddirWithTypesEntryType, +} + +/// Directory path whose entries (with type information) should be listed from the client-provided session filesystem. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFsReaddirWithTypesRequest { + /// Target session identifier + pub session_id: SessionId, + /// Path using SessionFs conventions + pub path: String, +} + +/// Entries in the requested directory paired with file/directory type information, or a filesystem error if the read failed. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFsReaddirWithTypesResult { + /// Directory entries with type information + pub entries: Vec, + /// Describes a filesystem error. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, +} + +/// Path of the file to read from the client-provided session filesystem. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFsReadFileRequest { + /// Target session identifier + pub session_id: SessionId, + /// Path using SessionFs conventions + pub path: String, +} + +/// File content as a UTF-8 string, or a filesystem error if the read failed. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFsReadFileResult { + /// File content as UTF-8 string + pub content: String, + /// Describes a filesystem error. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, +} + +/// Source and destination paths for renaming or moving an entry in the client-provided session filesystem. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFsRenameRequest { + /// Target session identifier + pub session_id: SessionId, + /// Source path using SessionFs conventions + pub src: String, + /// Destination path using SessionFs conventions + pub dest: String, +} + +/// Path to remove from the client-provided session filesystem, with options for recursive removal and force. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFsRmRequest { + /// Target session identifier + pub session_id: SessionId, + /// Path using SessionFs conventions + pub path: String, + /// Remove directories and their contents recursively + #[serde(skip_serializing_if = "Option::is_none")] + pub recursive: Option, + /// Ignore errors if the path does not exist + #[serde(skip_serializing_if = "Option::is_none")] + pub force: Option, +} + +/// Optional capabilities declared by the provider +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFsSetProviderCapabilities { + /// Whether the provider supports SQLite query/exists operations + #[serde(skip_serializing_if = "Option::is_none")] + pub sqlite: Option, +} + +/// Initial working directory, session-state path layout, and path conventions used to register the calling SDK client as the session filesystem provider. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFsSetProviderRequest { + /// Optional capabilities declared by the provider + #[serde(skip_serializing_if = "Option::is_none")] + pub capabilities: Option, + /// Path conventions used by this filesystem + pub conventions: SessionFsSetProviderConventions, + /// Initial working directory for sessions + pub initial_cwd: String, + /// Path within each session's SessionFs where the runtime stores files for that session + pub session_state_path: String, +} + +/// Indicates whether the calling client was registered as the session filesystem provider. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFsSetProviderResult { + /// Whether the provider was set successfully + pub success: bool, +} + +/// Indicates whether the per-session SQLite database already exists. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFsSqliteExistsResult { + /// Whether the session database already exists + pub exists: bool, +} + +/// SQL query, query type, and optional bind parameters for executing a SQLite query against the per-session database. The provider applies its SQLite busy timeout for every call. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFsSqliteQueryRequest { + /// Target session identifier + pub session_id: SessionId, + /// SQL query to execute + pub query: String, + /// How to execute the query: 'exec' for DDL/multi-statement (no results), 'query' for SELECT (returns rows), 'run' for INSERT/UPDATE/DELETE (returns rowsAffected) + pub query_type: SessionFsSqliteQueryType, + /// Optional named bind parameters + #[serde(skip_serializing_if = "Option::is_none")] + pub params: Option>, +} + +/// Query results including rows, columns, and rows affected, or a filesystem error if execution failed. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFsSqliteQueryResult { + /// Column names from the result set + pub columns: Vec, + /// Describes a filesystem error. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + /// SQLite last_insert_rowid() value for INSERT. + #[serde(skip_serializing_if = "Option::is_none")] + pub last_insert_rowid: Option, + /// For SELECT: array of row objects. For others: empty array. + pub rows: Vec>, + /// Number of rows affected (for INSERT/UPDATE/DELETE) + pub rows_affected: i64, +} + +/// Classified SQLite transaction failure. busyOrLocked guarantees rollback; postCommitAmbiguous must never be retried. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFsSqliteTransactionError { + pub error_class: SessionFsSqliteTransactionErrorClass, + pub message: String, +} + +/// One statement in an atomic SQLite transaction. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFsSqliteTransactionStatement { + /// Optional named bind parameters. + #[serde(skip_serializing_if = "Option::is_none")] + pub params: Option>, + /// SQL statement to execute. + pub query: String, + /// How to execute the statement. + pub query_type: SessionFsSqliteQueryType, +} + +/// Statements to execute atomically. Providers apply busy handling for every call. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFsSqliteTransactionRequest { + /// Target session identifier + pub session_id: SessionId, + pub statements: Vec, +} + +/// Per-statement results, or a classified transaction error. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFsSqliteTransactionResult { + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + pub results: Vec, +} + +/// Path whose metadata should be returned from the client-provided session filesystem. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFsStatRequest { + /// Target session identifier + pub session_id: SessionId, + /// Path using SessionFs conventions + pub path: String, +} + +/// Filesystem metadata for the requested path, or a filesystem error if the stat failed. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFsStatResult { + /// ISO 8601 timestamp of creation + pub birthtime: String, + /// Describes a filesystem error. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + /// Whether the path is a directory + pub is_directory: bool, + /// Whether the path is a file + pub is_file: bool, + /// ISO 8601 timestamp of last modification + pub mtime: String, + /// File size in bytes + pub size: i64, +} + +/// File path, content to write, and optional mode for the client-provided session filesystem. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFsWriteFileRequest { + /// Target session identifier + pub session_id: SessionId, + /// Path using SessionFs conventions + pub path: String, + /// Content to write + pub content: String, + /// Optional POSIX-style mode for newly created files + #[serde(skip_serializing_if = "Option::is_none")] + pub mode: Option, +} + +/// Installed plugin record for a session, with marketplace, version, install time, enabled state, cache path, and source. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionInstalledPlugin { + /// Path where the plugin is cached locally + #[serde(rename = "cache_path", skip_serializing_if = "Option::is_none")] + pub cache_path: Option, + /// Whether the plugin is currently enabled + pub enabled: bool, + /// Installation timestamp (ISO-8601) + #[serde(rename = "installed_at")] + pub installed_at: String, + /// Marketplace the plugin came from (empty string for direct repo installs) + pub marketplace: String, + /// Plugin name + pub name: String, + /// Source descriptor for direct repo installs (when marketplace is empty) + #[serde(skip_serializing_if = "Option::is_none")] + pub source: Option, + /// Per-plugin source fingerprint (a SHA-256 hash of the plugin's catalog source spec plus its resolved source subtree — NOT a Git commit SHA) captured at marketplace install/update time. Auto-update compares it against the freshly recomputed fingerprint to detect a content change that does not bump the version. Absent for pre-existing installs and for direct (non-marketplace) installs. + #[serde(rename = "source_sha", skip_serializing_if = "Option::is_none")] + pub source_sha: Option, + /// Installed version, if known + #[serde(skip_serializing_if = "Option::is_none")] + pub version: Option, +} + +/// Source descriptor for a direct GitHub plugin install, with `owner/repo`, optional ref or full commit SHA, and optional subpath. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionInstalledPluginSourceGitHub { + #[serde(skip_serializing_if = "Option::is_none")] + pub path: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub r#ref: Option, + pub repo: String, + /// Optional full 40-character hexadecimal commit SHA. + #[serde(skip_serializing_if = "Option::is_none")] + pub sha: Option, + /// Constant value. Always "github". + pub source: SessionInstalledPluginSourceGitHubSource, +} + +/// Source descriptor for a direct local plugin install, with a local filesystem path. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionInstalledPluginSourceLocal { + pub path: String, + /// Constant value. Always "local". + pub source: SessionInstalledPluginSourceLocalSource, +} + +/// Source descriptor for a direct URL plugin install, with URL, optional ref or full commit SHA, and optional subpath. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionInstalledPluginSourceUrl { + #[serde(skip_serializing_if = "Option::is_none")] + pub path: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub r#ref: Option, + /// Optional full 40-character hexadecimal commit SHA. + #[serde(skip_serializing_if = "Option::is_none")] + pub sha: Option, + /// Constant value. Always "url". + pub source: SessionInstalledPluginSourceUrlSource, + pub url: String, +} + +/// Baseline data provenance for a prediction. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionLimitPredictionBaselineData { + /// End of the baseline data slice. + pub window_end: String, + /// Start of the baseline data slice. + pub window_start: String, +} + +/// Semantic usage tier and its AI-credit cap. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionLimitPredictionTierOption { + /// AI-credit cap for this tier. + pub cap: f64, + pub tier: SessionLimitPredictionTier, +} + +/// Explainable AI-credit session-limit prediction. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionLimitPredictionDetails { + /// Baseline data provenance. + pub baseline_data: SessionLimitPredictionBaselineData, + /// Client population used for the prediction. + pub client_type: SessionLimitPredictionClientType, + /// Resolved model family when known. + #[serde(skip_serializing_if = "Option::is_none")] + pub family: Option, + /// Model identifier used for lookup. + pub model_id: String, + /// Recommended maximum AI credits for this session. + pub recommended_cap: f64, + /// Tier chosen as the recommended cap. + pub recommended_tier: SessionLimitPredictionTier, + /// Baseline fallback level used to create the prediction. + pub source: SessionLimitPredictionSource, + /// Key matched at the source level, such as a model id, family id, or `global`. + pub source_key: String, + /// Ordered usage tiers and their AI-credit caps. + pub tiers: Vec, +} + +/// Parameters for predicting an AI-credit session limit. Omitting `modelId` uses the session's currently selected model. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionLimitPredictionRequest { + /// Client type to size for. Defaults to `cli-interactive`. + #[serde(skip_serializing_if = "Option::is_none")] + pub client_type: Option, + /// Optional model identifier override. If omitted, the session's current model is used. + #[serde(skip_serializing_if = "Option::is_none")] + pub model_id: Option, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionLimitPredictionResultAvailable { + pub kind: SessionLimitPredictionResultAvailableKind, + /// Predicted session limit details. + pub prediction: SessionLimitPredictionDetails, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionLimitPredictionResultUnavailable { + pub kind: SessionLimitPredictionResultUnavailableKind, + /// Reason no prediction is available. + pub reason: SessionLimitPredictionUnavailableReason, +} + +/// Sessions matching the filter, ordered most-recently-modified first. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionList { + /// Sessions ordered most-recently-modified first. Discriminated by `isRemote`. + pub sessions: Vec, +} + +/// Optional filter applied to the returned sessions +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionListFilter { + /// Match sessions whose context.branch equals this value + #[serde(skip_serializing_if = "Option::is_none")] + pub branch: Option, + /// Match sessions whose context.cwd equals this value + #[serde(skip_serializing_if = "Option::is_none")] + pub cwd: Option, + /// Match sessions whose context.gitRoot equals this value + #[serde(skip_serializing_if = "Option::is_none")] + pub git_root: Option, + /// Match sessions whose context.repository equals this value + #[serde(skip_serializing_if = "Option::is_none")] + pub repository: Option, +} + +/// Queued repo-level startup prompts and the total hook command count after loading. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionLoadDeferredRepoHooksResult { + /// Total hook command count (user + plugin + repo) loaded for the session by this call. Captured atomically with startupPrompts so callers don't need to read a separate counter. + pub hook_count: i64, + /// Repo-level startup prompts queued from repo hook configs. Empty on resume, when no repo configs were pending, or when disableAllHooks is set. + pub startup_prompts: Vec, +} + +/// Enterprise permission policy expressed with the runtime's managed permission-rule syntax. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionManagedPermissions { + /// Permission rules that allow matching operations unless another managed source, deny, or ask rule restricts them. + #[serde(skip_serializing_if = "Option::is_none")] + pub allow: Option>, + /// Permission rules that require explicit human approval. + #[serde(skip_serializing_if = "Option::is_none")] + pub ask: Option>, + /// Permission rules that block matching operations. Deny has highest precedence. + #[serde(skip_serializing_if = "Option::is_none")] + pub deny: Option>, + /// When set to `disable`, prevents bypass/allow-all permission modes. + #[serde(skip_serializing_if = "Option::is_none")] + pub disable_bypass_permissions_mode: Option, +} + +/// Managed settings an SDK host may inject at session startup. Only permissions are accepted in this initial contract. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionManagedSettings { + #[serde(skip_serializing_if = "Option::is_none")] + pub permissions: Option, +} + +/// Public-facing projection of workspace metadata for SDK / TUI consumers +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionMetadataSnapshotWorkspace { + /// Branch checked out at session start, if any + #[serde(skip_serializing_if = "Option::is_none")] + pub branch: Option, + /// ISO 8601 timestamp when the workspace was created + #[serde(rename = "created_at", skip_serializing_if = "Option::is_none")] + pub created_at: Option, + /// Current working directory at session start + #[serde(skip_serializing_if = "Option::is_none")] + pub cwd: Option, + /// Resolved git root for cwd, if any + #[serde(rename = "git_root", skip_serializing_if = "Option::is_none")] + pub git_root: Option, + /// Repository host type, if known + #[serde(rename = "host_type", skip_serializing_if = "Option::is_none")] + pub host_type: Option, + /// Workspace identifier (1:1 with sessionId) + pub id: String, + /// Display name for the session, if set + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + /// Repository identifier in 'owner/repo' or 'org/project/repo' format, if any + #[serde(skip_serializing_if = "Option::is_none")] + pub repository: Option, + /// ISO 8601 timestamp when the workspace was last updated + #[serde(rename = "updated_at", skip_serializing_if = "Option::is_none")] + pub updated_at: Option, + /// Whether the display name was explicitly set by the user + #[serde(rename = "user_named", skip_serializing_if = "Option::is_none")] + pub user_named: Option, +} + +/// Point-in-time snapshot of slow-changing session identifier and state fields +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionMetadataSnapshot { + /// True when the session was detected to be in use by another process at construction time. Local consumers may surface a confirmation prompt before fully attaching. Always false for new sessions. + pub already_in_use: bool, + /// Runtime client name associated with the session (telemetry identifier). + #[serde(skip_serializing_if = "Option::is_none")] + pub client_name: Option, + /// The current agent mode for this session (e.g., 'interactive', 'plan', 'autopilot') + pub current_mode: MetadataSnapshotCurrentMode, + /// User-provided name supplied at session construction (via `--name`), if any. Immutable after construction. + #[serde(skip_serializing_if = "Option::is_none")] + pub initial_name: Option, + /// Whether this is a remote session (i.e., one whose runtime executes elsewhere and is steered through this process) + pub is_remote: bool, + /// ISO 8601 timestamp of when the session's persisted state was last modified on disk. For new sessions, equals startTime. For resumed sessions, reflects the previous modification time at construction. + pub modified_time: String, + /// Remote-session-specific metadata. Populated only when `isRemote` is true. Fields are immutable for the lifetime of the session. + #[serde(skip_serializing_if = "Option::is_none")] + pub remote_metadata: Option, + /// Currently selected model identifier, if any + #[serde(skip_serializing_if = "Option::is_none")] + pub selected_model: Option, + /// The unique identifier of the session + pub session_id: SessionId, + /// Current session limits, or null when no limits are active + pub session_limits: Option, + /// ISO 8601 timestamp of when the session started + pub start_time: String, + /// Short human-readable summary of the session, if known. Omitted when no summary has been generated. + #[serde(skip_serializing_if = "Option::is_none")] + pub summary: Option, + /// Absolute path to the session's current working directory + pub working_directory: String, + /// Public-facing workspace metadata for this session, or null if the session has no associated workspace. Excludes runtime-internal fields (GitHub IDs, summary count, internal flags). + pub workspace: Option, + /// Absolute path to the session's workspace directory on disk, or null if the session has no associated workspace + pub workspace_path: Option, +} + +/// Cost-category metadata for a CAPI model. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionModelPriceCategory { + pub id: String, + pub price_category: ModelPickerPriceCategory, +} + +/// The list of models available to this session. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionModelList { + /// Available models, ordered with the most preferred default first. Includes both Copilot (CAPI) models and any registry BYOK models; a BYOK model appears under its provider-qualified selection id (`provider/id`). + pub list: Vec, + /// Cost categories for the full CAPI catalog, including picker-disabled models that Auto may select. Metadata only; entries absent from `list` are not manually selectable. + #[serde(skip_serializing_if = "Option::is_none")] + pub model_price_categories: Option>, + /// Per-quota snapshots returned alongside the model list, keyed by quota type. + #[serde(skip_serializing_if = "Option::is_none")] + pub quota_snapshots: Option>, +} + +/// Source descriptor for a `sessions.open` content-exclusion rule, with source name and type. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource { + pub name: String, + pub r#type: String, +} + +/// Single content-exclusion rule supplied to `sessions.open` options, with paths, match conditions, and source. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionOpenOptionsAdditionalContentExclusionPolicyRule { + #[serde(skip_serializing_if = "Option::is_none")] + pub if_any_match: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub if_none_match: Option>, + pub paths: Vec, + /// Source descriptor for a `sessions.open` content-exclusion rule, with source name and type. + pub source: SessionOpenOptionsAdditionalContentExclusionPolicyRuleSource, +} + +/// Content-exclusion policy supplied to `sessions.open` options, with rules, last-updated data, and scope. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionOpenOptionsAdditionalContentExclusionPolicy { + #[serde(rename = "last_updated_at")] + pub last_updated_at: serde_json::Value, + pub rules: Vec, + /// Allowed values for the `SessionOpenOptionsAdditionalContentExclusionPolicyScope` enumeration. + pub scope: SessionOpenOptionsAdditionalContentExclusionPolicyScope, +} + +/// A host-provided script sourced before each built-in shell command when its shell target matches the active shell. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ShellInitScript { + /// Path to the script to source. + pub path: String, + /// Built-in shell that may source this script. + pub shell: ShellInitScriptShell, +} + +/// Per-session settings for built-in shell tools. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ShellOptions { + /// Controls automatic non-interactive profile loading where supported. Explicit initScripts are unaffected. + #[serde(skip_serializing_if = "Option::is_none")] + pub init_profile: Option, + /// Ordered host-provided script paths sourced before each built-in shell command when the + /// entry's shell target matches the active shell. Use these for rc files, environment setup scripts, + /// or other custom scripts. A script that returns a nonzero status is reported, and later scripts + /// and the user command continue while the shell remains running. Because scripts are sourced into + /// the command shell, `exit`, `exec`, failures under `set -e`, or other shell-terminating behavior + /// can prevent continuation. Script standard output is preserved; Bash script stderr is discarded, + /// PowerShell exception messages are replaced, and runtime-generated failure notices omit + /// configured script paths. When sandboxing is enabled, each script must already be readable under + /// the active sandbox filesystem policy. Pass an empty array to clear the list. + #[serde(skip_serializing_if = "Option::is_none")] + pub init_scripts: Option>, + /// Flags passed to the active built-in shell process on startup, replacing its default flags. + /// When omitted, the built-in Bash shell uses `--norc --noprofile`, + /// and the built-in PowerShell shell uses `-NoProfile -NoLogo`. + #[serde(skip_serializing_if = "Option::is_none")] + pub process_flags: Option>, +} + +/// Session construction options. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionOpenOptions { + /// Additional content-exclusion policies to merge into the session policy set. + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(skip_serializing_if = "Option::is_none")] + pub additional_content_exclusion_policies: + Option>, + /// Additional directories the agent may access beyond the working directory. Each entry is granted to the session's file-access allow-list and surfaced to the model (system prompt context and `@`-mention completion). Absolute paths are recommended; a relative path is resolved against the session's working directory. Nonexistent or unresolvable entries are skipped with a warning. This is applied on both session creation and resume, and is not persisted: a resumed session that omits this option does not retain previously supplied directories (re-supply them, exactly as the CLI re-passes `--add-dir`). + #[serde(skip_serializing_if = "Option::is_none")] + pub additional_directories: Option>, + /// Runtime context discriminator for agent filtering. + #[serde(skip_serializing_if = "Option::is_none")] + pub agent_context: Option, + /// Whether to include instructions from every MCP server in the system prompt instead of only allowlisted servers. + #[serde(skip_serializing_if = "Option::is_none")] + pub allow_all_mcp_server_instructions: Option, + /// Whether ask_user is explicitly disabled. + #[serde(skip_serializing_if = "Option::is_none")] + pub ask_user_disabled: Option, + /// Initial authentication info for the session. + #[serde(skip_serializing_if = "Option::is_none")] + pub auth_info: Option, + /// Allowlist of available tool names. + #[serde(skip_serializing_if = "Option::is_none")] + pub available_tools: Option>, + /// Options scoped to the built-in CAPI (Copilot API) provider. + #[serde(skip_serializing_if = "Option::is_none")] + pub capi: Option, + /// Structured client kind used for runtime behavior gates. + #[serde(skip_serializing_if = "Option::is_none")] + pub client_kind: Option, + /// Identifier of the client driving the session. + #[serde(skip_serializing_if = "Option::is_none")] + pub client_name: Option, + /// Whether commit-message coauthor trailers are enabled. + #[serde(skip_serializing_if = "Option::is_none")] + pub coauthor_enabled: Option, + /// Override Copilot configuration directory. + #[serde(skip_serializing_if = "Option::is_none")] + pub config_dir: Option, + /// Whether auto-mode continuation is enabled. + #[serde(skip_serializing_if = "Option::is_none")] + pub continue_on_auto_mode: Option, + /// Override URL for the Copilot API endpoint. + #[serde(skip_serializing_if = "Option::is_none")] + pub copilot_url: Option, + /// Whether custom agents default to local-only execution. + #[serde(skip_serializing_if = "Option::is_none")] + pub custom_agents_local_only: Option, + /// Parent engagement ID for detached child telemetry rollup. + #[serde(skip_serializing_if = "Option::is_none")] + pub detached_from_spawning_parent_engagement_id: Option, + /// Parent session ID for detached child telemetry rollup. + #[serde(skip_serializing_if = "Option::is_none")] + pub detached_from_spawning_parent_session_id: Option, + /// Instruction source IDs disabled for this session. + #[serde(skip_serializing_if = "Option::is_none")] + pub disabled_instruction_sources: Option>, + /// MCP server names disabled for this session. Disabled servers are not started or authenticated on create or cold resume. + #[serde(skip_serializing_if = "Option::is_none")] + pub disabled_mcp_servers: Option>, + /// Skill IDs disabled for this session. + #[serde(skip_serializing_if = "Option::is_none")] + pub disabled_skills: Option>, + /// Experimental: enable native model citations (Anthropic models today), normalized onto the `assistant.message` event. Off by default; may change or be removed while the citations surface is experimental. + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(skip_serializing_if = "Option::is_none")] + pub enable_citations: Option, + /// Opt in to capturing file changes for session rewind and session diff. Capture cannot reconstruct changes made before it was enabled. On create it starts capture from the first turn. It is also honored on resume: for a session that already has tracked prior turns, tracking continues automatically even if this is omitted; passing it on resume additionally enables tracking for an eligible session that has no prior root turn yet. Resuming a session whose prior root turns were never tracked has no restorable baseline, so tracking stays disabled for it and rewind reports file change tracking as unavailable; the resume itself still succeeds, so sessions that predate tracking remain loadable. The opt-in is only rejected when the session can never track (a subagent session, or one without local session storage). It is intentionally absent from the mutable options update because enabling it after edits have occurred would create an incomplete, misleading baseline. Subagents share the parent session's capture store and are not tracked as separate rewind points: a file a subagent writes is attributed to whichever root user turn was open when the capture was staged, just before the tool body ran. A turn cannot open while a staged capture is still in flight, so a subagent tool that staged under the spawning turn stays attributed to it however late the write lands, while a capture it stages after the user's next message belongs to that later turn. Attribution decides which turn's rewind point counts and file preview include that write; it does not narrow which rewinds revert it, because a rewind restores every capture from the selected turn onward, so the earlier spawning turn reverts it as well. + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_file_change_tracking: Option, + /// Opt-in: self-fetch and enforce enterprise managed settings at session bootstrap. + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_managed_settings: Option, + /// Whether on-demand custom instruction discovery is enabled. + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_on_demand_instruction_discovery: Option, + /// Whether shell-script safety heuristics are enabled. + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_script_safety: Option, + /// Whether model responses stream as delta events. + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_streaming: Option, + /// How MCP server environment values are interpreted. + #[serde(skip_serializing_if = "Option::is_none")] + pub env_value_mode: Option, + /// Override directory for session event logs. + #[serde(skip_serializing_if = "Option::is_none")] + pub events_log_directory: Option, + /// Whether subagent callback events should be forwarded into the session event log sink. + #[serde(skip_serializing_if = "Option::is_none")] + pub events_log_includes_subagents: Option, + /// Built-in subagent names to exclude from this session. Excluded built-ins are hidden from agent discovery and cannot be dispatched unless a custom agent with the same name is available. + #[serde(skip_serializing_if = "Option::is_none")] + pub excluded_builtin_agents: Option>, + /// Denylist of tool names. + #[serde(skip_serializing_if = "Option::is_none")] + pub excluded_tools: Option>, + /// ExP assignment ('flight') data injected by an SDK integrator, in the same JSON shape the Copilot CLI fetches from the experimentation service (CopilotExpAssignmentResponse). When supplied this is fed into the FeatureFlagService exactly like CLI-fetched assignments and ExP-backed flags wait for it. When absent the session does not block on ExP. + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) exp_assignments: Option, + /// Feature-flag values resolved by the host. + #[serde(skip_serializing_if = "Option::is_none")] + pub feature_flags: Option>, + /// Built-in subagent names to include in this session. When specified, only these built-ins are available, subject to runtime availability and exclusions. Custom agents with the same name remain available. + #[serde(skip_serializing_if = "Option::is_none")] + pub included_builtin_agents: Option>, + /// Installed plugins visible to the session. + #[serde(skip_serializing_if = "Option::is_none")] + pub installed_plugins: Option>, + /// Stable integration identifier for analytics. + #[serde(skip_serializing_if = "Option::is_none")] + pub integration_id: Option, + /// Whether experimental behavior is enabled. + #[serde(skip_serializing_if = "Option::is_none")] + pub is_experimental_mode: Option, + /// Whether interactive shell sessions are logged. + #[serde(skip_serializing_if = "Option::is_none")] + pub log_interactive_shells: Option, + /// Identifier sent to LSP-style integrations. + #[serde(skip_serializing_if = "Option::is_none")] + pub lsp_client_name: Option, + /// Permissions-only enterprise policy injected by the SDK host at session create or resume. Composes restrictively with self-fetched and device policy and is not persisted. + #[serde(skip_serializing_if = "Option::is_none")] + pub managed_settings: Option, + /// Maximum decoded byte size of a single inline model-facing binary tool result persisted in session events (default 10 MB). + #[serde(skip_serializing_if = "Option::is_none")] + pub max_inline_binary_bytes: Option, + /// Memory configuration for this session. + #[serde(skip_serializing_if = "Option::is_none")] + pub memory: Option, + /// Initial model identifier. + #[serde(skip_serializing_if = "Option::is_none")] + pub model: Option, + /// Initial model capability overrides. + #[serde(skip_serializing_if = "Option::is_none")] + pub model_capabilities_overrides: Option, + /// BYOK model definitions added to the selectable model list, each referencing a provider name. + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(skip_serializing_if = "Option::is_none")] + pub models: Option>, + /// Optional human-friendly session name. + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + /// Custom model-provider configuration (BYOK). + #[serde(skip_serializing_if = "Option::is_none")] + pub provider: Option, + /// Named BYOK provider connections, additive to CAPI auth. Combining with `provider` is rejected. + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(skip_serializing_if = "Option::is_none")] + pub providers: Option>, + /// Initial reasoning effort level. CAPI values are model-defined and validated against the selected model; BYOK providers may define additional values. When omitted, no effort override is applied. + #[serde(skip_serializing_if = "Option::is_none")] + pub reasoning_effort: Option, + /// Initial reasoning summary mode for supported model clients. + #[serde(skip_serializing_if = "Option::is_none")] + pub reasoning_summary: Option, + /// Telemetry-only remote-defaulted flag. + #[serde(skip_serializing_if = "Option::is_none")] + pub remote_defaulted_on: Option, + /// Telemetry-only remote exporting flag. + #[serde(skip_serializing_if = "Option::is_none")] + pub remote_exporting: Option, + /// Whether this session supports remote steering. + #[serde(skip_serializing_if = "Option::is_none")] + pub remote_steerable: Option, + /// Whether the host is an interactive UI. + #[serde(skip_serializing_if = "Option::is_none")] + pub running_in_interactive_mode: Option, + /// Resolved sandbox configuration. + #[serde(skip_serializing_if = "Option::is_none")] + pub sandbox_config: Option, + /// Capabilities enabled for this session. + #[serde(skip_serializing_if = "Option::is_none")] + pub session_capabilities: Option>, + /// Optional stable session identifier to use for a new session. + #[serde(skip_serializing_if = "Option::is_none")] + pub session_id: Option, + /// Initial session limits. + #[serde(skip_serializing_if = "Option::is_none")] + pub session_limits: Option, + /// Per-session settings for built-in shell tools. + #[serde(skip_serializing_if = "Option::is_none")] + pub shell: Option, + /// Use shell.initProfile instead. Shell init profile. + #[doc(hidden)] + #[deprecated] + #[serde(skip_serializing_if = "Option::is_none")] + pub shell_init_profile: Option, + /// PowerShell process flags applied to built-in and user-requested shell commands. + #[serde(skip_serializing_if = "Option::is_none")] + pub shell_process_flags: Option>, + /// Additional directories to search for skills. + #[serde(skip_serializing_if = "Option::is_none")] + pub skill_directories: Option>, + /// Whether to skip custom instruction sources. + #[serde(skip_serializing_if = "Option::is_none")] + pub skip_custom_instructions: Option, + /// Optional trajectory output file path. + #[serde(skip_serializing_if = "Option::is_none")] + pub trajectory_file: Option, + /// Initial output verbosity level for supported models. + #[serde(skip_serializing_if = "Option::is_none")] + pub verbosity: Option, + /// Working directory to anchor the session. + #[serde(skip_serializing_if = "Option::is_none")] + pub working_directory: Option, + /// Pre-resolved working-directory context for session startup. + #[serde(skip_serializing_if = "Option::is_none")] + pub working_directory_context: Option, +} + +/// Parameters for creating a new local session. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsOpenCreate { + /// Whether to emit session.start during creation. Defaults to true. + #[serde(skip_serializing_if = "Option::is_none")] + pub emit_start: Option, + /// Create a new local session. + pub kind: SessionsOpenCreateKind, + /// Session construction options. + #[serde(skip_serializing_if = "Option::is_none")] + pub options: Option, +} + +/// Parameters for resuming a specific local session. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsOpenResume { + /// Resume a specific local session by ID or prefix. + pub kind: SessionsOpenResumeKind, + /// Session resume options. + #[serde(skip_serializing_if = "Option::is_none")] + pub options: Option, + /// Whether to emit session.resume after loading. Defaults to true. + #[serde(skip_serializing_if = "Option::is_none")] + pub resume: Option, + /// Session ID or unique prefix to resume. + pub session_id: SessionId, + /// Suppress workspace.yaml metadata writeback when resuming from an incidental cwd. + #[serde(skip_serializing_if = "Option::is_none")] + pub suppress_resume_workspace_metadata_writeback: Option, +} + +/// Parameters for resuming the most relevant local session. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsOpenResumeLast { + /// Working-directory context used to choose the most relevant session. + #[serde(skip_serializing_if = "Option::is_none")] + pub context: Option, + /// Resume the most relevant existing local session. + pub kind: SessionsOpenResumeLastKind, + /// Session resume options. + #[serde(skip_serializing_if = "Option::is_none")] + pub options: Option, + /// Suppress workspace.yaml metadata writeback when resuming from an incidental cwd. + #[serde(skip_serializing_if = "Option::is_none")] + pub suppress_resume_workspace_metadata_writeback: Option, +} + +/// Parameters for attaching to an already-active session by ID. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsOpenAttach { + /// Attach to an already-active in-process session by ID. Unlike `resume`, this does NOT re-load from disk; the session must already be loaded by an earlier `create`/`resume` call. Returns `status: 'not_found'` when no active session matches the id. Useful for in-process consumers that need a fresh API handle to a session opened elsewhere (e.g., a peer foreground-session switch). + pub kind: SessionsOpenAttachKind, + /// Session ID to attach to. + pub session_id: SessionId, +} + +/// Parameters for connecting to a live remote session. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsOpenRemote { + /// Connect to a live remote session. + pub kind: SessionsOpenRemoteKind, + /// Session options for the connection. + #[serde(skip_serializing_if = "Option::is_none")] + pub options: Option, + /// Remote session identifier to connect to. + pub remote_session_id: SessionId, + /// Repository context for the remote session. + #[serde(skip_serializing_if = "Option::is_none")] + pub repository: Option, +} + +/// Parameters for creating a new cloud session. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsOpenCloud { + /// Create a new cloud (coding-agent) session. + pub kind: SessionsOpenCloudKind, + /// In-process callback invoked when the cloud task is created (before connection). Marked internal because a function reference cannot cross the JSON-RPC boundary. Disappears in the SDK migration: the field is purely cosmetic (it flips a single CLI phase label from 'creating' to 'connecting') and the wire-clean version just drops the intermediate phase. + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) on_task_created: Option, + /// Session options for cloud session creation. + #[serde(skip_serializing_if = "Option::is_none")] + pub options: Option, + /// Optional owner (user or organization login) to associate with the cloud session when no repository is provided. Ignored when `repository` is set (the repo's owner takes precedence). + #[serde(skip_serializing_if = "Option::is_none")] + pub owner: Option, + /// Repository for the cloud session. + #[serde(skip_serializing_if = "Option::is_none")] + pub repository: Option, +} + +/// Parameters for fetching a remote session and handing it off to a new local session. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsOpenHandoff { + /// Fetch a remote session and hand it off to a new local session. + pub kind: SessionsOpenHandoffKind, + /// Remote session metadata for the session to hand off (typically obtained from `sessions.list` with `source: "remote"`). + pub metadata: RemoteSessionMetadataValue, + /// In-process confirmation callback `(request) => boolean | Promise` invoked when the handoff needs the caller to confirm a non-fatal blocker (e.g. a repository mismatch between the current working directory and the remote session). Returning `true` proceeds with the handoff; returning `false` (or omitting the callback) aborts it. Marked internal because a function reference cannot cross the JSON-RPC boundary, for the same reasons as `onProgress`. + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) on_confirm: Option, + /// In-process progress callback `(update) => void` invoked for each handoff step. Marked internal because a function reference cannot cross the JSON-RPC boundary. The host-side `handoffSession` is already declared as `AsyncGenerator`; the schema layer flattens it because it does not yet support streaming methods. The wire-clean replacement is to expose the AsyncGenerator directly (or use vscode-jsonrpc `$/progress` notifications) once the schema/transport layer supports it. + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) on_progress: Option, + /// Session construction options for the new local session. + #[serde(skip_serializing_if = "Option::is_none")] + pub options: Option, + /// Task type determines the handoff strategy (CCA fetches events; CLI prepares a transient session). + #[serde(skip_serializing_if = "Option::is_none")] + pub task_type: Option, +} + +/// `sessions.open` handoff progress update with step, status, and optional message. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsOpenProgress { + /// Optional step message. + #[serde(skip_serializing_if = "Option::is_none")] + pub message: Option, + /// Step status. + pub status: SessionsOpenProgressStatus, + /// Handoff step. + pub step: SessionsOpenProgressStep, +} + +/// Result of opening a session. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionOpenResult { + /// Remote session metadata, present when status is `connected`. + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, + /// Handoff progress steps, present when status is `handed_off`. + #[serde(skip_serializing_if = "Option::is_none")] + pub progress: Option>, + /// Remote session ID, present when status is `connected`. + #[serde(skip_serializing_if = "Option::is_none")] + pub remote_session_id: Option, + /// In-process SessionClientApi handle for the opened session, returned to CLI callers as a transitional shortcut. Marked internal so the public SDK surface does not expose it; SDK consumers should construct per-session clients from `sessionId` instead. + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) session_api: Option, + /// Opened session ID. Omitted when status is `not_found`. + #[serde(skip_serializing_if = "Option::is_none")] + pub session_id: Option, + /// Startup prompts queued by user-level hook configs at session creation. Only populated when status is `created`; resumed sessions return an empty array. + #[serde(skip_serializing_if = "Option::is_none")] + pub startup_prompts: Option>, + /// Outcome of the open request. + pub status: SessionsOpenStatus, +} + +/// Outcome of the prune operation: deleted IDs, dry-run candidates, skipped IDs, total bytes freed, and the dry-run flag. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionPruneResult { + /// Session IDs that would be deleted in dry-run mode (always empty otherwise) + pub candidates: Vec, + /// Session IDs that were deleted (always empty in dry-run mode) + pub deleted: Vec, + /// True when no deletions were actually performed + pub dry_run: bool, + /// Total bytes freed (actual when not dry-run, projected when dry-run) + pub freed_bytes: i64, + /// Session IDs that were skipped (e.g., named sessions) + pub skipped: Vec, +} + +/// Session IDs to close, deactivate, and delete from disk. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsBulkDeleteRequest { + /// Session IDs to close, deactivate, and delete from disk + pub session_ids: Vec, +} + +/// Session IDs to test for live in-use locks. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsCheckInUseRequest { + /// Session IDs to test for live in-use locks + pub session_ids: Vec, +} + +/// Session IDs from the input set that are currently in use by another process. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsCheckInUseResult { + /// Session IDs from the input set that are currently held by another running process via an alive lock file + pub in_use: Vec, +} + +/// Session ID to close. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsCloseRequest { + /// Session ID to close + pub session_id: SessionId, +} + +/// Closes a session: emits shutdown, flushes pending events to disk, releases the in-use lock, disposes the active session. Idempotent: succeeds even if the session is not currently active. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsCloseResult {} + +/// Session ID to delete from disk. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsDeleteRequest { + /// Session ID to delete + pub session_id: SessionId, + /// Internal resolved session directory path to delete + #[serde(skip_serializing_if = "Option::is_none")] + pub session_path: Option, +} + +/// Session metadata records to enrich with summary and context information. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsEnrichMetadataRequest { + /// Session metadata records to enrich. Records that already have summary and context are returned unchanged. + pub sessions: Vec, +} + +/// New auth credentials to install on the session. Omit to leave credentials unchanged. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionSetCredentialsParams { + /// The new auth credentials to install on the session. When omitted or `undefined`, the call is a no-op and the session's existing credentials are preserved. The runtime installs the supplied value immediately for outbound model/API requests. When the credential carries a raw token (`token`, `env`, or `gh-cli`) but no `copilotUser`, the runtime additionally re-resolves `copilotUser` server-side (best-effort, asynchronously, after the synchronous install) so plan/quota/billing metadata regains fidelity; on resolution failure the verbatim credential remains installed. It does NOT otherwise validate the credential. Several variants carry secret material; treat this method's params as containing secrets at rest and in transit. + #[serde(skip_serializing_if = "Option::is_none")] + pub credentials: Option, +} + +/// Indicates whether the credential update succeeded. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionSetCredentialsResult { + /// Whether the session ended up with a populated `copilotUser` for the installed credentials. `true` when the supplied credential already carried `copilotUser` or it was successfully re-resolved server-side. `false` when the credential is installed without `copilotUser` — either re-resolution failed, or the variant cannot be re-resolved from the credential alone (only the raw-token variants `token`, `env`, and `gh-cli` can). In both `false` cases the token swap still applied, but plan/quota/billing metadata is degraded. Present whenever a credential was supplied; omitted only when no credential was supplied (no-op call). + #[serde(skip_serializing_if = "Option::is_none")] + pub copilot_user_resolved: Option, + /// Whether the operation succeeded + pub success: bool, +} + +/// Availability of built-in job tools surfaced to boundary consumers. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionSettingsBuiltInToolAvailabilitySnapshot { + #[serde(skip_serializing_if = "Option::is_none")] + pub create_pull_request: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub report_progress: Option, +} + +/// Named Rust-owned settings predicate to evaluate for this session. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionSettingsEvaluatePredicateRequest { + /// Predicate name. The runtime owns the raw feature-flag names and composition logic. + pub name: SessionSettingsPredicateName, + /// Tool name for tool-scoped predicates such as trivial-change handling. + #[serde(skip_serializing_if = "Option::is_none")] + pub tool_name: Option, +} + +/// Result of evaluating a Rust-owned settings predicate. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionSettingsEvaluatePredicateResult { + pub enabled: bool, +} + +/// Redacted job settings for a session. The job nonce is excluded. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionSettingsJobSnapshot { + #[serde(skip_serializing_if = "Option::is_none")] + pub built_in_tool_availability: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub event_type: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub is_trigger_job: Option, +} + +/// Redacted model routing settings for a session. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionSettingsModelSnapshot { + #[serde(skip_serializing_if = "Option::is_none")] + pub callback_url: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub default_reasoning_effort: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub instance_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub model: Option, +} + +/// Online-evaluation settings safe to expose across the SDK boundary. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionSettingsOnlineEvaluationSnapshot { + #[serde(skip_serializing_if = "Option::is_none")] + pub disable_online_evaluation: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_online_evaluation_output_file: Option, +} + +/// Redacted repository and GitHub host settings for a session. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionSettingsRepoSnapshot { + #[serde(skip_serializing_if = "Option::is_none")] + pub branch: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub commit: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub host: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub host_protocol: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub owner_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub owner_name: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub pr_commit_count: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub read_write: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub secret_scanning_url: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub server_url: Option, +} + +/// Redacted validation and memory-tool settings for a session. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionSettingsValidationSnapshot { + #[serde(skip_serializing_if = "Option::is_none")] + pub advisory_enabled: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub codeql_enabled: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub code_review_enabled: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub code_review_model: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub dependabot_timeout: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub memory_store_enabled: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub memory_vote_enabled: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub secret_scanning_enabled: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub timeout: Option, +} + +/// Redacted, serializable view of session runtime settings for SDK boundary consumers. Secrets and raw feature flags are intentionally excluded. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionSettingsSnapshot { + #[serde(skip_serializing_if = "Option::is_none")] + pub client_name: Option, + pub job: SessionSettingsJobSnapshot, + pub model: SessionSettingsModelSnapshot, + pub online_evaluation: SessionSettingsOnlineEvaluationSnapshot, + pub repo: SessionSettingsRepoSnapshot, + #[serde(skip_serializing_if = "Option::is_none")] + pub start_time_ms: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub timeout_ms: Option, + pub validation: SessionSettingsValidationSnapshot, + #[serde(skip_serializing_if = "Option::is_none")] + pub version: Option, +} + +/// UUID prefix to resolve to a unique session ID. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsFindByPrefixRequest { + /// UUID prefix (>=7 hex chars, <36 chars). Returns the unique session ID, or undefined when there is no match or the prefix matches multiple sessions. + pub prefix: String, +} + +/// Session ID matching the prefix, omitted when no unique match exists. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsFindByPrefixResult { + /// Omitted when no unique session matches the prefix (no match or ambiguous) #[serde(skip_serializing_if = "Option::is_none")] - pub working_directory_context: Option, + pub session_id: Option, +} + +/// GitHub task ID to look up. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsFindByTaskIDRequest { + /// GitHub task ID to look up + pub task_id: String, +} + +/// ID of the local session bound to the given GitHub task, or omitted when none. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsFindByTaskIDResult { + /// Omitted when no local session is bound to that GitHub task + #[serde(skip_serializing_if = "Option::is_none")] + pub session_id: Option, +} + +/// Source session identifier to fork from, optional event-ID boundary, and optional friendly name for the new session. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsForkRequest { + /// Optional friendly name to assign to the forked session. + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + /// Source session ID to fork from + pub session_id: SessionId, + /// Optional event ID boundary. When provided, the fork includes only events before this ID (exclusive). When omitted, all events are included. + #[serde(skip_serializing_if = "Option::is_none")] + pub to_event_id: Option, +} + +/// Identifier and optional friendly name assigned to the newly forked session. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsForkResult { + /// Friendly name assigned to the forked session, if any. + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + /// The new forked session's ID + pub session_id: SessionId, +} + +/// Session ID whose board entry count should be returned. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsGetBoardEntryCountRequest { + /// Session ID whose board entry count should be returned. + pub session_id: SessionId, +} + +/// Dynamic-context board entry count, when available. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsGetBoardEntryCountResult { + /// Board entry count, when available. + #[serde(skip_serializing_if = "Option::is_none")] + pub count: Option, +} + +/// Session ID whose event-log file path to compute. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsGetEventFilePathRequest { + /// Session ID whose event-log file path to compute + pub session_id: SessionId, +} + +/// Absolute path to the session's events.jsonl file on disk. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsGetEventFilePathResult { + /// Absolute path to the session's events.jsonl file + pub file_path: String, +} + +/// Optional working-directory context used to score session relevance. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsGetLastForContextRequest { + /// Optional working-directory context used to score session relevance. When omitted the most-recently-modified session wins. + #[serde(skip_serializing_if = "Option::is_none")] + pub context: Option, +} + +/// Most-relevant session ID for the supplied context, or omitted when no sessions exist. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsGetLastForContextResult { + /// Most-relevant session ID for the supplied context, or omitted when no sessions exist + #[serde(skip_serializing_if = "Option::is_none")] + pub session_id: Option, +} + +/// Session ID whose persisted metadata should be read. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsGetMetadataRequest { + /// Session ID to inspect + pub session_id: SessionId, +} + +/// Persisted local session metadata when the session exists. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsGetMetadataResult { + /// Local session metadata, omitted when the session does not exist. + #[serde(skip_serializing_if = "Option::is_none")] + pub session: Option, +} + +/// Session ID to look up the persisted remote-steerable flag for. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsGetPersistedRemoteSteerableRequest { + /// Session ID to look up the persisted remote-steerable flag for + pub session_id: SessionId, +} + +/// The session's persisted remote-steerable flag, or omitted when no value has been persisted. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsGetPersistedRemoteSteerableResult { + /// The session's persisted remote-steerable flag if recorded; omitted when no value has been persisted + #[serde(skip_serializing_if = "Option::is_none")] + pub remote_steerable: Option, +} + +/// Map of sessionId -> on-disk size in bytes for each session's workspace directory. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionSizes { + /// Map of sessionId -> on-disk size in bytes for the session's workspace directory + pub sizes: HashMap, +} + +/// Limit for non-empty local session IDs. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsListNonEmptySessionIdsRequest { + /// Maximum number of session IDs to return. + #[serde(skip_serializing_if = "Option::is_none")] + pub limit: Option, +} + +/// Recent local session IDs that contain user-visible history. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsListNonEmptySessionIdsResult { + /// Session IDs ordered newest-first. + pub session_ids: Vec, } -/// Parameters for creating a new local session. +/// Optional source filter, metadata-load limit, and context filter applied to the returned sessions. /// ///
/// @@ -12390,18 +15417,25 @@ pub struct SessionOpenOptions { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsOpenCreate { - /// Whether to emit session.start during creation. Defaults to true. +pub struct SessionsListRequest { + /// Optional filter applied to the returned sessions #[serde(skip_serializing_if = "Option::is_none")] - pub emit_start: Option, - /// Create a new local session. - pub kind: SessionsOpenCreateKind, - /// Session construction options. + pub filter: Option, + /// When true, include detached maintenance sessions. Defaults to false for user-facing session lists. #[serde(skip_serializing_if = "Option::is_none")] - pub options: Option, + pub include_detached: Option, + /// When provided, only the first N local sessions (sorted by modification time, newest first) load full metadata; remaining sessions return basic info only. Use 0 to return only basic info for every local session. Has no effect on remote entries (which always carry their full shape). + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata_limit: Option, + /// Which session sources to include. Defaults to `local` for backward compatibility. + #[serde(skip_serializing_if = "Option::is_none")] + pub source: Option, + /// Only meaningful when `source` includes remote. When true, propagates errors from the remote service instead of silently returning an empty remote list. Defaults to false. + #[serde(skip_serializing_if = "Option::is_none")] + pub throw_on_error: Option, } -/// Parameters for resuming a specific local session. +/// Active session ID whose deferred repo-level hooks should be loaded. /// ///
/// @@ -12411,23 +15445,12 @@ pub struct SessionsOpenCreate { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsOpenResume { - /// Resume a specific local session by ID or prefix. - pub kind: SessionsOpenResumeKind, - /// Session resume options. - #[serde(skip_serializing_if = "Option::is_none")] - pub options: Option, - /// Whether to emit session.resume after loading. Defaults to true. - #[serde(skip_serializing_if = "Option::is_none")] - pub resume: Option, - /// Session ID or unique prefix to resume. +pub struct SessionsLoadDeferredRepoHooksRequest { + /// Active session ID whose deferred repo-level hooks should be loaded pub session_id: SessionId, - /// Suppress workspace.yaml metadata writeback when resuming from an incidental cwd. - #[serde(skip_serializing_if = "Option::is_none")] - pub suppress_resume_workspace_metadata_writeback: Option, } -/// Parameters for resuming the most relevant local session. +/// Age threshold and optional flags controlling which old sessions are pruned (or simulated when dryRun is true). /// ///
/// @@ -12437,21 +15460,21 @@ pub struct SessionsOpenResume { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsOpenResumeLast { - /// Working-directory context used to choose the most relevant session. +pub struct SessionsPruneOldRequest { + /// When true, only report what would be deleted without performing any deletion #[serde(skip_serializing_if = "Option::is_none")] - pub context: Option, - /// Resume the most relevant existing local session. - pub kind: SessionsOpenResumeLastKind, - /// Session resume options. + pub dry_run: Option, + /// Session IDs that should never be considered for pruning #[serde(skip_serializing_if = "Option::is_none")] - pub options: Option, - /// Suppress workspace.yaml metadata writeback when resuming from an incidental cwd. + pub exclude_session_ids: Option>, + /// When true, named sessions (set via /rename) are also eligible for pruning #[serde(skip_serializing_if = "Option::is_none")] - pub suppress_resume_workspace_metadata_writeback: Option, + pub include_named: Option, + /// Delete sessions whose modifiedTime is at least this many days old + pub older_than_days: i64, } -/// Parameters for attaching to an already-active session by ID. +/// Session ID whose in-use lock should be released. /// ///
/// @@ -12461,14 +15484,12 @@ pub struct SessionsOpenResumeLast { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsOpenAttach { - /// Attach to an already-active in-process session by ID. Unlike `resume`, this does NOT re-load from disk; the session must already be loaded by an earlier `create`/`resume` call. Returns `status: 'not_found'` when no active session matches the id. Useful for in-process consumers that need a fresh API handle to a session opened elsewhere (e.g., a peer foreground-session switch). - pub kind: SessionsOpenAttachKind, - /// Session ID to attach to. +pub struct SessionsReleaseLockRequest { + /// Session ID whose in-use lock should be released pub session_id: SessionId, } -/// Parameters for connecting to a live remote session. +/// Release the in-use lock held by this process for the given session. No-op when this process does not currently hold a lock for the session. /// ///
/// @@ -12478,20 +15499,27 @@ pub struct SessionsOpenAttach { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsOpenRemote { - /// Connect to a live remote session. - pub kind: SessionsOpenRemoteKind, - /// Session options for the connection. - #[serde(skip_serializing_if = "Option::is_none")] - pub options: Option, - /// Remote session identifier to connect to. - pub remote_session_id: SessionId, - /// Repository context for the remote session. +pub struct SessionsReleaseLockResult {} + +/// Active session ID and an optional flag for deferring repo-level hooks until folder trust. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsReloadPluginHooksRequest { + /// When true, skip repo-level hooks. Use before folder trust is confirmed; loadDeferredRepoHooks loads them post-trust. #[serde(skip_serializing_if = "Option::is_none")] - pub repository: Option, + pub defer_repo_hooks: Option, + /// Active session ID to reload hooks for + pub session_id: SessionId, } -/// Parameters for creating a new cloud session. +/// Reload all hooks (user, plugin, optionally repo) and apply them to the active session. Call after installing or removing plugins so their hooks take effect immediately. No-op when no active session matches the given sessionId. /// ///
/// @@ -12501,25 +15529,24 @@ pub struct SessionsOpenRemote { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsOpenCloud { - /// Create a new cloud (coding-agent) session. - pub kind: SessionsOpenCloudKind, - /// In-process callback invoked when the cloud task is created (before connection). Marked internal because a function reference cannot cross the JSON-RPC boundary. Disappears in the SDK migration: the field is purely cosmetic (it flips a single CLI phase label from 'creating' to 'connecting') and the wire-clean version just drops the intermediate phase. - #[doc(hidden)] - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) on_task_created: Option, - /// Session options for cloud session creation. - #[serde(skip_serializing_if = "Option::is_none")] - pub options: Option, - /// Optional owner (user or organization login) to associate with the cloud session when no repository is provided. Ignored when `repository` is set (the repo's owner takes precedence). - #[serde(skip_serializing_if = "Option::is_none")] - pub owner: Option, - /// Repository for the cloud session. - #[serde(skip_serializing_if = "Option::is_none")] - pub repository: Option, +pub struct SessionsReloadPluginHooksResult {} + +/// Session ID whose pending events should be flushed to disk. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsSaveRequest { + /// Session ID whose pending events should be flushed to disk + pub session_id: SessionId, } -/// Parameters for fetching a remote session and handing it off to a new local session. +/// Flush a session's pending events to disk. No-op when no writer exists for the session (e.g., already closed). /// ///
/// @@ -12529,28 +15556,24 @@ pub struct SessionsOpenCloud { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsOpenHandoff { - /// Fetch a remote session and hand it off to a new local session. - pub kind: SessionsOpenHandoffKind, - /// Remote session metadata for the session to hand off (typically obtained from `sessions.list` with `source: "remote"`). - pub metadata: RemoteSessionMetadataValue, - /// In-process confirmation callback `(request) => boolean | Promise` invoked when the handoff needs the caller to confirm a non-fatal blocker (e.g. a repository mismatch between the current working directory and the remote session). Returning `true` proceeds with the handoff; returning `false` (or omitting the callback) aborts it. Marked internal because a function reference cannot cross the JSON-RPC boundary, for the same reasons as `onProgress`. - #[doc(hidden)] - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) on_confirm: Option, - /// In-process progress callback `(update) => void` invoked for each handoff step. Marked internal because a function reference cannot cross the JSON-RPC boundary. The host-side `handoffSession` is already declared as `AsyncGenerator`; the schema layer flattens it because it does not yet support streaming methods. The wire-clean replacement is to expose the AsyncGenerator directly (or use vscode-jsonrpc `$/progress` notifications) once the schema/transport layer supports it. - #[doc(hidden)] - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) on_progress: Option, - /// Session construction options for the new local session. - #[serde(skip_serializing_if = "Option::is_none")] - pub options: Option, - /// Task type determines the handoff strategy (CCA fetches events; CLI prepares a transient session). - #[serde(skip_serializing_if = "Option::is_none")] - pub task_type: Option, +pub struct SessionsSaveResult {} + +/// Manager-wide additional plugins to register; replaces any previously-configured set. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsSetAdditionalPluginsRequest { + /// Manager-wide additional plugins to register. Replaces any previously-configured set. Pass an empty array to clear. + pub plugins: Vec, } -/// `sessions.open` handoff progress update with step, status, and optional message. +/// Replace the manager-wide additional plugins. New session creations and subsequent hook reloads see the new set; already-running sessions keep their existing hook installation until the next reload. /// ///
/// @@ -12560,17 +15583,24 @@ pub struct SessionsOpenHandoff { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsOpenProgress { - /// Optional step message. - #[serde(skip_serializing_if = "Option::is_none")] - pub message: Option, - /// Step status. - pub status: SessionsOpenProgressStatus, - /// Handoff step. - pub step: SessionsOpenProgressStep, +pub struct SessionsSetAdditionalPluginsResult {} + +/// Patch for the singleton's steering state. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionsSetRemoteControlSteeringRequest { + /// Target steering state. Today only `true` is actionable on the underlying exporter; `false` is reserved for future use. + pub enabled: bool, } -/// Result of opening a session. +/// Parameters for attaching the remote-control singleton to a session. /// ///
/// @@ -12580,31 +15610,14 @@ pub struct SessionsOpenProgress { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionOpenResult { - /// Remote session metadata, present when status is `connected`. - #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option, - /// Handoff progress steps, present when status is `handed_off`. - #[serde(skip_serializing_if = "Option::is_none")] - pub progress: Option>, - /// Remote session ID, present when status is `connected`. - #[serde(skip_serializing_if = "Option::is_none")] - pub remote_session_id: Option, - /// In-process SessionClientApi handle for the opened session, returned to CLI callers as a transitional shortcut. Marked internal so the public SDK surface does not expose it; SDK consumers should construct per-session clients from `sessionId` instead. - #[doc(hidden)] - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) session_api: Option, - /// Opened session ID. Omitted when status is `not_found`. - #[serde(skip_serializing_if = "Option::is_none")] - pub session_id: Option, - /// Startup prompts queued by user-level hook configs at session creation. Only populated when status is `created`; resumed sessions return an empty array. - #[serde(skip_serializing_if = "Option::is_none")] - pub startup_prompts: Option>, - /// Outcome of the open request. - pub status: SessionsOpenStatus, +pub struct SessionsStartRemoteControlRequest { + /// Configuration for the runtime-managed remote-control singleton. + pub config: RemoteControlConfig, + /// Local session id to attach remote control to. + pub session_id: SessionId, } -/// Outcome of the prune operation: deleted IDs, dry-run candidates, skipped IDs, total bytes freed, and the dry-run flag. +/// Parameters for stopping the remote-control singleton. /// ///
/// @@ -12614,20 +15627,16 @@ pub struct SessionOpenResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPruneResult { - /// Session IDs that would be deleted in dry-run mode (always empty otherwise) - pub candidates: Vec, - /// Session IDs that were deleted (always empty in dry-run mode) - pub deleted: Vec, - /// True when no deletions were actually performed - pub dry_run: bool, - /// Total bytes freed (actual when not dry-run, projected when dry-run) - pub freed_bytes: i64, - /// Session IDs that were skipped (e.g., named sessions) - pub skipped: Vec, +pub struct SessionsStopRemoteControlRequest { + /// When provided, the stop is rejected unless the singleton currently points at this session id (compare-and-swap semantics). + #[serde(skip_serializing_if = "Option::is_none")] + pub expected_session_id: Option, + /// When true, the singleton is unconditionally torn down regardless of `expectedSessionId`. Use during shutdown or explicit `/remote off`. + #[serde(skip_serializing_if = "Option::is_none")] + pub force: Option, } -/// Session IDs to close, deactivate, and delete from disk. +/// Parameters for atomically rebinding the remote-control singleton. /// ///
/// @@ -12637,12 +15646,15 @@ pub struct SessionPruneResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsBulkDeleteRequest { - /// Session IDs to close, deactivate, and delete from disk - pub session_ids: Vec, +pub struct SessionsTransferRemoteControlRequest { + /// When provided, the transfer is rejected unless the singleton currently points at this session id (compare-and-swap semantics to avoid clobbering newer state). + #[serde(skip_serializing_if = "Option::is_none")] + pub expected_from_session_id: Option, + /// Local session id to point remote control at. + pub to_session_id: String, } -/// Session IDs to test for live in-use locks. +/// Telemetry engagement ID for the session, when available. /// ///
/// @@ -12652,12 +15664,13 @@ pub struct SessionsBulkDeleteRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsCheckInUseRequest { - /// Session IDs to test for live in-use locks - pub session_ids: Vec, +pub struct SessionTelemetryEngagement { + /// Current telemetry engagement ID, when available. + #[serde(skip_serializing_if = "Option::is_none")] + pub engagement_id: Option, } -/// Session IDs from the input set that are currently in use by another process. +/// Patch of mutable session options to apply to the running session. /// ///
/// @@ -12667,12 +15680,191 @@ pub struct SessionsCheckInUseRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsCheckInUseResult { - /// Session IDs from the input set that are currently held by another running process via an alive lock file - pub in_use: Vec, +pub struct SessionUpdateOptionsParams { + /// Additional content-exclusion policies to merge into the session's policy set. + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(skip_serializing_if = "Option::is_none")] + pub additional_content_exclusion_policies: + Option>, + /// Runtime context discriminator (e.g., `cli`, `actions`). + #[serde(skip_serializing_if = "Option::is_none")] + pub agent_context: Option, + /// Whether to include instructions from every MCP server in the system prompt instead of only allowlisted servers. + #[serde(skip_serializing_if = "Option::is_none")] + pub allow_all_mcp_server_instructions: Option, + /// Whether to disable the `ask_user` tool (encourages autonomous behavior). + #[serde(skip_serializing_if = "Option::is_none")] + pub ask_user_disabled: Option, + /// Allowlist of tool names available to this session. + #[serde(skip_serializing_if = "Option::is_none")] + pub available_tools: Option>, + /// Options scoped to the built-in CAPI (Copilot API) provider. + #[serde(skip_serializing_if = "Option::is_none")] + pub capi: Option, + /// Identifier of the client driving the session. + #[serde(skip_serializing_if = "Option::is_none")] + pub client_name: Option, + /// Whether to include the `Co-authored-by` trailer in commit messages. + #[serde(skip_serializing_if = "Option::is_none")] + pub coauthor_enabled: Option, + /// Context tier for models with tiered pricing. The session uses this to derive effective `modelCapabilitiesOverrides` so compaction, truncation, token display, and request limits honor the selected tier. + #[serde(skip_serializing_if = "Option::is_none")] + pub context_tier: Option, + /// Whether to allow auto-mode continuation across turns. + #[serde(skip_serializing_if = "Option::is_none")] + pub continue_on_auto_mode: Option, + /// Override URL for the Copilot API endpoint. + #[serde(skip_serializing_if = "Option::is_none")] + pub copilot_url: Option, + /// Whether to default custom agents to local-only execution. + #[serde(skip_serializing_if = "Option::is_none")] + pub custom_agents_local_only: Option, + /// Instruction source IDs to exclude from the system prompt. + #[serde(skip_serializing_if = "Option::is_none")] + pub disabled_instruction_sources: Option>, + /// Skill IDs that should be excluded from this session. + #[serde(skip_serializing_if = "Option::is_none")] + pub disabled_skills: Option>, + /// Whether to enable loading of `.github/hooks/` filesystem hooks. Separate from the SDK callback hook mechanism. + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_file_hooks: Option, + /// Whether to enable host git operations (context resolution, child repo scanning, git info in system prompt). + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_host_git_operations: Option, + /// Whether to discover custom instructions on demand after successful file views (AGENTS.md / CLAUDE.md / .github/copilot-instructions.md surfacing). Combined with `skipCustomInstructions`. + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_on_demand_instruction_discovery: Option, + /// Whether to surface reasoning-summary events from the model. + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_reasoning_summaries: Option, + /// Whether shell-script safety heuristics are enabled. + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_script_safety: Option, + /// Whether to enable cross-session store writes and reads. + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_session_store: Option, + /// Whether to enable skill directory scanning and loading. Falls back to enableConfigDiscovery when unset. + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_skills: Option, + /// Whether to stream model responses. + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_streaming: Option, + /// How env values are passed to MCP servers (`direct` inlines literal values; `indirect` resolves at launch). + #[serde(skip_serializing_if = "Option::is_none")] + pub env_value_mode: Option, + /// Override directory for the session-events log. When unset, the runtime's default events log directory is used. + #[serde(skip_serializing_if = "Option::is_none")] + pub events_log_directory: Option, + /// Whether subagent callback events should be forwarded into the session event log sink. + #[serde(skip_serializing_if = "Option::is_none")] + pub events_log_includes_subagents: Option, + /// Built-in subagent names to exclude from this session. Excluded built-ins are hidden from agent discovery and cannot be dispatched unless a custom agent with the same name is available. + #[serde(skip_serializing_if = "Option::is_none")] + pub excluded_builtin_agents: Option>, + /// Denylist of tool names for this session. + #[serde(skip_serializing_if = "Option::is_none")] + pub excluded_tools: Option>, + /// Map of feature-flag IDs to their boolean enabled state. + #[serde(skip_serializing_if = "Option::is_none")] + pub feature_flags: Option>, + /// Built-in subagent names to include in this session. When specified, only these built-ins are available, subject to runtime availability and exclusions. Custom agents with the same name remain available. Set to null to remove the allowlist restriction. + #[serde(skip_serializing_if = "Option::is_none")] + pub included_builtin_agents: Option>, + /// Full set of installed plugins for the session. Replaces the existing list; the runtime invalidates the skills cache only when the list materially changes. + #[serde(skip_serializing_if = "Option::is_none")] + pub installed_plugins: Option>, + /// Stable integration identifier used for analytics and rate-limit attribution. + #[serde(skip_serializing_if = "Option::is_none")] + pub integration_id: Option, + /// Whether experimental capabilities are enabled. + #[serde(skip_serializing_if = "Option::is_none")] + pub is_experimental_mode: Option, + /// Whether interactive shell sessions are logged. + #[serde(skip_serializing_if = "Option::is_none")] + pub log_interactive_shells: Option, + /// Identifier sent to LSP-style integrations. + #[serde(skip_serializing_if = "Option::is_none")] + pub lsp_client_name: Option, + /// Whether to expose the `manage_schedule` tool to the agent. The runtime always owns the per-session schedule registry; this flag only controls tool exposure (typically gated to staff users). + #[serde(skip_serializing_if = "Option::is_none")] + pub manage_schedule_enabled: Option, + /// Maximum decoded byte size of a single model-facing binary tool result (e.g. an image) persisted inline in session events and re-presented to the model on later turns / resume. Larger results are persisted as a metadata-only marker and shown to the model as a short text note. Defaults to 10 MB. + #[serde(skip_serializing_if = "Option::is_none")] + pub max_inline_binary_bytes: Option, + /// The model ID to use for assistant turns. + #[serde(skip_serializing_if = "Option::is_none")] + pub model: Option, + /// Per-property model capability overrides for the selected model. + #[serde(skip_serializing_if = "Option::is_none")] + pub model_capabilities_overrides: Option, + /// Organization-level custom instructions to inject into the system prompt. + #[serde(skip_serializing_if = "Option::is_none")] + pub organization_custom_instructions: Option, + /// Custom model-provider configuration (BYOK). + #[serde(skip_serializing_if = "Option::is_none")] + pub provider: Option, + /// Reasoning effort for the selected model. CAPI values are model-defined and validated against the selected model; BYOK providers may define additional values. When omitted, no effort override is applied. + #[serde(skip_serializing_if = "Option::is_none")] + pub reasoning_effort: Option, + /// Reasoning summary mode for supported model clients. + #[serde(skip_serializing_if = "Option::is_none")] + pub reasoning_summary: Option, + /// Whether the session is running in an interactive UI. + #[serde(skip_serializing_if = "Option::is_none")] + pub running_in_interactive_mode: Option, + /// Resolved sandbox configuration. + #[serde(skip_serializing_if = "Option::is_none")] + pub sandbox_config: Option, + /// Replaces the session's capability set with the given list. Use to enable or disable capabilities mid-session (e.g., remove `memory` for reproducible scripted runs). Omit the field to leave the existing capability set unchanged. + #[serde(skip_serializing_if = "Option::is_none")] + pub session_capabilities: Option>, + /// Optional session limits. Pass null to clear the session limits. + #[serde(skip_serializing_if = "Option::is_none")] + pub session_limits: Option, + /// Per-session settings for built-in shell tools. + #[serde(skip_serializing_if = "Option::is_none")] + pub shell: Option, + /// Use shell.initProfile instead. Shell init profile (`None` or `NonInteractive`). + #[doc(hidden)] + #[deprecated] + #[serde(skip_serializing_if = "Option::is_none")] + pub shell_init_profile: Option, + /// PowerShell process flags applied to built-in and user-requested shell commands. + #[serde(skip_serializing_if = "Option::is_none")] + pub shell_process_flags: Option>, + /// Additional directories to search for skills. + #[serde(skip_serializing_if = "Option::is_none")] + pub skill_directories: Option>, + /// Whether to skip loading custom instruction sources. + #[serde(skip_serializing_if = "Option::is_none")] + pub skip_custom_instructions: Option, + /// Whether to skip embedding retrieval pipeline initialization and execution. + #[serde(skip_serializing_if = "Option::is_none")] + pub skip_embedding_retrieval: Option, + /// When true, the selected custom agent's prompt is not injected into the user message (skill context is still injected). Used by automation triggers where the agent prompt is already in the problem statement. + #[serde(skip_serializing_if = "Option::is_none")] + pub suppress_custom_agent_prompt: Option, + /// Controls how availableTools (allowlist) and excludedTools (denylist) combine when both are set. + #[serde(skip_serializing_if = "Option::is_none")] + pub tool_filter_precedence: Option, + /// Optional path for trajectory output. + #[serde(skip_serializing_if = "Option::is_none")] + pub trajectory_file: Option, + /// Output verbosity level for supported models. + #[serde(skip_serializing_if = "Option::is_none")] + pub verbosity: Option, + /// Absolute working-directory path for shell tools. + #[serde(skip_serializing_if = "Option::is_none")] + pub working_directory: Option, } -/// Session ID to close. +/// Indicates whether the session options patch was applied successfully. /// ///
/// @@ -12682,12 +15874,15 @@ pub struct SessionsCheckInUseResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsCloseRequest { - /// Session ID to close - pub session_id: SessionId, +pub struct SessionUpdateOptionsResult { + /// Number of hooks loaded from installed plugins, returned when installedPlugins is updated + #[serde(skip_serializing_if = "Option::is_none")] + pub plugin_hook_count: Option, + /// Whether the operation succeeded + pub success: bool, } -/// Closes a session: emits shutdown, flushes pending events to disk, releases the in-use lock, disposes the active session. Idempotent: succeeds even if the session is not currently active. +/// User-requested shell execution cancellation handle. /// ///
/// @@ -12697,9 +15892,12 @@ pub struct SessionsCloseRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsCloseResult {} +pub struct ShellCancelUserRequestedRequest { + /// Request ID previously passed to executeUserRequested + pub request_id: RequestId, +} -/// Session metadata records to enrich with summary and context information. +/// Shell command to run, with optional working directory and timeout in milliseconds. /// ///
/// @@ -12709,12 +15907,18 @@ pub struct SessionsCloseResult {} ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsEnrichMetadataRequest { - /// Session metadata records to enrich. Records that already have summary and context are returned unchanged. - pub sessions: Vec, +pub struct ShellExecRequest { + /// Shell command to execute + pub command: String, + /// Working directory (defaults to session working directory) + #[serde(skip_serializing_if = "Option::is_none")] + pub cwd: Option, + /// Timeout in milliseconds (default: 30000) + #[serde(skip_serializing_if = "Option::is_none")] + pub timeout: Option, } -/// New auth credentials to install on the session. Omit to leave credentials unchanged. +/// Identifier of the spawned process, used to correlate streamed output and exit notifications. /// ///
/// @@ -12724,13 +15928,12 @@ pub struct SessionsEnrichMetadataRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionSetCredentialsParams { - /// The new auth credentials to install on the session. When omitted or `undefined`, the call is a no-op and the session's existing credentials are preserved. The runtime installs the supplied value immediately for outbound model/API requests. When the credential carries a raw token (`token`, `env`, or `gh-cli`) but no `copilotUser`, the runtime additionally re-resolves `copilotUser` server-side (best-effort, asynchronously, after the synchronous install) so plan/quota/billing metadata regains fidelity; on resolution failure the verbatim credential remains installed. It does NOT otherwise validate the credential. Several variants carry secret material; treat this method's params as containing secrets at rest and in transit. - #[serde(skip_serializing_if = "Option::is_none")] - pub credentials: Option, +pub struct ShellExecResult { + /// Unique identifier for tracking streamed output + pub process_id: String, } -/// Indicates whether the credential update succeeded. +/// User-requested shell command and cancellation handle. /// ///
/// @@ -12740,15 +15943,14 @@ pub struct SessionSetCredentialsParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionSetCredentialsResult { - /// Whether the session ended up with a populated `copilotUser` for the installed credentials. `true` when the supplied credential already carried `copilotUser` or it was successfully re-resolved server-side. `false` when the credential is installed without `copilotUser` — either re-resolution failed, or the variant cannot be re-resolved from the credential alone (only the raw-token variants `token`, `env`, and `gh-cli` can). In both `false` cases the token swap still applied, but plan/quota/billing metadata is degraded. Present whenever a credential was supplied; omitted only when no credential was supplied (no-op call). - #[serde(skip_serializing_if = "Option::is_none")] - pub copilot_user_resolved: Option, - /// Whether the operation succeeded - pub success: bool, +pub struct ShellExecuteUserRequestedRequest { + /// Shell command to execute + pub command: String, + /// Caller-provided cancellation handle for this execution + pub request_id: RequestId, } -/// Availability of built-in job tools surfaced to boundary consumers. +/// Identifier of a process previously returned by "shell.exec" and the signal to send. /// ///
/// @@ -12758,14 +15960,15 @@ pub struct SessionSetCredentialsResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionSettingsBuiltInToolAvailabilitySnapshot { - #[serde(skip_serializing_if = "Option::is_none")] - pub create_pull_request: Option, +pub struct ShellKillRequest { + /// Process identifier returned by shell.exec + pub process_id: String, + /// Signal to send (default: SIGTERM) #[serde(skip_serializing_if = "Option::is_none")] - pub report_progress: Option, + pub signal: Option, } -/// Named Rust-owned settings predicate to evaluate for this session. +/// Indicates whether the signal was delivered; false if the process was unknown or already exited. /// ///
/// @@ -12775,15 +15978,12 @@ pub struct SessionSettingsBuiltInToolAvailabilitySnapshot { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionSettingsEvaluatePredicateRequest { - /// Predicate name. The runtime owns the raw feature-flag names and composition logic. - pub name: SessionSettingsPredicateName, - /// Tool name for tool-scoped predicates such as trivial-change handling. - #[serde(skip_serializing_if = "Option::is_none")] - pub tool_name: Option, +pub struct ShellKillResult { + /// Whether the signal was sent successfully + pub killed: bool, } -/// Result of evaluating a Rust-owned settings predicate. +/// Parameters for shutting down the session /// ///
/// @@ -12793,11 +15993,16 @@ pub struct SessionSettingsEvaluatePredicateRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionSettingsEvaluatePredicateResult { - pub enabled: bool, +pub struct ShutdownRequest { + /// Optional human-readable reason. Typically the message of the error that triggered shutdown when type is 'error'. + #[serde(skip_serializing_if = "Option::is_none")] + pub reason: Option, + /// Why the session is being shut down. Defaults to "routine" when omitted. + #[serde(skip_serializing_if = "Option::is_none")] + pub r#type: Option, } -/// Redacted job settings for a session. The job nonce is excluded. +/// Skill metadata available to a session, with name, description, source, enabled/invocable state, path, plugin, and argument hint. /// ///
/// @@ -12807,16 +16012,32 @@ pub struct SessionSettingsEvaluatePredicateResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionSettingsJobSnapshot { +pub struct Skill { + /// Optional freeform hint describing the skill's expected arguments, from the `argument-hint` frontmatter field #[serde(skip_serializing_if = "Option::is_none")] - pub built_in_tool_availability: Option, + pub argument_hint: Option, + /// Canonical slash command name used to invoke the skill, without the leading '/' #[serde(skip_serializing_if = "Option::is_none")] - pub event_type: Option, + pub command_name: Option, + /// Description of what the skill does + pub description: String, + /// Whether the skill is currently enabled + pub enabled: bool, + /// Unique identifier for the skill + pub name: String, + /// Absolute path to the skill file #[serde(skip_serializing_if = "Option::is_none")] - pub is_trigger_job: Option, + pub path: Option, + /// Name of the plugin that provides the skill, when source is 'plugin' + #[serde(skip_serializing_if = "Option::is_none")] + pub plugin_name: Option, + /// Source location type (e.g., project, personal-copilot, plugin, builtin) + pub source: SkillSource, + /// Whether the skill can be invoked by the user as a slash command + pub user_invocable: bool, } -/// Redacted model routing settings for a session. +/// Canonical directory where skills can be discovered or created, with scope, preference, and optional project path. /// ///
/// @@ -12826,18 +16047,19 @@ pub struct SessionSettingsJobSnapshot { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionSettingsModelSnapshot { - #[serde(skip_serializing_if = "Option::is_none")] - pub callback_url: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub default_reasoning_effort: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub instance_id: Option, +pub struct SkillDiscoveryPath { + /// Absolute path of the create/discovery target (may not exist on disk yet) + pub path: String, + /// Whether this is the canonical directory to create a new skill in its tier. At most one entry per tier is preferred; the `personal-agents` and `custom` scopes are never preferred. + pub preferred_for_creation: bool, + /// The input project path this directory was derived from (only for project scope) #[serde(skip_serializing_if = "Option::is_none")] - pub model: Option, + pub project_path: Option, + /// Which tier this directory belongs to + pub scope: SkillDiscoveryScope, } -/// Online-evaluation settings safe to expose across the SDK boundary. +/// Canonical locations where skills can be created so the runtime will recognize them. /// ///
/// @@ -12847,14 +16069,12 @@ pub struct SessionSettingsModelSnapshot { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionSettingsOnlineEvaluationSnapshot { - #[serde(skip_serializing_if = "Option::is_none")] - pub disable_online_evaluation: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub enable_online_evaluation_output_file: Option, +pub struct SkillDiscoveryPathList { + /// Canonical skill create/discovery directories, in priority order + pub paths: Vec, } -/// Redacted repository and GitHub host settings for a session. +/// Skills available to the session, with their enabled state. /// ///
/// @@ -12864,34 +16084,12 @@ pub struct SessionSettingsOnlineEvaluationSnapshot { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionSettingsRepoSnapshot { - #[serde(skip_serializing_if = "Option::is_none")] - pub branch: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub commit: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub host: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub host_protocol: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub id: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub owner_id: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub owner_name: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub pr_commit_count: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub read_write: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub secret_scanning_url: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub server_url: Option, +pub struct SkillList { + /// Available skills + pub skills: Vec, } -/// Redacted validation and memory-tool settings for a session. +/// Skill names to mark as disabled in global configuration, replacing any previous list. /// ///
/// @@ -12901,28 +16099,12 @@ pub struct SessionSettingsRepoSnapshot { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionSettingsValidationSnapshot { - #[serde(skip_serializing_if = "Option::is_none")] - pub advisory_enabled: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub codeql_enabled: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub code_review_enabled: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub code_review_model: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub dependabot_timeout: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub memory_store_enabled: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub memory_vote_enabled: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub secret_scanning_enabled: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub timeout: Option, +pub struct SkillsConfigSetDisabledSkillsRequest { + /// List of skill names to disable + pub disabled_skills: Vec, } -/// Redacted, serializable view of session runtime settings for SDK boundary consumers. Secrets and raw feature flags are intentionally excluded. +/// Name of the skill to disable for the session. /// ///
/// @@ -12932,23 +16114,12 @@ pub struct SessionSettingsValidationSnapshot { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionSettingsSnapshot { - #[serde(skip_serializing_if = "Option::is_none")] - pub client_name: Option, - pub job: SessionSettingsJobSnapshot, - pub model: SessionSettingsModelSnapshot, - pub online_evaluation: SessionSettingsOnlineEvaluationSnapshot, - pub repo: SessionSettingsRepoSnapshot, - #[serde(skip_serializing_if = "Option::is_none")] - pub start_time_ms: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub timeout_ms: Option, - pub validation: SessionSettingsValidationSnapshot, - #[serde(skip_serializing_if = "Option::is_none")] - pub version: Option, +pub struct SkillsDisableRequest { + /// Name of the skill to disable + pub name: String, } -/// UUID prefix to resolve to a unique session ID. +/// Optional project paths and additional skill directories to include in discovery. /// ///
/// @@ -12958,12 +16129,19 @@ pub struct SessionSettingsSnapshot { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsFindByPrefixRequest { - /// UUID prefix (>=7 hex chars, <36 chars). Returns the unique session ID, or undefined when there is no match or the prefix matches multiple sessions. - pub prefix: String, +pub struct SkillsDiscoverRequest { + /// When true, omit skills from the host's global sources (personal, custom, plugin, and built-in), returning only project-scoped skills. For multitenant deployments. + #[serde(skip_serializing_if = "Option::is_none")] + pub exclude_host_skills: Option, + /// Optional list of project directory paths to scan for project-scoped skills + #[serde(skip_serializing_if = "Option::is_none")] + pub project_paths: Option>, + /// Optional list of additional skill directory paths to include + #[serde(skip_serializing_if = "Option::is_none")] + pub skill_directories: Option>, } -/// Session ID matching the prefix, omitted when no unique match exists. +/// Name of the skill to enable for the session. /// ///
/// @@ -12973,13 +16151,12 @@ pub struct SessionsFindByPrefixRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsFindByPrefixResult { - /// Omitted when no unique session matches the prefix (no match or ambiguous) - #[serde(skip_serializing_if = "Option::is_none")] - pub session_id: Option, +pub struct SkillsEnableRequest { + /// Name of the skill to enable + pub name: String, } -/// GitHub task ID to look up. +/// Optional project paths to enumerate. /// ///
/// @@ -12989,12 +16166,16 @@ pub struct SessionsFindByPrefixResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsFindByTaskIDRequest { - /// GitHub task ID to look up - pub task_id: String, +pub struct SkillsGetDiscoveryPathsRequest { + /// When true, omit the host's personal and custom skill directories, leaving only project directories. For multitenant deployments. + #[serde(skip_serializing_if = "Option::is_none")] + pub exclude_host_skills: Option, + /// Optional list of project directory paths. When omitted or empty, only personal and custom directories are returned. + #[serde(skip_serializing_if = "Option::is_none")] + pub project_paths: Option>, } -/// ID of the local session bound to the given GitHub task, or omitted when none. +/// Skill invocation record with name, path, content, allowed tools, and turn number. /// ///
/// @@ -13004,13 +16185,21 @@ pub struct SessionsFindByTaskIDRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsFindByTaskIDResult { - /// Omitted when no local session is bound to that GitHub task +pub struct SkillsInvokedSkill { + /// Tools that should be auto-approved when this skill is active, captured at invocation time #[serde(skip_serializing_if = "Option::is_none")] - pub session_id: Option, + pub allowed_tools: Option>, + /// Full content of the skill file + pub content: String, + /// Turn number when the skill was invoked + pub invoked_at_turn: i64, + /// Unique identifier for the skill + pub name: String, + /// Path to the SKILL.md file + pub path: String, } -/// Source session identifier to fork from, optional event-ID boundary, and optional friendly name for the new session. +/// Skills invoked during this session, ordered by invocation time (most recent last). /// ///
/// @@ -13020,18 +16209,12 @@ pub struct SessionsFindByTaskIDResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsForkRequest { - /// Optional friendly name to assign to the forked session. - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, - /// Source session ID to fork from - pub session_id: SessionId, - /// Optional event ID boundary. When provided, the fork includes only events before this ID (exclusive). When omitted, all events are included. - #[serde(skip_serializing_if = "Option::is_none")] - pub to_event_id: Option, +pub struct SkillsGetInvokedResult { + /// Skills invoked during this session, ordered by invocation time (most recent last) + pub skills: Vec, } -/// Identifier and optional friendly name assigned to the newly forked session. +/// Diagnostics from reloading skill definitions, with warnings and errors as separate lists. /// ///
/// @@ -13041,15 +16224,14 @@ pub struct SessionsForkRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsForkResult { - /// Friendly name assigned to the forked session, if any. - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, - /// The new forked session's ID - pub session_id: SessionId, +pub struct SkillsLoadDiagnostics { + /// Errors emitted while loading skills (e.g. skills that failed to load entirely) + pub errors: Vec, + /// Warnings emitted while loading skills (e.g. skills that loaded but had issues) + pub warnings: Vec, } -/// Session ID whose board entry count should be returned. +/// Slash-command invocation result that submits an agent prompt, with display prompt, optional mode, optional user-facing notice, and settings-change flag. /// ///
/// @@ -13059,12 +16241,25 @@ pub struct SessionsForkResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsGetBoardEntryCountRequest { - /// Session ID whose board entry count should be returned. - pub session_id: SessionId, +pub struct SlashCommandAgentPromptResult { + /// Prompt text to display to the user + pub display_prompt: String, + /// Agent prompt result discriminator + pub kind: SlashCommandAgentPromptResultKind, + /// Optional target session mode for the agent prompt + #[serde(skip_serializing_if = "Option::is_none")] + pub mode: Option, + /// Optional user-facing notice to show before the prompt is submitted + #[serde(skip_serializing_if = "Option::is_none")] + pub notice: Option, + /// Prompt to submit to the agent + pub prompt: String, + /// True when the invocation mutated user runtime settings; consumers caching settings should refresh + #[serde(skip_serializing_if = "Option::is_none")] + pub runtime_settings_changed: Option, } -/// Dynamic-context board entry count, when available. +/// Slash-command invocation result indicating completion, with optional message and settings-change flag. /// ///
/// @@ -13074,13 +16269,18 @@ pub struct SessionsGetBoardEntryCountRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsGetBoardEntryCountResult { - /// Board entry count, when available. +pub struct SlashCommandCompletedResult { + /// Completed result discriminator + pub kind: SlashCommandCompletedResultKind, + /// Optional user-facing message describing the completed command + #[serde(skip_serializing_if = "Option::is_none")] + pub message: Option, + /// True when the invocation mutated user runtime settings; consumers caching settings should refresh #[serde(skip_serializing_if = "Option::is_none")] - pub count: Option, + pub runtime_settings_changed: Option, } -/// Session ID whose event-log file path to compute. +/// Slash-command invocation result containing text output plus Markdown/ANSI rendering flags. /// ///
/// @@ -13090,12 +16290,23 @@ pub struct SessionsGetBoardEntryCountResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsGetEventFilePathRequest { - /// Session ID whose event-log file path to compute - pub session_id: SessionId, +pub struct SlashCommandTextResult { + /// Text result discriminator + pub kind: SlashCommandTextResultKind, + /// Whether text contains Markdown + #[serde(skip_serializing_if = "Option::is_none")] + pub markdown: Option, + /// Whether ANSI sequences should be preserved + #[serde(skip_serializing_if = "Option::is_none")] + pub preserve_ansi: Option, + /// True when the invocation mutated user runtime settings; consumers caching settings should refresh + #[serde(skip_serializing_if = "Option::is_none")] + pub runtime_settings_changed: Option, + /// Text output for the client to render + pub text: String, } -/// Absolute path to the session's events.jsonl file on disk. +/// Selectable slash-command subcommand option with name, description, and optional group label. /// ///
/// @@ -13105,12 +16316,17 @@ pub struct SessionsGetEventFilePathRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsGetEventFilePathResult { - /// Absolute path to the session's events.jsonl file - pub file_path: String, +pub struct SlashCommandSelectSubcommandOption { + /// Human-readable description of the subcommand + pub description: String, + /// Optional group label for organizing options + #[serde(skip_serializing_if = "Option::is_none")] + pub group: Option, + /// Subcommand name to invoke + pub name: String, } -/// Optional working-directory context used to score session relevance. +/// Slash-command invocation result asking the client to present subcommand options for a parent command. /// ///
/// @@ -13120,13 +16336,21 @@ pub struct SessionsGetEventFilePathResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsGetLastForContextRequest { - /// Optional working-directory context used to score session relevance. When omitted the most-recently-modified session wins. +pub struct SlashCommandSelectSubcommandResult { + /// Parent command name that requires subcommand selection + pub command: String, + /// Select subcommand result discriminator + pub kind: SlashCommandSelectSubcommandResultKind, + /// Available subcommand options for the client to present + pub options: Vec, + /// True when the invocation mutated user runtime settings; consumers caching settings should refresh #[serde(skip_serializing_if = "Option::is_none")] - pub context: Option, + pub runtime_settings_changed: Option, + /// Human-readable title for the selection UI + pub title: String, } -/// Most-relevant session ID for the supplied context, or omitted when no sessions exist. +/// Subagent model, reasoning effort, and context tier settings /// ///
/// @@ -13136,13 +16360,19 @@ pub struct SessionsGetLastForContextRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsGetLastForContextResult { - /// Most-relevant session ID for the supplied context, or omitted when no sessions exist +pub struct SubagentSettingsEntry { + /// Context tier override for matching subagents #[serde(skip_serializing_if = "Option::is_none")] - pub session_id: Option, + pub context_tier: Option, + /// Reasoning effort override for matching subagents + #[serde(skip_serializing_if = "Option::is_none")] + pub effort_level: Option, + /// Model override for matching subagents + #[serde(skip_serializing_if = "Option::is_none")] + pub model: Option, } -/// Session ID to look up the persisted remote-steerable flag for. +/// Subagent settings to apply, or null to clear the live session override /// ///
/// @@ -13152,12 +16382,22 @@ pub struct SessionsGetLastForContextResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsGetPersistedRemoteSteerableRequest { - /// Session ID to look up the persisted remote-steerable flag for - pub session_id: SessionId, +pub struct SubagentSettings { + /// Per-agent settings keyed by subagent agent_type + #[serde(skip_serializing_if = "Option::is_none")] + pub agents: Option>, + /// Names of subagents the user has turned off; they cannot be dispatched + #[serde(skip_serializing_if = "Option::is_none")] + pub disabled_subagents: Option>, + /// Maximum number of subagents that can run concurrently; applies to usage-based billing users only + #[serde(skip_serializing_if = "Option::is_none")] + pub max_concurrency: Option, + /// Maximum subagent nesting depth; applies to usage-based billing users only + #[serde(skip_serializing_if = "Option::is_none")] + pub max_depth: Option, } -/// The session's persisted remote-steerable flag, or omitted when no value has been persisted. +/// Tracked background agent task metadata, including IDs, status, timing, agent type, prompt, model, result, and latest response. /// ///
/// @@ -13167,13 +16407,59 @@ pub struct SessionsGetPersistedRemoteSteerableRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsGetPersistedRemoteSteerableResult { - /// The session's persisted remote-steerable flag if recorded; omitted when no value has been persisted +pub struct TaskAgentInfo { + /// ISO 8601 timestamp when the current active period began #[serde(skip_serializing_if = "Option::is_none")] - pub remote_steerable: Option, + pub active_started_at: Option, + /// Accumulated active execution time in milliseconds + #[serde(skip_serializing_if = "Option::is_none")] + pub active_time_ms: Option, + /// Type of agent running this task + pub agent_type: String, + /// Whether the task is currently in the original sync wait and can be moved to background mode. False once it is already backgrounded, idle, finished, or no longer has a promotable sync waiter. + #[serde(skip_serializing_if = "Option::is_none")] + pub can_promote_to_background: Option, + /// ISO 8601 timestamp when the task finished + #[serde(skip_serializing_if = "Option::is_none")] + pub completed_at: Option, + /// Short description of the task + pub description: String, + /// Error message when the task failed + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + /// Whether task execution is synchronously awaited or managed in the background + #[serde(skip_serializing_if = "Option::is_none")] + pub execution_mode: Option, + /// Unique task identifier + pub id: String, + /// ISO 8601 timestamp when the agent entered idle state + #[serde(skip_serializing_if = "Option::is_none")] + pub idle_since: Option, + /// Most recent response text from the agent + #[serde(skip_serializing_if = "Option::is_none")] + pub latest_response: Option, + /// Requested model override for the task when specified + #[serde(skip_serializing_if = "Option::is_none")] + pub model: Option, + /// Most recent prompt delivered to the agent. Updated whenever the agent receives a follow-up message. + pub prompt: String, + /// Runtime model resolved for the task when available + #[serde(skip_serializing_if = "Option::is_none")] + pub resolved_model: Option, + /// Result text from the task when available + #[serde(skip_serializing_if = "Option::is_none")] + pub result: Option, + /// ISO 8601 timestamp when the task was started + pub started_at: String, + /// Current lifecycle status of the task + pub status: TaskStatus, + /// Tool call ID associated with this agent task + pub tool_call_id: String, + /// Task kind + pub r#type: TaskAgentInfoType, } -/// Map of sessionId -> on-disk size in bytes for each session's workspace directory. +/// Timestamped display line for task progress output or recent agent activity. /// ///
/// @@ -13183,12 +16469,14 @@ pub struct SessionsGetPersistedRemoteSteerableResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionSizes { - /// Map of sessionId -> on-disk size in bytes for the session's workspace directory - pub sizes: HashMap, +pub struct TaskProgressLine { + /// Display message, e.g., "▸ bash", "✓ edit src/foo.ts" + pub message: String, + /// ISO 8601 timestamp when this event occurred + pub timestamp: String, } -/// Optional source filter, metadata-load limit, and context filter applied to the returned sessions. +/// Progress snapshot for an agent task, with recent activity lines and optional latest intent. /// ///
/// @@ -13198,25 +16486,17 @@ pub struct SessionSizes { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsListRequest { - /// Optional filter applied to the returned sessions - #[serde(skip_serializing_if = "Option::is_none")] - pub filter: Option, - /// When true, include detached maintenance sessions. Defaults to false for user-facing session lists. - #[serde(skip_serializing_if = "Option::is_none")] - pub include_detached: Option, - /// When provided, only the first N local sessions (sorted by modification time, newest first) load full metadata; remaining sessions return basic info only. Use 0 to return only basic info for every local session. Has no effect on remote entries (which always carry their full shape). - #[serde(skip_serializing_if = "Option::is_none")] - pub metadata_limit: Option, - /// Which session sources to include. Defaults to `local` for backward compatibility. - #[serde(skip_serializing_if = "Option::is_none")] - pub source: Option, - /// Only meaningful when `source` includes remote. When true, propagates errors from the remote service instead of silently returning an empty remote list. Defaults to false. +pub struct TaskAgentProgress { + /// The most recent intent reported by the agent #[serde(skip_serializing_if = "Option::is_none")] - pub throw_on_error: Option, + pub latest_intent: Option, + /// Recent tool execution events converted to display lines + pub recent_activity: Vec, + /// Progress kind + pub r#type: TaskAgentProgressType, } -/// Active session ID whose deferred repo-level hooks should be loaded. +/// Background tasks currently tracked by the session. /// ///
/// @@ -13226,12 +16506,12 @@ pub struct SessionsListRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsLoadDeferredRepoHooksRequest { - /// Active session ID whose deferred repo-level hooks should be loaded - pub session_id: SessionId, +pub struct TaskList { + /// Currently tracked tasks + pub tasks: Vec, } -/// Age threshold and optional flags controlling which old sessions are pruned (or simulated when dryRun is true). +/// Identifier of the background task to cancel. /// ///
/// @@ -13241,21 +16521,12 @@ pub struct SessionsLoadDeferredRepoHooksRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsPruneOldRequest { - /// When true, only report what would be deleted without performing any deletion - #[serde(skip_serializing_if = "Option::is_none")] - pub dry_run: Option, - /// Session IDs that should never be considered for pruning - #[serde(skip_serializing_if = "Option::is_none")] - pub exclude_session_ids: Option>, - /// When true, named sessions (set via /rename) are also eligible for pruning - #[serde(skip_serializing_if = "Option::is_none")] - pub include_named: Option, - /// Delete sessions whose modifiedTime is at least this many days old - pub older_than_days: i64, +pub struct TasksCancelRequest { + /// Task identifier + pub id: String, } -/// Session ID whose in-use lock should be released. +/// Indicates whether the background task was successfully cancelled. /// ///
/// @@ -13265,12 +16536,12 @@ pub struct SessionsPruneOldRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsReleaseLockRequest { - /// Session ID whose in-use lock should be released - pub session_id: SessionId, +pub struct TasksCancelResult { + /// Whether the task was successfully cancelled + pub cancelled: bool, } -/// Release the in-use lock held by this process for the given session. No-op when this process does not currently hold a lock for the session. +/// The first sync-waiting task that can currently be promoted to background mode. /// ///
/// @@ -13280,9 +16551,13 @@ pub struct SessionsReleaseLockRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsReleaseLockResult {} +pub struct TasksGetCurrentPromotableResult { + /// The first sync-waiting task (agent first, then shell) that can currently be promoted to background mode. Omitted if no such task exists. The returned task is guaranteed to have executionMode='sync' and canPromoteToBackground=true at the time of the call. + #[serde(skip_serializing_if = "Option::is_none")] + pub task: Option, +} -/// Active session ID and an optional flag for deferring repo-level hooks until folder trust. +/// Identifier of the background task to fetch progress for. /// ///
/// @@ -13292,15 +16567,12 @@ pub struct SessionsReleaseLockResult {} ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsReloadPluginHooksRequest { - /// When true, skip repo-level hooks. Use before folder trust is confirmed; loadDeferredRepoHooks loads them post-trust. - #[serde(skip_serializing_if = "Option::is_none")] - pub defer_repo_hooks: Option, - /// Active session ID to reload hooks for - pub session_id: SessionId, +pub struct TasksGetProgressRequest { + /// Task identifier (agent ID or shell ID) + pub id: String, } -/// Reload all hooks (user, plugin, optionally repo) and apply them to the active session. Call after installing or removing plugins so their hooks take effect immediately. No-op when no active session matches the given sessionId. +/// Progress information for the task, or null when no task with that ID is tracked. /// ///
/// @@ -13310,9 +16582,12 @@ pub struct SessionsReloadPluginHooksRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsReloadPluginHooksResult {} +pub struct TasksGetProgressResult { + /// Progress information for the task, discriminated by type. Returns null when no task with this ID is currently tracked. + pub progress: Option, +} -/// Session ID whose pending events should be flushed to disk. +/// Tracked shell task metadata, including ID, command, status, timing, attachment/execution mode, log path, and PID. /// ///
/// @@ -13322,12 +16597,39 @@ pub struct SessionsReloadPluginHooksResult {} ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsSaveRequest { - /// Session ID whose pending events should be flushed to disk - pub session_id: SessionId, +pub struct TaskShellInfo { + /// Whether the shell runs inside a managed PTY session or as an independent background process + pub attachment_mode: TaskShellInfoAttachmentMode, + /// Whether this shell task can be promoted to background mode + #[serde(skip_serializing_if = "Option::is_none")] + pub can_promote_to_background: Option, + /// Command being executed + pub command: String, + /// ISO 8601 timestamp when the task finished + #[serde(skip_serializing_if = "Option::is_none")] + pub completed_at: Option, + /// Short description of the task + pub description: String, + /// Whether task execution is synchronously awaited or managed in the background + #[serde(skip_serializing_if = "Option::is_none")] + pub execution_mode: Option, + /// Unique task identifier + pub id: String, + /// Path to the detached shell log, when available + #[serde(skip_serializing_if = "Option::is_none")] + pub log_path: Option, + /// Process ID when available + #[serde(skip_serializing_if = "Option::is_none")] + pub pid: Option, + /// ISO 8601 timestamp when the task was started + pub started_at: String, + /// Current lifecycle status of the task + pub status: TaskStatus, + /// Task kind + pub r#type: TaskShellInfoType, } -/// Flush a session's pending events to disk. No-op when no writer exists for the session (e.g., already closed). +/// Progress snapshot for a shell task, with recent stdout/stderr output and optional process ID. /// ///
/// @@ -13337,9 +16639,17 @@ pub struct SessionsSaveRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsSaveResult {} +pub struct TaskShellProgress { + /// Process ID when available + #[serde(skip_serializing_if = "Option::is_none")] + pub pid: Option, + /// Recent stdout/stderr lines from the running shell command + pub recent_output: String, + /// Progress kind + pub r#type: TaskShellProgressType, +} -/// Manager-wide additional plugins to register; replaces any previously-configured set. +/// The promoted task as it now exists in background mode, omitted if no promotable task was waiting. /// ///
/// @@ -13349,12 +16659,13 @@ pub struct SessionsSaveResult {} ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsSetAdditionalPluginsRequest { - /// Manager-wide additional plugins to register. Replaces any previously-configured set. Pass an empty array to clear. - pub plugins: Vec, +pub struct TasksPromoteCurrentToBackgroundResult { + /// The promoted task as it now exists in background mode, omitted if no promotable task was waiting. Atomic operation: avoids the race window of getCurrentPromotable + promoteToBackground. + #[serde(skip_serializing_if = "Option::is_none")] + pub task: Option, } -/// Replace the manager-wide additional plugins. New session creations and subsequent hook reloads see the new set; already-running sessions keep their existing hook installation until the next reload. +/// Identifier of the task to promote to background mode. /// ///
/// @@ -13364,9 +16675,12 @@ pub struct SessionsSetAdditionalPluginsRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsSetAdditionalPluginsResult {} +pub struct TasksPromoteToBackgroundRequest { + /// Task identifier + pub id: String, +} -/// Patch for the singleton's steering state. +/// Indicates whether the task was successfully promoted to background mode. /// ///
/// @@ -13376,12 +16690,12 @@ pub struct SessionsSetAdditionalPluginsResult {} ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsSetRemoteControlSteeringRequest { - /// Target steering state. Today only `true` is actionable on the underlying exporter; `false` is reserved for future use. - pub enabled: bool, +pub struct TasksPromoteToBackgroundResult { + /// Whether the task was successfully promoted to background mode + pub promoted: bool, } -/// Parameters for attaching the remote-control singleton to a session. +/// Refresh metadata for any detached background shells the runtime knows about. Use after a long pause to pick up exit/output state for shells running outside the agent loop. /// ///
/// @@ -13391,14 +16705,9 @@ pub struct SessionsSetRemoteControlSteeringRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsStartRemoteControlRequest { - /// Configuration for the runtime-managed remote-control singleton. - pub config: RemoteControlConfig, - /// Local session id to attach remote control to. - pub session_id: SessionId, -} +pub struct TasksRefreshResult {} -/// Parameters for stopping the remote-control singleton. +/// Identifier of the completed or cancelled task to remove from tracking. /// ///
/// @@ -13408,16 +16717,12 @@ pub struct SessionsStartRemoteControlRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsStopRemoteControlRequest { - /// When provided, the stop is rejected unless the singleton currently points at this session id (compare-and-swap semantics). - #[serde(skip_serializing_if = "Option::is_none")] - pub expected_session_id: Option, - /// When true, the singleton is unconditionally torn down regardless of `expectedSessionId`. Use during shutdown or explicit `/remote off`. - #[serde(skip_serializing_if = "Option::is_none")] - pub force: Option, +pub struct TasksRemoveRequest { + /// Task identifier + pub id: String, } -/// Parameters for atomically rebinding the remote-control singleton. +/// Indicates whether the task was removed. False when the task does not exist or is still running/idle. /// ///
/// @@ -13427,15 +16732,12 @@ pub struct SessionsStopRemoteControlRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsTransferRemoteControlRequest { - /// When provided, the transfer is rejected unless the singleton currently points at this session id (compare-and-swap semantics to avoid clobbering newer state). - #[serde(skip_serializing_if = "Option::is_none")] - pub expected_from_session_id: Option, - /// Local session id to point remote control at. - pub to_session_id: String, +pub struct TasksRemoveResult { + /// Whether the task was removed. Returns false if the task does not exist or is still running/idle (cancel it first). + pub removed: bool, } -/// Telemetry engagement ID for the session, when available. +/// Identifier of the target agent task, message content, and optional sender agent ID. /// ///
/// @@ -13445,13 +16747,17 @@ pub struct SessionsTransferRemoteControlRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionTelemetryEngagement { - /// Current telemetry engagement ID, when available. +pub struct TasksSendMessageRequest { + /// Agent ID of the sender, if sent on behalf of another agent #[serde(skip_serializing_if = "Option::is_none")] - pub engagement_id: Option, + pub from_agent_id: Option, + /// Agent task identifier + pub id: String, + /// Message content to send to the agent + pub message: String, } -/// Patch of mutable session options to apply to the running session. +/// Indicates whether the message was delivered, with an error message when delivery failed. /// ///
/// @@ -13461,183 +16767,40 @@ pub struct SessionTelemetryEngagement { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionUpdateOptionsParams { - /// Additional content-exclusion policies to merge into the session's policy set. - /// - ///
- /// - /// **Experimental.** This type is part of an experimental wire-protocol surface - /// and may change or be removed in future SDK or CLI releases. - /// - ///
- #[serde(skip_serializing_if = "Option::is_none")] - pub additional_content_exclusion_policies: - Option>, - /// Runtime context discriminator (e.g., `cli`, `actions`). - #[serde(skip_serializing_if = "Option::is_none")] - pub agent_context: Option, - /// Whether to include instructions from every MCP server in the system prompt instead of only allowlisted servers. - #[serde(skip_serializing_if = "Option::is_none")] - pub allow_all_mcp_server_instructions: Option, - /// Whether to disable the `ask_user` tool (encourages autonomous behavior). - #[serde(skip_serializing_if = "Option::is_none")] - pub ask_user_disabled: Option, - /// Allowlist of tool names available to this session. - #[serde(skip_serializing_if = "Option::is_none")] - pub available_tools: Option>, - /// Options scoped to the built-in CAPI (Copilot API) provider. - #[serde(skip_serializing_if = "Option::is_none")] - pub capi: Option, - /// Identifier of the client driving the session. - #[serde(skip_serializing_if = "Option::is_none")] - pub client_name: Option, - /// Whether to include the `Co-authored-by` trailer in commit messages. - #[serde(skip_serializing_if = "Option::is_none")] - pub coauthor_enabled: Option, - /// Context tier for models with tiered pricing. The session uses this to derive effective `modelCapabilitiesOverrides` so compaction, truncation, token display, and request limits honor the selected tier. - #[serde(skip_serializing_if = "Option::is_none")] - pub context_tier: Option, - /// Whether to allow auto-mode continuation across turns. - #[serde(skip_serializing_if = "Option::is_none")] - pub continue_on_auto_mode: Option, - /// Override URL for the Copilot API endpoint. - #[serde(skip_serializing_if = "Option::is_none")] - pub copilot_url: Option, - /// Whether to default custom agents to local-only execution. - #[serde(skip_serializing_if = "Option::is_none")] - pub custom_agents_local_only: Option, - /// Instruction source IDs to exclude from the system prompt. - #[serde(skip_serializing_if = "Option::is_none")] - pub disabled_instruction_sources: Option>, - /// Skill IDs that should be excluded from this session. - #[serde(skip_serializing_if = "Option::is_none")] - pub disabled_skills: Option>, - /// Whether to enable loading of `.github/hooks/` filesystem hooks. Separate from the SDK callback hook mechanism. - #[serde(skip_serializing_if = "Option::is_none")] - pub enable_file_hooks: Option, - /// Whether to enable host git operations (context resolution, child repo scanning, git info in system prompt). - #[serde(skip_serializing_if = "Option::is_none")] - pub enable_host_git_operations: Option, - /// Whether to discover custom instructions on demand after successful file views (AGENTS.md / CLAUDE.md / .github/copilot-instructions.md surfacing). Combined with `skipCustomInstructions` and the runtime-side `ON_DEMAND_INSTRUCTIONS` feature flag. - #[serde(skip_serializing_if = "Option::is_none")] - pub enable_on_demand_instruction_discovery: Option, - /// Whether to surface reasoning-summary events from the model. - #[serde(skip_serializing_if = "Option::is_none")] - pub enable_reasoning_summaries: Option, - /// Whether shell-script safety heuristics are enabled. - #[serde(skip_serializing_if = "Option::is_none")] - pub enable_script_safety: Option, - /// Whether to enable cross-session store writes and reads. - #[serde(skip_serializing_if = "Option::is_none")] - pub enable_session_store: Option, - /// Whether to enable skill directory scanning and loading. Falls back to enableConfigDiscovery when unset. - #[serde(skip_serializing_if = "Option::is_none")] - pub enable_skills: Option, - /// Whether to stream model responses. - #[serde(skip_serializing_if = "Option::is_none")] - pub enable_streaming: Option, - /// How env values are passed to MCP servers (`direct` inlines literal values; `indirect` resolves at launch). - #[serde(skip_serializing_if = "Option::is_none")] - pub env_value_mode: Option, - /// Override directory for the session-events log. When unset, the runtime's default events log directory is used. - #[serde(skip_serializing_if = "Option::is_none")] - pub events_log_directory: Option, - /// Built-in subagent names to exclude from this session. Excluded built-ins are hidden from agent discovery and cannot be dispatched unless a custom agent with the same name is available. - #[serde(skip_serializing_if = "Option::is_none")] - pub excluded_builtin_agents: Option>, - /// Denylist of tool names for this session. - #[serde(skip_serializing_if = "Option::is_none")] - pub excluded_tools: Option>, - /// Map of feature-flag IDs to their boolean enabled state. - #[serde(skip_serializing_if = "Option::is_none")] - pub feature_flags: Option>, - /// Built-in subagent names to include in this session. When specified, only these built-ins are available, subject to runtime availability and exclusions. Custom agents with the same name remain available. Set to null to remove the allowlist restriction. - #[serde(skip_serializing_if = "Option::is_none")] - pub included_builtin_agents: Option>, - /// Full set of installed plugins for the session. Replaces the existing list; the runtime invalidates the skills cache only when the list materially changes. - #[serde(skip_serializing_if = "Option::is_none")] - pub installed_plugins: Option>, - /// Stable integration identifier used for analytics and rate-limit attribution. - #[serde(skip_serializing_if = "Option::is_none")] - pub integration_id: Option, - /// Whether experimental capabilities are enabled. - #[serde(skip_serializing_if = "Option::is_none")] - pub is_experimental_mode: Option, - /// Whether interactive shell sessions are logged. - #[serde(skip_serializing_if = "Option::is_none")] - pub log_interactive_shells: Option, - /// Identifier sent to LSP-style integrations. - #[serde(skip_serializing_if = "Option::is_none")] - pub lsp_client_name: Option, - /// Whether to expose the `manage_schedule` tool to the agent. The runtime always owns the per-session schedule registry; this flag only controls tool exposure (typically gated to staff users). - #[serde(skip_serializing_if = "Option::is_none")] - pub manage_schedule_enabled: Option, - /// Maximum decoded byte size of a single model-facing binary tool result (e.g. an image) persisted inline in session events and re-presented to the model on later turns / resume. Larger results are persisted as a metadata-only marker and shown to the model as a short text note. Defaults to 10 MB. - #[serde(skip_serializing_if = "Option::is_none")] - pub max_inline_binary_bytes: Option, - /// The model ID to use for assistant turns. - #[serde(skip_serializing_if = "Option::is_none")] - pub model: Option, - /// Per-property model capability overrides for the selected model. - #[serde(skip_serializing_if = "Option::is_none")] - pub model_capabilities_overrides: Option, - /// Organization-level custom instructions to inject into the system prompt. - #[serde(skip_serializing_if = "Option::is_none")] - pub organization_custom_instructions: Option, - /// Custom model-provider configuration (BYOK). - #[serde(skip_serializing_if = "Option::is_none")] - pub provider: Option, - /// Reasoning effort for the selected model (model-defined enum). - #[serde(skip_serializing_if = "Option::is_none")] - pub reasoning_effort: Option, - /// Reasoning summary mode for supported model clients. - #[serde(skip_serializing_if = "Option::is_none")] - pub reasoning_summary: Option, - /// Whether the session is running in an interactive UI. - #[serde(skip_serializing_if = "Option::is_none")] - pub running_in_interactive_mode: Option, - /// Resolved sandbox configuration. - #[serde(skip_serializing_if = "Option::is_none")] - pub sandbox_config: Option, - /// Replaces the session's capability set with the given list. Use to enable or disable capabilities mid-session (e.g., remove `memory` for reproducible scripted runs). Omit the field to leave the existing capability set unchanged. - #[serde(skip_serializing_if = "Option::is_none")] - pub session_capabilities: Option>, - /// Optional session limits. Pass null to clear the session limits. - #[serde(skip_serializing_if = "Option::is_none")] - pub session_limits: Option, - /// Shell init profile (`None` or `NonInteractive`). - #[serde(skip_serializing_if = "Option::is_none")] - pub shell_init_profile: Option, - /// Per-shell process flags (e.g., `pwsh` arguments). - #[serde(skip_serializing_if = "Option::is_none")] - pub shell_process_flags: Option>, - /// Additional directories to search for skills. - #[serde(skip_serializing_if = "Option::is_none")] - pub skill_directories: Option>, - /// Whether to skip loading custom instruction sources. - #[serde(skip_serializing_if = "Option::is_none")] - pub skip_custom_instructions: Option, - /// Whether to skip embedding retrieval pipeline initialization and execution. - #[serde(skip_serializing_if = "Option::is_none")] - pub skip_embedding_retrieval: Option, - /// When true, the selected custom agent's prompt is not injected into the user message (skill context is still injected). Used by automation triggers where the agent prompt is already in the problem statement. - #[serde(skip_serializing_if = "Option::is_none")] - pub suppress_custom_agent_prompt: Option, - /// Controls how availableTools (allowlist) and excludedTools (denylist) combine when both are set. - #[serde(skip_serializing_if = "Option::is_none")] - pub tool_filter_precedence: Option, - /// Optional path for trajectory output. +pub struct TasksSendMessageResult { + /// Error message if delivery failed #[serde(skip_serializing_if = "Option::is_none")] - pub trajectory_file: Option, - /// Output verbosity level for supported models. + pub error: Option, + /// Whether the message was successfully delivered or steered + pub sent: bool, +} + +/// Agent type, prompt, name, and optional description and model override for the new task. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TasksStartAgentRequest { + /// Type of agent to start (e.g., 'explore', 'task', 'general-purpose') + pub agent_type: String, + /// Short description of the task #[serde(skip_serializing_if = "Option::is_none")] - pub verbosity: Option, - /// Absolute working-directory path for shell tools. + pub description: Option, + /// Optional model override #[serde(skip_serializing_if = "Option::is_none")] - pub working_directory: Option, + pub model: Option, + /// Short name for the agent, used to generate a human-readable ID + pub name: String, + /// Task prompt for the agent + pub prompt: String, } -/// Indicates whether the session options patch was applied successfully. +/// Identifier assigned to the newly started background agent task. /// ///
/// @@ -13647,15 +16810,12 @@ pub struct SessionUpdateOptionsParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionUpdateOptionsResult { - /// Number of hooks loaded from installed plugins, returned when installedPlugins is updated - #[serde(skip_serializing_if = "Option::is_none")] - pub plugin_hook_count: Option, - /// Whether the operation succeeded - pub success: bool, +pub struct TasksStartAgentResult { + /// Generated agent ID for the background task + pub agent_id: String, } -/// User-requested shell execution cancellation handle. +/// Wait until all in-flight background tasks (agents + shells) and any follow-up turns scheduled by their completions have settled. Returns when the runtime is fully drained or after an internal timeout (default 10 minutes; configurable via COPILOT_TASK_WAIT_TIMEOUT_SECONDS). /// ///
/// @@ -13665,12 +16825,24 @@ pub struct SessionUpdateOptionsResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ShellCancelUserRequestedRequest { - /// Request ID previously passed to executeUserRequested - pub request_id: RequestId, +pub struct TasksWaitForPendingResult {} + +/// Feature override key/value pairs to attach to subsequent telemetry events from this session. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TelemetrySetFeatureOverridesRequest { + /// Override key/value pairs to attach to subsequent telemetry events from this session. Replaces any previously-set overrides. + pub features: HashMap, } -/// Shell command to run, with optional working directory and timeout in milliseconds. +/// Authentication-info variant for SDK-configured token authentication, carrying host and the secret token value. /// ///
/// @@ -13680,18 +16852,45 @@ pub struct ShellCancelUserRequestedRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ShellExecRequest { - /// Shell command to execute - pub command: String, - /// Working directory (defaults to session working directory) +pub struct TokenAuthInfo { + /// Snapshot of the authenticated user's Copilot subscription info, if known. Mirrors the GitHub API `/copilot_internal/v2/token` user response shape — the runtime trusts this verbatim and does not re-fetch when set. #[serde(skip_serializing_if = "Option::is_none")] - pub cwd: Option, - /// Timeout in milliseconds (default: 30000) + pub copilot_user: Option, + /// Authentication host. + pub host: String, + /// The token value itself. Treat as a secret. + pub token: String, + /// SDK-side token authentication; the host configured the token directly via the SDK. + pub r#type: TokenAuthInfoType, +} + +/// Built-in tool metadata with identifier, optional namespaced name, description, input-parameter schema, and usage instructions. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Tool { + /// Description of what the tool does + pub description: String, + /// Optional instructions for how to use this tool effectively #[serde(skip_serializing_if = "Option::is_none")] - pub timeout: Option, + pub instructions: Option, + /// Tool identifier (e.g., "bash", "grep", "str_replace_editor") + pub name: String, + /// Optional namespaced name for declarative filtering (e.g., "playwright/navigate" for MCP tools) + #[serde(skip_serializing_if = "Option::is_none")] + pub namespaced_name: Option, + /// JSON Schema for the tool's input parameters + #[serde(skip_serializing_if = "Option::is_none")] + pub parameters: Option>, } -/// Identifier of the spawned process, used to correlate streamed output and exit notifications. +/// Built-in tools available for the requested model, with their parameters and instructions. /// ///
/// @@ -13701,12 +16900,12 @@ pub struct ShellExecRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ShellExecResult { - /// Unique identifier for tracking streamed output - pub process_id: String, +pub struct ToolList { + /// List of available built-in tools with metadata + pub tools: Vec, } -/// User-requested shell command and cancellation handle. +/// Current lightweight tool metadata snapshot for the session. /// ///
/// @@ -13716,14 +16915,12 @@ pub struct ShellExecResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ShellExecuteUserRequestedRequest { - /// Shell command to execute - pub command: String, - /// Caller-provided cancellation handle for this execution - pub request_id: RequestId, +pub struct ToolsGetCurrentMetadataResult { + /// Current tool metadata, or null when tools have not been initialized yet + pub tools: Option>, } -/// Identifier of a process previously returned by "shell.exec" and the signal to send. +/// Resolve, build, and validate the runtime tool list for this session. Subagent sessions and consumer flows that need an initialized tool set before `send` invoke this. Default base-class implementation is a no-op for sessions that don't support tool validation. /// ///
/// @@ -13733,15 +16930,25 @@ pub struct ShellExecuteUserRequestedRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ShellKillRequest { - /// Process identifier returned by shell.exec - pub process_id: String, - /// Signal to send (default: SIGTERM) +pub struct ToolsInitializeAndValidateResult {} + +/// Optional model identifier whose tool overrides should be applied to the listing. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ToolsListRequest { + /// Optional model ID — when provided, the returned tool list reflects model-specific overrides #[serde(skip_serializing_if = "Option::is_none")] - pub signal: Option, + pub model: Option, } -/// Indicates whether the signal was delivered; false if the process was unknown or already exited. +/// Empty result after applying subagent settings /// ///
/// @@ -13751,12 +16958,26 @@ pub struct ShellKillRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ShellKillResult { - /// Whether the signal was sent successfully - pub killed: bool, +pub struct ToolsUpdateSubagentSettingsResult {} + +/// Selectable option for a UI elicitation multi-select array item, with submitted value and display label. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UIElicitationArrayAnyOfFieldItemsAnyOf { + /// Value submitted when this option is selected. + pub r#const: String, + /// Display label for this option. + pub title: String, } -/// Parameters for shutting down the session +/// Schema applied to each item in the array. /// ///
/// @@ -13766,16 +16987,12 @@ pub struct ShellKillResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ShutdownRequest { - /// Optional human-readable reason. Typically the message of the error that triggered shutdown when type is 'error'. - #[serde(skip_serializing_if = "Option::is_none")] - pub reason: Option, - /// Why the session is being shut down. Defaults to "routine" when omitted. - #[serde(skip_serializing_if = "Option::is_none")] - pub r#type: Option, +pub struct UIElicitationArrayAnyOfFieldItems { + /// Selectable options, each with a value and a display label. + pub any_of: Vec, } -/// Skill metadata available to a session, with name, description, source, enabled/invocable state, path, plugin, and argument hint. +/// Multi-select string field where each option pairs a value with a display label. /// ///
/// @@ -13785,29 +17002,29 @@ pub struct ShutdownRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct Skill { - /// Optional freeform hint describing the skill's expected arguments, from the `argument-hint` frontmatter field +pub struct UIElicitationArrayAnyOfField { + /// Default values selected when the form is first shown. #[serde(skip_serializing_if = "Option::is_none")] - pub argument_hint: Option, - /// Description of what the skill does - pub description: String, - /// Whether the skill is currently enabled - pub enabled: bool, - /// Unique identifier for the skill - pub name: String, - /// Absolute path to the skill file + pub default: Option>, + /// Help text describing the field. #[serde(skip_serializing_if = "Option::is_none")] - pub path: Option, - /// Name of the plugin that provides the skill, when source is 'plugin' + pub description: Option, + /// Schema applied to each item in the array. + pub items: UIElicitationArrayAnyOfFieldItems, + /// Maximum number of items the user may select. #[serde(skip_serializing_if = "Option::is_none")] - pub plugin_name: Option, - /// Source location type (e.g., project, personal-copilot, plugin, builtin) - pub source: SkillSource, - /// Whether the skill can be invoked by the user as a slash command - pub user_invocable: bool, + pub max_items: Option, + /// Minimum number of items the user must select. + #[serde(skip_serializing_if = "Option::is_none")] + pub min_items: Option, + /// Human-readable label for the field. + #[serde(skip_serializing_if = "Option::is_none")] + pub title: Option, + /// Type discriminator. Always "array". + pub r#type: UIElicitationArrayAnyOfFieldType, } -/// Canonical directory where skills can be discovered or created, with scope, preference, and optional project path. +/// Schema applied to each item in the array. /// ///
/// @@ -13817,19 +17034,14 @@ pub struct Skill { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SkillDiscoveryPath { - /// Absolute path of the create/discovery target (may not exist on disk yet) - pub path: String, - /// Whether this is the canonical directory to create a new skill in its tier. At most one entry per tier is preferred; the `personal-agents` and `custom` scopes are never preferred. - pub preferred_for_creation: bool, - /// The input project path this directory was derived from (only for project scope) - #[serde(skip_serializing_if = "Option::is_none")] - pub project_path: Option, - /// Which tier this directory belongs to - pub scope: SkillDiscoveryScope, +pub struct UIElicitationArrayEnumFieldItems { + /// Allowed string values for each selected item. + pub r#enum: Vec, + /// Type discriminator. Always "string". + pub r#type: UIElicitationArrayEnumFieldItemsType, } -/// Canonical locations where skills can be created so the runtime will recognize them. +/// Multi-select string field whose allowed values are defined inline. /// ///
/// @@ -13839,12 +17051,29 @@ pub struct SkillDiscoveryPath { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SkillDiscoveryPathList { - /// Canonical skill create/discovery directories, in priority order - pub paths: Vec, +pub struct UIElicitationArrayEnumField { + /// Default values selected when the form is first shown. + #[serde(skip_serializing_if = "Option::is_none")] + pub default: Option>, + /// Help text describing the field. + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + /// Schema applied to each item in the array. + pub items: UIElicitationArrayEnumFieldItems, + /// Maximum number of items the user may select. + #[serde(skip_serializing_if = "Option::is_none")] + pub max_items: Option, + /// Minimum number of items the user must select. + #[serde(skip_serializing_if = "Option::is_none")] + pub min_items: Option, + /// Human-readable label for the field. + #[serde(skip_serializing_if = "Option::is_none")] + pub title: Option, + /// Type discriminator. Always "array". + pub r#type: UIElicitationArrayEnumFieldType, } -/// Skills available to the session, with their enabled state. +/// JSON Schema describing the form fields to present to the user /// ///
/// @@ -13854,12 +17083,17 @@ pub struct SkillDiscoveryPathList { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SkillList { - /// Available skills - pub skills: Vec, +pub struct UIElicitationSchema { + /// Form field definitions, keyed by field name + pub properties: HashMap, + /// List of required field names + #[serde(skip_serializing_if = "Option::is_none")] + pub required: Option>, + /// Schema type indicator (always 'object') + pub r#type: UIElicitationSchemaType, } -/// Skill names to mark as disabled in global configuration, replacing any previous list. +/// Prompt message and JSON schema describing the form fields to elicit from the user. /// ///
/// @@ -13869,12 +17103,14 @@ pub struct SkillList { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SkillsConfigSetDisabledSkillsRequest { - /// List of skill names to disable - pub disabled_skills: Vec, +pub struct UIElicitationRequest { + /// Message describing what information is needed from the user + pub message: String, + /// JSON Schema describing the form fields to present to the user + pub requested_schema: UIElicitationSchema, } -/// Name of the skill to disable for the session. +/// The elicitation response (accept with form values, decline, or cancel) /// ///
/// @@ -13884,12 +17120,15 @@ pub struct SkillsConfigSetDisabledSkillsRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SkillsDisableRequest { - /// Name of the skill to disable - pub name: String, +pub struct UIElicitationResponse { + /// The user's response: accept (submitted), decline (rejected), or cancel (dismissed) + pub action: UIElicitationResponseAction, + /// The form values submitted by the user (present when action is 'accept') + #[serde(skip_serializing_if = "Option::is_none")] + pub content: Option>, } -/// Optional project paths and additional skill directories to include in discovery. +/// Indicates whether the elicitation response was accepted; false if it was already resolved by another client. /// ///
/// @@ -13899,19 +17138,12 @@ pub struct SkillsDisableRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SkillsDiscoverRequest { - /// When true, omit skills from the host's global sources (personal, custom, plugin, and built-in), returning only project-scoped skills. For multitenant deployments. - #[serde(skip_serializing_if = "Option::is_none")] - pub exclude_host_skills: Option, - /// Optional list of project directory paths to scan for project-scoped skills - #[serde(skip_serializing_if = "Option::is_none")] - pub project_paths: Option>, - /// Optional list of additional skill directory paths to include - #[serde(skip_serializing_if = "Option::is_none")] - pub skill_directories: Option>, +pub struct UIElicitationResult { + /// Whether the response was accepted. False if the request was already resolved by another client. + pub success: bool, } -/// Name of the skill to enable for the session. +/// Boolean field rendered as a yes/no toggle. /// ///
/// @@ -13921,12 +17153,21 @@ pub struct SkillsDiscoverRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SkillsEnableRequest { - /// Name of the skill to enable - pub name: String, +pub struct UIElicitationSchemaPropertyBoolean { + /// Default value selected when the form is first shown. + #[serde(skip_serializing_if = "Option::is_none")] + pub default: Option, + /// Help text describing the field. + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + /// Human-readable label for the field. + #[serde(skip_serializing_if = "Option::is_none")] + pub title: Option, + /// Type discriminator. Always "boolean". + pub r#type: UIElicitationSchemaPropertyBooleanType, } -/// Optional project paths to enumerate. +/// Numeric field accepting either a number or an integer. /// ///
/// @@ -13936,16 +17177,27 @@ pub struct SkillsEnableRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SkillsGetDiscoveryPathsRequest { - /// When true, omit the host's personal and custom skill directories, leaving only project directories. For multitenant deployments. +pub struct UIElicitationSchemaPropertyNumber { + /// Default value populated in the input when the form is first shown. #[serde(skip_serializing_if = "Option::is_none")] - pub exclude_host_skills: Option, - /// Optional list of project directory paths. When omitted or empty, only personal and custom directories are returned. + pub default: Option, + /// Help text describing the field. #[serde(skip_serializing_if = "Option::is_none")] - pub project_paths: Option>, + pub description: Option, + /// Maximum allowed value (inclusive). + #[serde(skip_serializing_if = "Option::is_none")] + pub maximum: Option, + /// Minimum allowed value (inclusive). + #[serde(skip_serializing_if = "Option::is_none")] + pub minimum: Option, + /// Human-readable label for the field. + #[serde(skip_serializing_if = "Option::is_none")] + pub title: Option, + /// Numeric type accepted by the field. + pub r#type: UIElicitationSchemaPropertyNumberType, } -/// Skill invocation record with name, path, content, allowed tools, and turn number. +/// Free-text string field with optional length and format constraints. /// ///
/// @@ -13955,21 +17207,30 @@ pub struct SkillsGetDiscoveryPathsRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SkillsInvokedSkill { - /// Tools that should be auto-approved when this skill is active, captured at invocation time +pub struct UIElicitationSchemaPropertyString { + /// Default value populated in the input when the form is first shown. #[serde(skip_serializing_if = "Option::is_none")] - pub allowed_tools: Option>, - /// Full content of the skill file - pub content: String, - /// Turn number when the skill was invoked - pub invoked_at_turn: i64, - /// Unique identifier for the skill - pub name: String, - /// Path to the SKILL.md file - pub path: String, + pub default: Option, + /// Help text describing the field. + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + /// Optional format hint that constrains the accepted input. + #[serde(skip_serializing_if = "Option::is_none")] + pub format: Option, + /// Maximum number of characters allowed. + #[serde(skip_serializing_if = "Option::is_none")] + pub max_length: Option, + /// Minimum number of characters required. + #[serde(skip_serializing_if = "Option::is_none")] + pub min_length: Option, + /// Human-readable label for the field. + #[serde(skip_serializing_if = "Option::is_none")] + pub title: Option, + /// Type discriminator. Always "string". + pub r#type: UIElicitationSchemaPropertyStringType, } -/// Skills invoked during this session, ordered by invocation time (most recent last). +/// Single-select string field whose allowed values are defined inline. /// ///
/// @@ -13979,12 +17240,26 @@ pub struct SkillsInvokedSkill { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SkillsGetInvokedResult { - /// Skills invoked during this session, ordered by invocation time (most recent last) - pub skills: Vec, +pub struct UIElicitationStringEnumField { + /// Default value selected when the form is first shown. + #[serde(skip_serializing_if = "Option::is_none")] + pub default: Option, + /// Help text describing the field. + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + /// Allowed string values. + pub r#enum: Vec, + /// Optional display labels for each enum value, in the same order as `enum`. + #[serde(skip_serializing_if = "Option::is_none")] + pub enum_names: Option>, + /// Human-readable label for the field. + #[serde(skip_serializing_if = "Option::is_none")] + pub title: Option, + /// Type discriminator. Always "string". + pub r#type: UIElicitationStringEnumFieldType, } -/// Diagnostics from reloading skill definitions, with warnings and errors as separate lists. +/// Selectable option for a UI elicitation single-select string field, with submitted value and display label. /// ///
/// @@ -13994,14 +17269,14 @@ pub struct SkillsGetInvokedResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SkillsLoadDiagnostics { - /// Errors emitted while loading skills (e.g. skills that failed to load entirely) - pub errors: Vec, - /// Warnings emitted while loading skills (e.g. skills that loaded but had issues) - pub warnings: Vec, +pub struct UIElicitationStringOneOfFieldOneOf { + /// Value submitted when this option is selected. + pub r#const: String, + /// Display label for this option. + pub title: String, } -/// Slash-command invocation result that submits an agent prompt, with display prompt, optional mode, optional user-facing notice, and settings-change flag. +/// Single-select string field where each option pairs a value with a display label. /// ///
/// @@ -14011,25 +17286,23 @@ pub struct SkillsLoadDiagnostics { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SlashCommandAgentPromptResult { - /// Prompt text to display to the user - pub display_prompt: String, - /// Agent prompt result discriminator - pub kind: SlashCommandAgentPromptResultKind, - /// Optional target session mode for the agent prompt +pub struct UIElicitationStringOneOfField { + /// Default value selected when the form is first shown. #[serde(skip_serializing_if = "Option::is_none")] - pub mode: Option, - /// Optional user-facing notice to show before the prompt is submitted + pub default: Option, + /// Help text describing the field. #[serde(skip_serializing_if = "Option::is_none")] - pub notice: Option, - /// Prompt to submit to the agent - pub prompt: String, - /// True when the invocation mutated user runtime settings; consumers caching settings should refresh + pub description: Option, + /// Selectable options, each with a value and a display label. + pub one_of: Vec, + /// Human-readable label for the field. #[serde(skip_serializing_if = "Option::is_none")] - pub runtime_settings_changed: Option, + pub title: Option, + /// Type discriminator. Always "string". + pub r#type: UIElicitationStringOneOfFieldType, } -/// Slash-command invocation result indicating completion, with optional message and settings-change flag. +/// Transient question to answer without adding it to conversation history. /// ///
/// @@ -14039,18 +17312,20 @@ pub struct SlashCommandAgentPromptResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SlashCommandCompletedResult { - /// Completed result discriminator - pub kind: SlashCommandCompletedResultKind, - /// Optional user-facing message describing the completed command +pub struct UIEphemeralQueryRequest { + /// In-process `AbortSignal` forwarded to the model client to cancel an in-flight request. Marked internal: excluded from the public SDK surface. Replaced by an explicit cancellation token + cancel RPC in the SDK migration. + #[doc(hidden)] #[serde(skip_serializing_if = "Option::is_none")] - pub message: Option, - /// True when the invocation mutated user runtime settings; consumers caching settings should refresh + pub(crate) abort_signal: Option, + /// In-process streaming callback `(text) => void` invoked with each token as the model emits it. Marked internal: excluded from the public SDK surface. In a process-separated SDK this is replaced by a streaming RPC that yields chunks and a final answer. + #[doc(hidden)] #[serde(skip_serializing_if = "Option::is_none")] - pub runtime_settings_changed: Option, + pub(crate) on_chunk: Option, + /// Question to answer from the current conversation context. + pub question: String, } -/// Slash-command invocation result containing text output plus Markdown/ANSI rendering flags. +/// Transient answer generated from current conversation context. /// ///
/// @@ -14060,23 +17335,12 @@ pub struct SlashCommandCompletedResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SlashCommandTextResult { - /// Text result discriminator - pub kind: SlashCommandTextResultKind, - /// Whether text contains Markdown - #[serde(skip_serializing_if = "Option::is_none")] - pub markdown: Option, - /// Whether ANSI sequences should be preserved - #[serde(skip_serializing_if = "Option::is_none")] - pub preserve_ansi: Option, - /// True when the invocation mutated user runtime settings; consumers caching settings should refresh - #[serde(skip_serializing_if = "Option::is_none")] - pub runtime_settings_changed: Option, - /// Text output for the client to render - pub text: String, +pub struct UIEphemeralQueryResult { + /// Full assistant response text. + pub answer: String, } -/// Selectable slash-command subcommand option with name, description, and optional group label. +/// User response for a pending exit-plan-mode request, with approval state, selected action, auto-approve flag, and feedback. /// ///
/// @@ -14086,17 +17350,24 @@ pub struct SlashCommandTextResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SlashCommandSelectSubcommandOption { - /// Human-readable description of the subcommand - pub description: String, - /// Optional group label for organizing options +pub struct UIExitPlanModeResponse { + /// Whether the plan was approved. + pub approved: bool, + /// Whether subsequent edits should be auto-approved without confirmation. #[serde(skip_serializing_if = "Option::is_none")] - pub group: Option, - /// Subcommand name to invoke - pub name: String, + pub auto_approve_edits: Option, + /// When true, the agent is instructed to end its turn without starting implementation so the client can restore the session model and auto-submit a fresh implementation turn on it. Set only when a distinct plan configuration (a different model, reasoning effort, or context tier) actually ran the planning turn. + #[serde(skip_serializing_if = "Option::is_none")] + pub defer_implementation: Option, + /// Feedback from the user when they declined the plan or requested changes. + #[serde(skip_serializing_if = "Option::is_none")] + pub feedback: Option, + /// The action the user selected. Defaults to 'autopilot' when autoApproveEdits is true, otherwise 'interactive'. + #[serde(skip_serializing_if = "Option::is_none")] + pub selected_action: Option, } -/// Slash-command invocation result asking the client to present subcommand options for a parent command. +/// Request ID of a pending `auto_mode_switch.requested` event and the user's response. /// ///
/// @@ -14106,21 +17377,14 @@ pub struct SlashCommandSelectSubcommandOption { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SlashCommandSelectSubcommandResult { - /// Parent command name that requires subcommand selection - pub command: String, - /// Select subcommand result discriminator - pub kind: SlashCommandSelectSubcommandResultKind, - /// Available subcommand options for the client to present - pub options: Vec, - /// True when the invocation mutated user runtime settings; consumers caching settings should refresh - #[serde(skip_serializing_if = "Option::is_none")] - pub runtime_settings_changed: Option, - /// Human-readable title for the selection UI - pub title: String, +pub struct UIHandlePendingAutoModeSwitchRequest { + /// The unique request ID from the auto_mode_switch.requested event + pub request_id: RequestId, + /// User's choice for auto-mode switching: yes (allow this turn), yes_always (allow + persist as setting), or no (decline). + pub response: UIAutoModeSwitchResponse, } -/// Subagent model, reasoning effort, and context tier settings +/// Pending elicitation request ID and the user's response (accept/decline/cancel + form values). /// ///
/// @@ -14130,19 +17394,14 @@ pub struct SlashCommandSelectSubcommandResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SubagentSettingsEntry { - /// Context tier override for matching subagents - #[serde(skip_serializing_if = "Option::is_none")] - pub context_tier: Option, - /// Reasoning effort override for matching subagents - #[serde(skip_serializing_if = "Option::is_none")] - pub effort_level: Option, - /// Model override for matching subagents - #[serde(skip_serializing_if = "Option::is_none")] - pub model: Option, +pub struct UIHandlePendingElicitationRequest { + /// The unique request ID from the elicitation.requested event + pub request_id: RequestId, + /// The elicitation response (accept with form values, decline, or cancel) + pub result: UIElicitationResponse, } -/// Subagent settings to apply, or null to clear the live session override +/// Request ID of a pending `exit_plan_mode.requested` event and the user's response. /// ///
/// @@ -14152,22 +17411,14 @@ pub struct SubagentSettingsEntry { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SubagentSettings { - /// Per-agent settings keyed by subagent agent_type - #[serde(skip_serializing_if = "Option::is_none")] - pub agents: Option>, - /// Names of subagents the user has turned off; they cannot be dispatched - #[serde(skip_serializing_if = "Option::is_none")] - pub disabled_subagents: Option>, - /// Maximum number of subagents that can run concurrently; applies to usage-based billing users only - #[serde(skip_serializing_if = "Option::is_none")] - pub max_concurrency: Option, - /// Maximum subagent nesting depth; applies to usage-based billing users only - #[serde(skip_serializing_if = "Option::is_none")] - pub max_depth: Option, +pub struct UIHandlePendingExitPlanModeRequest { + /// The unique request ID from the exit_plan_mode.requested event + pub request_id: RequestId, + /// User response for a pending exit-plan-mode request, with approval state, selected action, auto-approve flag, and feedback. + pub response: UIExitPlanModeResponse, } -/// Tracked background agent task metadata, including IDs, status, timing, agent type, prompt, model, result, and latest response. +/// Indicates whether the pending UI request was resolved by this call. /// ///
/// @@ -14177,59 +17428,12 @@ pub struct SubagentSettings { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct TaskAgentInfo { - /// ISO 8601 timestamp when the current active period began - #[serde(skip_serializing_if = "Option::is_none")] - pub active_started_at: Option, - /// Accumulated active execution time in milliseconds - #[serde(skip_serializing_if = "Option::is_none")] - pub active_time_ms: Option, - /// Type of agent running this task - pub agent_type: String, - /// Whether the task is currently in the original sync wait and can be moved to background mode. False once it is already backgrounded, idle, finished, or no longer has a promotable sync waiter. - #[serde(skip_serializing_if = "Option::is_none")] - pub can_promote_to_background: Option, - /// ISO 8601 timestamp when the task finished - #[serde(skip_serializing_if = "Option::is_none")] - pub completed_at: Option, - /// Short description of the task - pub description: String, - /// Error message when the task failed - #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, - /// Whether task execution is synchronously awaited or managed in the background - #[serde(skip_serializing_if = "Option::is_none")] - pub execution_mode: Option, - /// Unique task identifier - pub id: String, - /// ISO 8601 timestamp when the agent entered idle state - #[serde(skip_serializing_if = "Option::is_none")] - pub idle_since: Option, - /// Most recent response text from the agent - #[serde(skip_serializing_if = "Option::is_none")] - pub latest_response: Option, - /// Requested model override for the task when specified - #[serde(skip_serializing_if = "Option::is_none")] - pub model: Option, - /// Most recent prompt delivered to the agent. Updated whenever the agent receives a follow-up message. - pub prompt: String, - /// Runtime model resolved for the task when available - #[serde(skip_serializing_if = "Option::is_none")] - pub resolved_model: Option, - /// Result text from the task when available - #[serde(skip_serializing_if = "Option::is_none")] - pub result: Option, - /// ISO 8601 timestamp when the task was started - pub started_at: String, - /// Current lifecycle status of the task - pub status: TaskStatus, - /// Tool call ID associated with this agent task - pub tool_call_id: String, - /// Task kind - pub r#type: TaskAgentInfoType, +pub struct UIHandlePendingResult { + /// True if the request was still pending and was resolved by this call. False if the request ID was unknown, already resolved by another client (e.g. GitHub), expired, or otherwise no longer pending. + pub success: bool, } -/// Timestamped display line for task progress output or recent agent activity. +/// Optional sampling result payload. Omit to reject/cancel the sampling request without providing a result. /// ///
/// @@ -14239,14 +17443,9 @@ pub struct TaskAgentInfo { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct TaskProgressLine { - /// Display message, e.g., "▸ bash", "✓ edit src/foo.ts" - pub message: String, - /// ISO 8601 timestamp when this event occurred - pub timestamp: String, -} +pub struct UIHandlePendingSamplingResponse {} -/// Progress snapshot for an agent task, with recent activity lines and optional latest intent. +/// Request ID of a pending `sampling.requested` event and an optional sampling result payload (omit to reject). /// ///
/// @@ -14256,17 +17455,15 @@ pub struct TaskProgressLine { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct TaskAgentProgress { - /// The most recent intent reported by the agent +pub struct UIHandlePendingSamplingRequest { + /// The unique request ID from the sampling.requested event + pub request_id: RequestId, + /// Optional sampling result payload. Omit to reject/cancel the sampling request without providing a result. #[serde(skip_serializing_if = "Option::is_none")] - pub latest_intent: Option, - /// Recent tool execution events converted to display lines - pub recent_activity: Vec, - /// Progress kind - pub r#type: TaskAgentProgressType, + pub response: Option, } -/// Background tasks currently tracked by the session. +/// The user's selected action for an exhausted session limit. /// ///
/// @@ -14276,12 +17473,18 @@ pub struct TaskAgentProgress { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct TaskList { - /// Currently tracked tasks - pub tasks: Vec, +pub struct UISessionLimitsExhaustedResponse { + /// Action selected by the user. + pub action: UISessionLimitsExhaustedResponseAction, + /// AI Credits to add to the current max when action is 'add'. + #[serde(skip_serializing_if = "Option::is_none")] + pub additional_ai_credits: Option, + /// New absolute max AI Credits when action is 'set'. + #[serde(skip_serializing_if = "Option::is_none")] + pub max_ai_credits: Option, } -/// Identifier of the background task to cancel. +/// Request ID of a pending `session_limits_exhausted.requested` event and the user's selected limit action. /// ///
/// @@ -14291,12 +17494,14 @@ pub struct TaskList { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct TasksCancelRequest { - /// Task identifier - pub id: String, +pub struct UIHandlePendingSessionLimitsExhaustedRequest { + /// The unique request ID from the session_limits_exhausted.requested event + pub request_id: RequestId, + /// The selected session-limit action. + pub response: UISessionLimitsExhaustedResponse, } -/// Indicates whether the background task was successfully cancelled. +/// User response for a pending user-input request, with answer text and whether it was typed freeform. /// ///
/// @@ -14306,12 +17511,14 @@ pub struct TasksCancelRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct TasksCancelResult { - /// Whether the task was successfully cancelled - pub cancelled: bool, +pub struct UIUserInputResponse { + /// The user's answer text + pub answer: String, + /// True if the user typed a freeform response, false if they selected a presented choice. Used by telemetry to differentiate between free text input and choice selection. + pub was_freeform: bool, } -/// The first sync-waiting task that can currently be promoted to background mode. +/// Request ID of a pending `user_input.requested` event and the user's response. /// ///
/// @@ -14321,13 +17528,14 @@ pub struct TasksCancelResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct TasksGetCurrentPromotableResult { - /// The first sync-waiting task (agent first, then shell) that can currently be promoted to background mode. Omitted if no such task exists. The returned task is guaranteed to have executionMode='sync' and canPromoteToBackground=true at the time of the call. - #[serde(skip_serializing_if = "Option::is_none")] - pub task: Option, +pub struct UIHandlePendingUserInputRequest { + /// The unique request ID from the user_input.requested event + pub request_id: RequestId, + /// User response for a pending user-input request, with answer text and whether it was typed freeform. + pub response: UIUserInputResponse, } -/// Identifier of the background task to fetch progress for. +/// Register an in-process handler for `auto_mode_switch.requested` events. The caller still attaches the actual listener via the standard event-subscription mechanism; this registration solely tells the server bridge to skip its own dispatch (so a remote client doesn't race the in-process handler for the same requestId). /// ///
/// @@ -14337,12 +17545,12 @@ pub struct TasksGetCurrentPromotableResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct TasksGetProgressRequest { - /// Task identifier (agent ID or shell ID) - pub id: String, +pub struct UIRegisterDirectAutoModeSwitchHandlerResult { + /// Opaque handle representing the registration. Pass this same handle to `unregisterDirectAutoModeSwitchHandler` when the in-process handler is no longer active. Multiple registrations are reference-counted; the server bridge will only dispatch auto-mode-switch requests when no handles are active. + pub handle: String, } -/// Progress information for the task, or null when no task with that ID is tracked. +/// Opaque handle previously returned by `registerDirectAutoModeSwitchHandler` to release. /// ///
/// @@ -14352,12 +17560,12 @@ pub struct TasksGetProgressRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct TasksGetProgressResult { - /// Progress information for the task, discriminated by type. Returns null when no task with this ID is currently tracked. - pub progress: Option, +pub struct UIUnregisterDirectAutoModeSwitchHandlerRequest { + /// Handle previously returned by `registerDirectAutoModeSwitchHandler` + pub handle: String, } -/// Tracked shell task metadata, including ID, command, status, timing, attachment/execution mode, log path, and PID. +/// Indicates whether the handle was active and the registration count was decremented. /// ///
/// @@ -14367,39 +17575,30 @@ pub struct TasksGetProgressResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct TaskShellInfo { - /// Whether the shell runs inside a managed PTY session or as an independent background process - pub attachment_mode: TaskShellInfoAttachmentMode, - /// Whether this shell task can be promoted to background mode - #[serde(skip_serializing_if = "Option::is_none")] - pub can_promote_to_background: Option, - /// Command being executed - pub command: String, - /// ISO 8601 timestamp when the task finished +pub struct UIUnregisterDirectAutoModeSwitchHandlerResult { + /// True if the handle was active and decremented the counter; false if the handle was unknown. + pub unregistered: bool, +} + +/// Configured per-agent subagent overrides +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UpdateSubagentSettingsRequestSubagents { + /// Per-agent settings keyed by subagent agent_type #[serde(skip_serializing_if = "Option::is_none")] - pub completed_at: Option, - /// Short description of the task - pub description: String, - /// Whether task execution is synchronously awaited or managed in the background + pub agents: Option>, + /// Names of subagents the user has turned off; they cannot be dispatched #[serde(skip_serializing_if = "Option::is_none")] - pub execution_mode: Option, - /// Unique task identifier - pub id: String, - /// Path to the detached shell log, when available + pub disabled_subagents: Option>, + /// Maximum number of subagents that can run concurrently; applies to usage-based billing users only #[serde(skip_serializing_if = "Option::is_none")] - pub log_path: Option, - /// Process ID when available + pub max_concurrency: Option, + /// Maximum subagent nesting depth; applies to usage-based billing users only #[serde(skip_serializing_if = "Option::is_none")] - pub pid: Option, - /// ISO 8601 timestamp when the task was started - pub started_at: String, - /// Current lifecycle status of the task - pub status: TaskStatus, - /// Task kind - pub r#type: TaskShellInfoType, + pub max_depth: Option, } -/// Progress snapshot for a shell task, with recent stdout/stderr output and optional process ID. +/// Subagent settings to apply to the current session /// ///
/// @@ -14409,17 +17608,33 @@ pub struct TaskShellInfo { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct TaskShellProgress { - /// Process ID when available - #[serde(skip_serializing_if = "Option::is_none")] - pub pid: Option, - /// Recent stdout/stderr lines from the running shell command - pub recent_output: String, - /// Progress kind - pub r#type: TaskShellProgressType, +pub struct UpdateSubagentSettingsRequest { + /// Subagent settings to apply, or null to clear the live session override + pub subagents: Option, +} + +/// Aggregated code change metrics +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UsageMetricsCodeChanges { + /// Distinct file paths modified during the session + pub files_modified: Vec, + /// Number of distinct files modified + pub files_modified_count: i64, + /// Total lines of code added + pub lines_added: i64, + /// Total lines of code removed + pub lines_removed: i64, } -/// The promoted task as it now exists in background mode, omitted if no promotable task was waiting. +/// Request count and cost metrics for this model /// ///
/// @@ -14429,13 +17644,14 @@ pub struct TaskShellProgress { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct TasksPromoteCurrentToBackgroundResult { - /// The promoted task as it now exists in background mode, omitted if no promotable task was waiting. Atomic operation: avoids the race window of getCurrentPromotable + promoteToBackground. - #[serde(skip_serializing_if = "Option::is_none")] - pub task: Option, +pub struct UsageMetricsModelMetricRequests { + /// User-initiated premium request cost (with multiplier applied) + pub cost: f64, + /// Number of API requests made with this model + pub count: i64, } -/// Identifier of the task to promote to background mode. +/// Per-model token-detail entry containing the accumulated token count for one token type. /// ///
/// @@ -14445,12 +17661,12 @@ pub struct TasksPromoteCurrentToBackgroundResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct TasksPromoteToBackgroundRequest { - /// Task identifier - pub id: String, +pub struct UsageMetricsModelMetricTokenDetail { + /// Accumulated token count for this token type + pub token_count: i64, } -/// Indicates whether the task was successfully promoted to background mode. +/// Token usage metrics for this model /// ///
/// @@ -14460,12 +17676,21 @@ pub struct TasksPromoteToBackgroundRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct TasksPromoteToBackgroundResult { - /// Whether the task was successfully promoted to background mode - pub promoted: bool, +pub struct UsageMetricsModelMetricUsage { + /// Total tokens read from prompt cache + pub cache_read_tokens: i64, + /// Total tokens written to prompt cache + pub cache_write_tokens: i64, + /// Total input tokens consumed + pub input_tokens: i64, + /// Total output tokens produced + pub output_tokens: i64, + /// Total output tokens used for reasoning + #[serde(skip_serializing_if = "Option::is_none")] + pub reasoning_tokens: Option, } -/// Refresh metadata for any detached background shells the runtime knows about. Use after a long pause to pick up exit/output state for shells running outside the agent loop. +/// Per-model usage metrics, including request counts/costs, token usage, nano-AI units, and per-token-type details. /// ///
/// @@ -14475,9 +17700,23 @@ pub struct TasksPromoteToBackgroundResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct TasksRefreshResult {} +pub struct UsageMetricsModelMetric { + /// Latest known prompt-cache expiration for this model. A timestamp in the past indicates that the observed cache has expired. + #[serde(skip_serializing_if = "Option::is_none")] + pub cache_expires_at: Option, + /// Request count and cost metrics for this model + pub requests: UsageMetricsModelMetricRequests, + /// Token count details per type + #[serde(skip_serializing_if = "Option::is_none")] + pub token_details: Option>, + /// Accumulated nano-AI units cost for this model + #[serde(skip_serializing_if = "Option::is_none")] + pub total_nano_aiu: Option, + /// Token usage metrics for this model + pub usage: UsageMetricsModelMetricUsage, +} -/// Identifier of the completed or cancelled task to remove from tracking. +/// Session-wide token-detail entry containing the accumulated token count for one token type. /// ///
/// @@ -14487,12 +17726,12 @@ pub struct TasksRefreshResult {} ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct TasksRemoveRequest { - /// Task identifier - pub id: String, +pub struct UsageMetricsTokenDetail { + /// Accumulated token count for this token type + pub token_count: i64, } -/// Indicates whether the task was removed. False when the task does not exist or is still running/idle. +/// Accumulated session usage metrics, including premium request cost, token counts, model breakdown, and code-change totals. /// ///
/// @@ -14502,12 +17741,35 @@ pub struct TasksRemoveRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct TasksRemoveResult { - /// Whether the task was removed. Returns false if the task does not exist or is still running/idle (cancel it first). - pub removed: bool, +pub struct UsageGetMetricsResult { + /// Aggregated code change metrics + pub code_changes: UsageMetricsCodeChanges, + /// Currently active model identifier + #[serde(skip_serializing_if = "Option::is_none")] + pub current_model: Option, + /// Input tokens from the most recent main-agent API call + pub last_call_input_tokens: i64, + /// Output tokens from the most recent main-agent API call + pub last_call_output_tokens: i64, + /// Per-model token and request metrics, keyed by model identifier + pub model_metrics: HashMap, + /// ISO 8601 timestamp when the session started + pub session_start_time: String, + /// Session-wide per-token-type accumulated token counts + #[serde(skip_serializing_if = "Option::is_none")] + pub token_details: Option>, + /// Total time spent in model API calls (milliseconds) + pub total_api_duration_ms: i64, + /// Session-wide accumulated nano-AI units cost + #[serde(skip_serializing_if = "Option::is_none")] + pub total_nano_aiu: Option, + /// Total user-initiated premium request cost across all models (may be fractional due to multipliers) + pub total_premium_request_cost: f64, + /// Raw count of user-initiated API requests + pub total_user_requests: i64, } -/// Identifier of the target agent task, message content, and optional sender agent ID. +/// Authentication-info variant for OAuth user auth, with host and login; the token remains in the runtime secret store. /// ///
/// @@ -14517,17 +17779,19 @@ pub struct TasksRemoveResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct TasksSendMessageRequest { - /// Agent ID of the sender, if sent on behalf of another agent +pub struct UserAuthInfo { + /// Snapshot of the authenticated user's Copilot subscription info, if known. Mirrors the GitHub API `/copilot_internal/v2/token` user response shape — the runtime trusts this verbatim and does not re-fetch when set. #[serde(skip_serializing_if = "Option::is_none")] - pub from_agent_id: Option, - /// Agent task identifier - pub id: String, - /// Message content to send to the agent - pub message: String, + pub copilot_user: Option, + /// Authentication host. + pub host: String, + /// OAuth user login. + pub login: String, + /// OAuth user authentication. The token itself is held in the runtime's secret token store (keyed by host+login) and is NOT carried in this struct. + pub r#type: UserAuthInfoType, } -/// Indicates whether the message was delivered, with an error message when delivery failed. +/// Result of a user-requested shell command. /// ///
/// @@ -14537,15 +17801,22 @@ pub struct TasksSendMessageRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct TasksSendMessageResult { - /// Error message if delivery failed +pub struct UserRequestedShellCommandResult { + /// Error output when the execution failed #[serde(skip_serializing_if = "Option::is_none")] pub error: Option, - /// Whether the message was successfully delivered or steered - pub sent: bool, + /// Process exit code, when available + #[serde(skip_serializing_if = "Option::is_none")] + pub exit_code: Option, + /// Captured command output + pub output: String, + /// Whether the command completed successfully + pub success: bool, + /// Tool call id emitted for the shell execution + pub tool_call_id: String, } -/// Agent type, prompt, name, and optional description and model override for the new task. +/// A single user setting's effective value alongside its default, so consumers can render settings left at their default. /// ///
/// @@ -14555,22 +17826,16 @@ pub struct TasksSendMessageResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct TasksStartAgentRequest { - /// Type of agent to start (e.g., 'explore', 'task', 'general-purpose') - pub agent_type: String, - /// Short description of the task - #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, - /// Optional model override - #[serde(skip_serializing_if = "Option::is_none")] - pub model: Option, - /// Short name for the agent, used to generate a human-readable ID - pub name: String, - /// Task prompt for the agent - pub prompt: String, +pub struct UserSettingMetadata { + /// The centrally-known default for this setting (null when no default is registered). + pub default: serde_json::Value, + /// True when the user has not set an explicit value for this setting (i.e. it is left at its default). Reflects whether the user has overridden the key, not whether the effective value happens to equal the default — a key explicitly set to a value identical to the default still reports false. + pub is_default: bool, + /// The effective value: the user's value if set, otherwise the default. + pub value: serde_json::Value, } -/// Identifier assigned to the newly started background agent task. +/// Per-key metadata for every known user setting (settings.json overlaid with the legacy config.json, config.json wins), including settings left at their default. Excludes repository- and enterprise-managed overrides. /// ///
/// @@ -14580,12 +17845,12 @@ pub struct TasksStartAgentRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct TasksStartAgentResult { - /// Generated agent ID for the background task - pub agent_id: String, +pub struct UserSettingsGetResult { + /// Every known user setting keyed by setting name, each with its effective value, default, and whether it is at the default. + pub settings: HashMap, } -/// Wait until all in-flight background tasks (agents + shells) and any follow-up turns scheduled by their completions have settled. Returns when the runtime is fully drained or after an internal timeout (default 10 minutes; configurable via COPILOT_TASK_WAIT_TIMEOUT_SECONDS). +/// Partial user settings to write to settings.json. Each top-level key is written individually, replacing the existing value; a key whose value is null is removed. /// ///
/// @@ -14595,9 +17860,12 @@ pub struct TasksStartAgentResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct TasksWaitForPendingResult {} +pub struct UserSettingsSetRequest { + /// Partial user settings to write, as a free-form object keyed by setting name + pub settings: serde_json::Value, +} -/// Feature override key/value pairs to attach to subsequent telemetry events from this session. +/// Outcome of writing user settings. /// ///
/// @@ -14607,12 +17875,12 @@ pub struct TasksWaitForPendingResult {} ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct TelemetrySetFeatureOverridesRequest { - /// Override key/value pairs to attach to subsequent telemetry events from this session. Replaces any previously-set overrides. - pub features: HashMap, +pub struct UserSettingsSetResult { + /// Top-level keys whose write landed in settings.json but is shadowed by a value still present in the legacy config.json (config.json wins on read). The write does not take effect until the legacy value is removed. + pub shadowed_keys: Vec, } -/// Authentication-info variant for SDK-configured token authentication, carrying host and the secret token value. +/// Current sharing status and shareable GitHub URL for a session. /// ///
/// @@ -14622,19 +17890,18 @@ pub struct TelemetrySetFeatureOverridesRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct TokenAuthInfo { - /// Snapshot of the authenticated user's Copilot subscription info, if known. Mirrors the GitHub API `/copilot_internal/v2/token` user response shape — the runtime trusts this verbatim and does not re-fetch when set. +pub struct VisibilityGetResult { + /// Shareable GitHub URL for the session. Present when the session is synced and the URL can be resolved. #[serde(skip_serializing_if = "Option::is_none")] - pub copilot_user: Option, - /// Authentication host. - pub host: String, - /// The token value itself. Treat as a secret. - pub token: String, - /// SDK-side token authentication; the host configured the token directly via the SDK. - pub r#type: TokenAuthInfoType, + pub share_url: Option, + /// Current sharing status. Absent when the session is not synced or the status could not be retrieved (e.g. the user is not authenticated). + #[serde(skip_serializing_if = "Option::is_none")] + pub status: Option, + /// Whether the session has been synced to Mission Control (i.e. has a GitHub task). When false, the session cannot be shared and `status`/`shareUrl` are absent. + pub synced: bool, } -/// Built-in tool metadata with identifier, optional namespaced name, description, input-parameter schema, and usage instructions. +/// Desired sharing status for the session. /// ///
/// @@ -14644,23 +17911,12 @@ pub struct TokenAuthInfo { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct Tool { - /// Description of what the tool does - pub description: String, - /// Optional instructions for how to use this tool effectively - #[serde(skip_serializing_if = "Option::is_none")] - pub instructions: Option, - /// Tool identifier (e.g., "bash", "grep", "str_replace_editor") - pub name: String, - /// Optional namespaced name for declarative filtering (e.g., "playwright/navigate" for MCP tools) - #[serde(skip_serializing_if = "Option::is_none")] - pub namespaced_name: Option, - /// JSON Schema for the tool's input parameters - #[serde(skip_serializing_if = "Option::is_none")] - pub parameters: Option>, +pub struct VisibilitySetRequest { + /// Sharing status to apply. "repo" makes the session visible to repository readers; "unshared" restricts it to the creator and collaborators. + pub status: SessionVisibilityStatus, } -/// Built-in tools available for the requested model, with their parameters and instructions. +/// Effective sharing status and shareable GitHub URL after updating session visibility. /// ///
/// @@ -14670,12 +17926,18 @@ pub struct Tool { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ToolList { - /// List of available built-in tools with metadata - pub tools: Vec, +pub struct VisibilitySetResult { + /// Shareable GitHub URL for the session. Present when the session is synced and the URL can be resolved. + #[serde(skip_serializing_if = "Option::is_none")] + pub share_url: Option, + /// Effective sharing status after the update. May differ from the requested status for task types that are already visible to repository readers by default. Absent when the update could not be applied (e.g. the session is not synced or the user is not authenticated). + #[serde(skip_serializing_if = "Option::is_none")] + pub status: Option, + /// Whether the session has been synced to Mission Control (i.e. has a GitHub task). When false, the visibility change could not be applied and `status`/`shareUrl` are absent. + pub synced: bool, } -/// Current lightweight tool metadata snapshot for the session. +/// A single changed file and its unified diff. /// ///
/// @@ -14685,12 +17947,22 @@ pub struct ToolList { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ToolsGetCurrentMetadataResult { - /// Current tool metadata, or null when tools have not been initialized yet - pub tools: Option>, +pub struct WorkspaceDiffFileChange { + /// Type of change represented by this file diff. + pub change_type: WorkspaceDiffFileChangeType, + /// Unified diff content for the file. Empty when the diff was truncated. + pub diff: String, + /// Whether the diff content was omitted because it exceeded the per-file size limit. + #[serde(skip_serializing_if = "Option::is_none")] + pub is_truncated: Option, + /// Original file path for renamed files. + #[serde(skip_serializing_if = "Option::is_none")] + pub old_path: Option, + /// Path to the changed file, relative to the workspace root when the file lives under it. A file changed outside the workspace root keeps a `../`-relative path, or an absolute path when no relative path exists (for example a different Windows drive). + pub path: String, } -/// Resolve, build, and validate the runtime tool list for this session. Subagent sessions and consumer flows that need an initialized tool set before `send` invoke this. Default base-class implementation is a no-op for sessions that don't support tool validation. +/// Workspace diff result for the requested mode. /// ///
/// @@ -14700,9 +17972,24 @@ pub struct ToolsGetCurrentMetadataResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ToolsInitializeAndValidateResult {} +pub struct WorkspaceDiffResult { + /// Default branch used for a branch diff, when branch mode was requested. + #[serde(skip_serializing_if = "Option::is_none")] + pub base_branch: Option, + /// Changed files and their unified diffs. + pub changes: Vec, + /// Whether the requested diff fell back to unstaged changes, either because branch diff failed or session diff was unavailable. + pub is_fallback: bool, + /// Effective mode used for the returned changes. + pub mode: WorkspaceDiffMode, + /// Diff mode requested by the client. + pub requested_mode: WorkspaceDiffMode, + /// Why the session diff could not be produced, when applicable. Set only when `session` mode was requested and `isFallback` is true, so a client can tell the permanent `file-change-tracking-disabled` apart from the transient `session-busy`, which the same request answers once the session settles. Never set for `unstaged` or `branch` mode, and never `unsupported-remote-session`: a remote session's captures live on its own host, so a `session`-mode diff is rejected for one rather than answered with a controller-side fallback. + #[serde(skip_serializing_if = "Option::is_none")] + pub unavailable_reason: Option, +} -/// Optional model identifier whose tool overrides should be applied to the listing. +/// Compaction summary checkpoint to persist. /// ///
/// @@ -14712,13 +17999,14 @@ pub struct ToolsInitializeAndValidateResult {} ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ToolsListRequest { - /// Optional model ID — when provided, the returned tool list reflects model-specific overrides - #[serde(skip_serializing_if = "Option::is_none")] - pub model: Option, +pub struct WorkspacesAddSummaryRequest { + /// Markdown summary content to persist. + pub content: String, + /// Summary title shown in checkpoint listings. + pub title: String, } -/// Empty result after applying subagent settings +/// Persisted summary metadata and refreshed workspace metadata. /// ///
/// @@ -14728,9 +18016,14 @@ pub struct ToolsListRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ToolsUpdateSubagentSettingsResult {} +pub struct WorkspacesAddSummaryResult { + #[serde(skip_serializing_if = "Option::is_none")] + pub summary: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub workspace: Option, +} -/// Selectable option for a UI elicitation multi-select array item, with submitted value and display label. +/// Whether the autopilot objective file exists. /// ///
/// @@ -14740,14 +18033,12 @@ pub struct ToolsUpdateSubagentSettingsResult {} ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UIElicitationArrayAnyOfFieldItemsAnyOf { - /// Value submitted when this option is selected. - pub r#const: String, - /// Display label for this option. - pub title: String, +pub struct WorkspacesAutopilotObjectiveExistsResult { + /// True when the objective file exists. + pub exists: bool, } -/// Schema applied to each item in the array. +/// Workspace checkpoint metadata with assigned number, human-readable title, and checkpoint filename. /// ///
/// @@ -14757,12 +18048,16 @@ pub struct UIElicitationArrayAnyOfFieldItemsAnyOf { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UIElicitationArrayAnyOfFieldItems { - /// Selectable options, each with a value and a display label. - pub any_of: Vec, +pub struct WorkspacesCheckpoints { + /// Filename of the checkpoint within the workspace checkpoints directory + pub filename: String, + /// Checkpoint number assigned by the workspace manager + pub number: i64, + /// Human-readable checkpoint title + pub title: String, } -/// Multi-select string field where each option pairs a value with a display label. +/// Relative path and UTF-8 content for the workspace file to create or overwrite. /// ///
/// @@ -14772,29 +18067,14 @@ pub struct UIElicitationArrayAnyOfFieldItems { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UIElicitationArrayAnyOfField { - /// Default values selected when the form is first shown. - #[serde(skip_serializing_if = "Option::is_none")] - pub default: Option>, - /// Help text describing the field. - #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, - /// Schema applied to each item in the array. - pub items: UIElicitationArrayAnyOfFieldItems, - /// Maximum number of items the user may select. - #[serde(skip_serializing_if = "Option::is_none")] - pub max_items: Option, - /// Minimum number of items the user must select. - #[serde(skip_serializing_if = "Option::is_none")] - pub min_items: Option, - /// Human-readable label for the field. - #[serde(skip_serializing_if = "Option::is_none")] - pub title: Option, - /// Type discriminator. Always "array". - pub r#type: UIElicitationArrayAnyOfFieldType, +pub struct WorkspacesCreateFileRequest { + /// File content to write as a UTF-8 string + pub content: String, + /// Relative path within the workspace files directory + pub path: String, } -/// Schema applied to each item in the array. +/// Result of deleting the autopilot objective file. /// ///
/// @@ -14804,14 +18084,12 @@ pub struct UIElicitationArrayAnyOfField { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UIElicitationArrayEnumFieldItems { - /// Allowed string values for each selected item. - pub r#enum: Vec, - /// Type discriminator. Always "string". - pub r#type: UIElicitationArrayEnumFieldItemsType, +pub struct WorkspacesDeleteAutopilotObjectiveResult { + /// True when a file was deleted. + pub deleted: bool, } -/// Multi-select string field whose allowed values are defined inline. +/// Parameters for computing a workspace diff. /// ///
/// @@ -14821,29 +18099,15 @@ pub struct UIElicitationArrayEnumFieldItems { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UIElicitationArrayEnumField { - /// Default values selected when the form is first shown. - #[serde(skip_serializing_if = "Option::is_none")] - pub default: Option>, - /// Help text describing the field. - #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, - /// Schema applied to each item in the array. - pub items: UIElicitationArrayEnumFieldItems, - /// Maximum number of items the user may select. - #[serde(skip_serializing_if = "Option::is_none")] - pub max_items: Option, - /// Minimum number of items the user must select. - #[serde(skip_serializing_if = "Option::is_none")] - pub min_items: Option, - /// Human-readable label for the field. +pub struct WorkspacesDiffRequest { + /// When true, ignore whitespace-only changes (git `--ignore-all-space`). Defaults to false. #[serde(skip_serializing_if = "Option::is_none")] - pub title: Option, - /// Type discriminator. Always "array". - pub r#type: UIElicitationArrayEnumFieldType, + pub ignore_whitespace: Option, + /// Diff mode requested by the client. + pub mode: WorkspaceDiffMode, } -/// JSON Schema describing the form fields to present to the user +/// Optional session context used when creating a local workspace. /// ///
/// @@ -14853,34 +18117,55 @@ pub struct UIElicitationArrayEnumField { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UIElicitationSchema { - /// Form field definitions, keyed by field name - pub properties: HashMap, - /// List of required field names +pub struct WorkspacesEnsureRequest { + /// Opaque workspace context supplied by the session host. #[serde(skip_serializing_if = "Option::is_none")] - pub required: Option>, - /// Schema type indicator (always 'object') - pub r#type: UIElicitationSchemaType, + pub context: Option, } -/// Prompt message and JSON schema describing the form fields to elicit from the user. -/// -///
-/// -/// **Experimental.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UIElicitationRequest { - /// Message describing what information is needed from the user - pub message: String, - /// JSON Schema describing the form fields to present to the user - pub requested_schema: UIElicitationSchema, +pub struct WorkspacesGetWorkspaceResultWorkspace { + #[serde(skip_serializing_if = "Option::is_none")] + pub branch: Option, + #[serde( + rename = "chronicle_sync_dismissed", + skip_serializing_if = "Option::is_none" + )] + pub chronicle_sync_dismissed: Option, + #[serde(rename = "client_name", skip_serializing_if = "Option::is_none")] + pub client_name: Option, + #[serde(rename = "created_at", skip_serializing_if = "Option::is_none")] + pub created_at: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub cwd: Option, + #[serde(rename = "git_root", skip_serializing_if = "Option::is_none")] + pub git_root: Option, + /// Allowed values for the `WorkspacesWorkspaceDetailsHostType` enumeration. + #[serde(rename = "host_type", skip_serializing_if = "Option::is_none")] + pub host_type: Option, + pub id: String, + #[serde(rename = "mc_last_event_id", skip_serializing_if = "Option::is_none")] + pub mc_last_event_id: Option, + #[serde(rename = "mc_session_id", skip_serializing_if = "Option::is_none")] + pub mc_session_id: Option, + #[serde(rename = "mc_task_id", skip_serializing_if = "Option::is_none")] + pub mc_task_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(rename = "remote_steerable", skip_serializing_if = "Option::is_none")] + pub remote_steerable: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub repository: Option, + #[serde(rename = "summary_count", skip_serializing_if = "Option::is_none")] + pub summary_count: Option, + #[serde(rename = "updated_at", skip_serializing_if = "Option::is_none")] + pub updated_at: Option, + #[serde(rename = "user_named", skip_serializing_if = "Option::is_none")] + pub user_named: Option, } -/// The elicitation response (accept with form values, decline, or cancel) +/// Current workspace metadata for the session, including its absolute filesystem path when available. /// ///
/// @@ -14890,15 +18175,15 @@ pub struct UIElicitationRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UIElicitationResponse { - /// The user's response: accept (submitted), decline (rejected), or cancel (dismissed) - pub action: UIElicitationResponseAction, - /// The form values submitted by the user (present when action is 'accept') +pub struct WorkspacesGetWorkspaceResult { + /// Absolute filesystem path to the workspace directory. Omitted when the session has no workspace (e.g. remote sessions). #[serde(skip_serializing_if = "Option::is_none")] - pub content: Option>, + pub path: Option, + /// Current workspace metadata, or null if not available + pub workspace: Option, } -/// Indicates whether the elicitation response was accepted; false if it was already resolved by another client. +/// Workspace checkpoints in chronological order; empty when the workspace is not enabled. /// ///
/// @@ -14908,12 +18193,12 @@ pub struct UIElicitationResponse { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UIElicitationResult { - /// Whether the response was accepted. False if the request was already resolved by another client. - pub success: bool, +pub struct WorkspacesListCheckpointsResult { + /// Workspace checkpoints in chronological order. Empty when workspace is not enabled. + pub checkpoints: Vec, } -/// Boolean field rendered as a yes/no toggle. +/// Relative paths of files stored in the session workspace files directory. /// ///
/// @@ -14923,21 +18208,12 @@ pub struct UIElicitationResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UIElicitationSchemaPropertyBoolean { - /// Default value selected when the form is first shown. - #[serde(skip_serializing_if = "Option::is_none")] - pub default: Option, - /// Help text describing the field. - #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, - /// Human-readable label for the field. - #[serde(skip_serializing_if = "Option::is_none")] - pub title: Option, - /// Type discriminator. Always "boolean". - pub r#type: UIElicitationSchemaPropertyBooleanType, +pub struct WorkspacesListFilesResult { + /// Relative file paths in the workspace files directory + pub files: Vec, } -/// Numeric field accepting either a number or an integer. +/// Autopilot objective file content, or null when missing. /// ///
/// @@ -14947,27 +18223,12 @@ pub struct UIElicitationSchemaPropertyBoolean { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UIElicitationSchemaPropertyNumber { - /// Default value populated in the input when the form is first shown. - #[serde(skip_serializing_if = "Option::is_none")] - pub default: Option, - /// Help text describing the field. - #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, - /// Maximum allowed value (inclusive). - #[serde(skip_serializing_if = "Option::is_none")] - pub maximum: Option, - /// Minimum allowed value (inclusive). - #[serde(skip_serializing_if = "Option::is_none")] - pub minimum: Option, - /// Human-readable label for the field. - #[serde(skip_serializing_if = "Option::is_none")] - pub title: Option, - /// Numeric type accepted by the field. - pub r#type: UIElicitationSchemaPropertyNumberType, +pub struct WorkspacesReadAutopilotObjectiveResult { + /// Autopilot objective file content, or null when missing. + pub content: Option, } -/// Free-text string field with optional length and format constraints. +/// Checkpoint number to read. /// ///
/// @@ -14977,30 +18238,12 @@ pub struct UIElicitationSchemaPropertyNumber { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UIElicitationSchemaPropertyString { - /// Default value populated in the input when the form is first shown. - #[serde(skip_serializing_if = "Option::is_none")] - pub default: Option, - /// Help text describing the field. - #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, - /// Optional format hint that constrains the accepted input. - #[serde(skip_serializing_if = "Option::is_none")] - pub format: Option, - /// Maximum number of characters allowed. - #[serde(skip_serializing_if = "Option::is_none")] - pub max_length: Option, - /// Minimum number of characters required. - #[serde(skip_serializing_if = "Option::is_none")] - pub min_length: Option, - /// Human-readable label for the field. - #[serde(skip_serializing_if = "Option::is_none")] - pub title: Option, - /// Type discriminator. Always "string". - pub r#type: UIElicitationSchemaPropertyStringType, +pub struct WorkspacesReadCheckpointRequest { + /// Checkpoint number to read + pub number: i64, } -/// Single-select string field whose allowed values are defined inline. +/// Checkpoint content as a UTF-8 string, or null when the checkpoint or workspace is missing. /// ///
/// @@ -15010,26 +18253,12 @@ pub struct UIElicitationSchemaPropertyString { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UIElicitationStringEnumField { - /// Default value selected when the form is first shown. - #[serde(skip_serializing_if = "Option::is_none")] - pub default: Option, - /// Help text describing the field. - #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, - /// Allowed string values. - pub r#enum: Vec, - /// Optional display labels for each enum value, in the same order as `enum`. - #[serde(skip_serializing_if = "Option::is_none")] - pub enum_names: Option>, - /// Human-readable label for the field. - #[serde(skip_serializing_if = "Option::is_none")] - pub title: Option, - /// Type discriminator. Always "string". - pub r#type: UIElicitationStringEnumFieldType, +pub struct WorkspacesReadCheckpointResult { + /// Checkpoint content as a UTF-8 string, or null when the checkpoint or workspace is missing + pub content: Option, } -/// Selectable option for a UI elicitation single-select string field, with submitted value and display label. +/// Relative path of the workspace file to read. /// ///
/// @@ -15039,14 +18268,12 @@ pub struct UIElicitationStringEnumField { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UIElicitationStringOneOfFieldOneOf { - /// Value submitted when this option is selected. - pub r#const: String, - /// Display label for this option. - pub title: String, +pub struct WorkspacesReadFileRequest { + /// Relative path within the workspace files directory + pub path: String, } -/// Single-select string field where each option pairs a value with a display label. +/// Contents of the requested workspace file as a UTF-8 string. /// ///
/// @@ -15056,23 +18283,12 @@ pub struct UIElicitationStringOneOfFieldOneOf { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UIElicitationStringOneOfField { - /// Default value selected when the form is first shown. - #[serde(skip_serializing_if = "Option::is_none")] - pub default: Option, - /// Help text describing the field. - #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, - /// Selectable options, each with a value and a display label. - pub one_of: Vec, - /// Human-readable label for the field. - #[serde(skip_serializing_if = "Option::is_none")] - pub title: Option, - /// Type discriminator. Always "string". - pub r#type: UIElicitationStringOneOfFieldType, +pub struct WorkspacesReadFileResult { + /// File content as a UTF-8 string + pub content: String, } -/// Transient question to answer without adding it to conversation history. +/// Pasted content to save as a UTF-8 file in the session workspace. /// ///
/// @@ -15082,20 +18298,23 @@ pub struct UIElicitationStringOneOfField { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UIEphemeralQueryRequest { - /// In-process `AbortSignal` forwarded to the model client to cancel an in-flight request. Marked internal: excluded from the public SDK surface. Replaced by an explicit cancellation token + cancel RPC in the SDK migration. - #[doc(hidden)] - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) abort_signal: Option, - /// In-process streaming callback `(text) => void` invoked with each token as the model emits it. Marked internal: excluded from the public SDK surface. In a process-separated SDK this is replaced by a streaming RPC that yields chunks and a final answer. - #[doc(hidden)] - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) on_chunk: Option, - /// Question to answer from the current conversation context. - pub question: String, +pub struct WorkspacesSaveLargePasteRequest { + /// Pasted content to save as a UTF-8 file + pub content: String, } -/// Transient answer generated from current conversation context. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct WorkspacesSaveLargePasteResultSaved { + /// Filename within the workspace files directory + pub filename: String, + /// Absolute filesystem path to the saved paste file + pub file_path: String, + /// Size of the saved file in bytes + pub size_bytes: i64, +} + +/// Descriptor for the saved paste file, or null when the workspace is unavailable. /// ///
/// @@ -15105,12 +18324,12 @@ pub struct UIEphemeralQueryRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UIEphemeralQueryResult { - /// Full assistant response text. - pub answer: String, +pub struct WorkspacesSaveLargePasteResult { + /// Saved-paste descriptor, or null when the workspace is unavailable (e.g. CCA runtime, non-infinite sessions, remote sessions) + pub saved: Option, } -/// User response for a pending exit-plan-mode request, with approval state, selected action, auto-approve flag, and feedback. +/// Rollback point for local workspace summaries. /// ///
/// @@ -15120,21 +18339,12 @@ pub struct UIEphemeralQueryResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UIExitPlanModeResponse { - /// Whether the plan was approved. - pub approved: bool, - /// Whether subsequent edits should be auto-approved without confirmation. - #[serde(skip_serializing_if = "Option::is_none")] - pub auto_approve_edits: Option, - /// Feedback from the user when they declined the plan or requested changes. - #[serde(skip_serializing_if = "Option::is_none")] - pub feedback: Option, - /// The action the user selected. Defaults to 'autopilot' when autoApproveEdits is true, otherwise 'interactive'. - #[serde(skip_serializing_if = "Option::is_none")] - pub selected_action: Option, +pub struct WorkspacesTruncateSummariesRequest { + /// Number of newest summaries to keep. + pub keep_count: i64, } -/// Request ID of a pending `auto_mode_switch.requested` event and the user's response. +/// Public-facing workspace metadata for this session, or null if the session has no associated workspace. Excludes runtime-internal fields (GitHub IDs, summary count, internal flags). /// ///
/// @@ -15144,14 +18354,39 @@ pub struct UIExitPlanModeResponse { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UIHandlePendingAutoModeSwitchRequest { - /// The unique request ID from the auto_mode_switch.requested event - pub request_id: RequestId, - /// User's choice for auto-mode switching: yes (allow this turn), yes_always (allow + persist as setting), or no (decline). - pub response: UIAutoModeSwitchResponse, +pub struct WorkspaceSummary { + /// Branch checked out at session start, if any + #[serde(skip_serializing_if = "Option::is_none")] + pub branch: Option, + /// ISO 8601 timestamp when the workspace was created + #[serde(rename = "created_at", skip_serializing_if = "Option::is_none")] + pub created_at: Option, + /// Current working directory at session start + #[serde(skip_serializing_if = "Option::is_none")] + pub cwd: Option, + /// Resolved git root for cwd, if any + #[serde(rename = "git_root", skip_serializing_if = "Option::is_none")] + pub git_root: Option, + /// Repository host type, if known + #[serde(rename = "host_type", skip_serializing_if = "Option::is_none")] + pub host_type: Option, + /// Workspace identifier (1:1 with sessionId) + pub id: String, + /// Display name for the session, if set + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + /// Repository identifier in 'owner/repo' or 'org/project/repo' format, if any + #[serde(skip_serializing_if = "Option::is_none")] + pub repository: Option, + /// ISO 8601 timestamp when the workspace was last updated + #[serde(rename = "updated_at", skip_serializing_if = "Option::is_none")] + pub updated_at: Option, + /// Whether the display name was explicitly set by the user + #[serde(rename = "user_named", skip_serializing_if = "Option::is_none")] + pub user_named: Option, } -/// Pending elicitation request ID and the user's response (accept/decline/cancel + form values). +/// Workspace metadata fields to update. /// ///
/// @@ -15161,14 +18396,16 @@ pub struct UIHandlePendingAutoModeSwitchRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UIHandlePendingElicitationRequest { - /// The unique request ID from the elicitation.requested event - pub request_id: RequestId, - /// The elicitation response (accept with form values, decline, or cancel) - pub result: UIElicitationResponse, +pub struct WorkspacesUpdateMetadataRequest { + /// Opaque workspace context supplied by the session host. + #[serde(skip_serializing_if = "Option::is_none")] + pub context: Option, + /// Optional workspace display name override. + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, } -/// Request ID of a pending `exit_plan_mode.requested` event and the user's response. +/// Autopilot objective file content to persist. /// ///
/// @@ -15178,14 +18415,12 @@ pub struct UIHandlePendingElicitationRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UIHandlePendingExitPlanModeRequest { - /// The unique request ID from the exit_plan_mode.requested event - pub request_id: RequestId, - /// User response for a pending exit-plan-mode request, with approval state, selected action, auto-approve flag, and feedback. - pub response: UIExitPlanModeResponse, +pub struct WorkspacesWriteAutopilotObjectiveRequest { + /// Autopilot objective file content. + pub content: String, } -/// Indicates whether the pending UI request was resolved by this call. +/// Result of writing the autopilot objective file. /// ///
/// @@ -15195,12 +18430,12 @@ pub struct UIHandlePendingExitPlanModeRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UIHandlePendingResult { - /// True if the request was still pending and was resolved by this call. False if the request ID was unknown, already resolved by another client (e.g. GitHub), expired, or otherwise no longer pending. - pub success: bool, +pub struct WorkspacesWriteAutopilotObjectiveResult { + /// Filesystem operation performed. + pub operation: String, } -/// Optional sampling result payload. Omit to reject/cancel the sampling request without providing a result. +/// List of Copilot models available to the resolved user, including capabilities and billing metadata. /// ///
/// @@ -15210,9 +18445,12 @@ pub struct UIHandlePendingResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UIHandlePendingSamplingResponse {} +pub struct ModelsListResult { + /// List of available models with full metadata + pub models: Vec, +} -/// Request ID of a pending `sampling.requested` event and an optional sampling result payload (omit to reject). +/// The running runtime's complete catalog of well-known built-in model IDs, including supported models and additional IDs with built-in metadata. /// ///
/// @@ -15222,15 +18460,12 @@ pub struct UIHandlePendingSamplingResponse {} ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UIHandlePendingSamplingRequest { - /// The unique request ID from the sampling.requested event - pub request_id: RequestId, - /// Optional sampling result payload. Omit to reject/cancel the sampling request without providing a result. - #[serde(skip_serializing_if = "Option::is_none")] - pub response: Option, +pub struct ModelsGetBuiltInCatalogResult { + /// Built-in model entries. + pub models: Vec, } -/// The user's selected action for an exhausted session limit. +/// Built-in tools available for the requested model, with their parameters and instructions. /// ///
/// @@ -15240,18 +18475,12 @@ pub struct UIHandlePendingSamplingRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UISessionLimitsExhaustedResponse { - /// Action selected by the user. - pub action: UISessionLimitsExhaustedResponseAction, - /// AI Credits to add to the current max when action is 'add'. - #[serde(skip_serializing_if = "Option::is_none")] - pub additional_ai_credits: Option, - /// New absolute max AI Credits when action is 'set'. - #[serde(skip_serializing_if = "Option::is_none")] - pub max_ai_credits: Option, +pub struct ToolsListResult { + /// List of available built-in tools with metadata + pub tools: Vec, } -/// Request ID of a pending `session_limits_exhausted.requested` event and the user's selected limit action. +/// User-configured MCP servers, keyed by server name. /// ///
/// @@ -15261,14 +18490,12 @@ pub struct UISessionLimitsExhaustedResponse { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UIHandlePendingSessionLimitsExhaustedRequest { - /// The unique request ID from the session_limits_exhausted.requested event - pub request_id: RequestId, - /// The selected session-limit action. - pub response: UISessionLimitsExhaustedResponse, +pub struct McpConfigListResult { + /// All MCP servers from user config, keyed by name + pub servers: HashMap, } -/// User response for a pending user-input request, with answer text and whether it was typed freeform. +/// Extensions discovered from persisted Copilot home state and their effective loading mode. Launch-scoped additional plugins are not included. /// ///
/// @@ -15278,14 +18505,14 @@ pub struct UIHandlePendingSessionLimitsExhaustedRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UIUserInputResponse { - /// The user's answer text - pub answer: String, - /// True if the user typed a freeform response, false if they selected a presented choice. Used by telemetry to differentiate between free text input and choice selection. - pub was_freeform: bool, +pub struct ExtensionsDiscoverResult { + /// Discovered user and enabled installed-plugin extensions from persisted Copilot home state + pub extensions: Vec, + /// Effective extension loading mode. Defaults to load_and_augment when unset. + pub mode: DiscoveredExtensionMode, } -/// Request ID of a pending `user_input.requested` event and the user's response. +/// Plugins installed in user/global state. /// ///
/// @@ -15295,14 +18522,12 @@ pub struct UIUserInputResponse { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UIHandlePendingUserInputRequest { - /// The unique request ID from the user_input.requested event - pub request_id: RequestId, - /// User response for a pending user-input request, with answer text and whether it was typed freeform. - pub response: UIUserInputResponse, +pub struct PluginsListResult { + /// Installed plugins + pub plugins: Vec, } -/// Register an in-process handler for `auto_mode_switch.requested` events. The caller still attaches the actual listener via the standard event-subscription mechanism; this registration solely tells the server bridge to skip its own dispatch (so a remote client doesn't race the in-process handler for the same requestId). +/// Result of installing a plugin. /// ///
/// @@ -15312,12 +18537,20 @@ pub struct UIHandlePendingUserInputRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UIRegisterDirectAutoModeSwitchHandlerResult { - /// Opaque handle representing the registration. Pass this same handle to `unregisterDirectAutoModeSwitchHandler` when the in-process handler is no longer active. Multiple registrations are reference-counted; the server bridge will only dispatch auto-mode-switch requests when no handles are active. - pub handle: String, +pub struct PluginsInstallResult { + /// Set when the install path is deprecated (e.g. direct repo / URL / local installs). Callers should surface this to end users. + #[serde(skip_serializing_if = "Option::is_none")] + pub deprecation_warning: Option, + /// The newly installed plugin's metadata + pub plugin: InstalledPluginInfo, + /// Optional post-install message provided by the plugin (e.g. setup instructions) + #[serde(skip_serializing_if = "Option::is_none")] + pub post_install_message: Option, + /// Number of skills discovered and installed from the plugin + pub skills_installed: i64, } -/// Opaque handle previously returned by `registerDirectAutoModeSwitchHandler` to release. +/// Result of updating a single plugin. /// ///
/// @@ -15327,12 +18560,18 @@ pub struct UIRegisterDirectAutoModeSwitchHandlerResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UIUnregisterDirectAutoModeSwitchHandlerRequest { - /// Handle previously returned by `registerDirectAutoModeSwitchHandler` - pub handle: String, +pub struct PluginsUpdateResult { + /// Version after the update, when reported by the plugin manifest + #[serde(skip_serializing_if = "Option::is_none")] + pub new_version: Option, + /// Version that was previously installed, when available + #[serde(skip_serializing_if = "Option::is_none")] + pub previous_version: Option, + /// Number of skills discovered and installed after the update + pub skills_installed: i64, } -/// Indicates whether the handle was active and the registration count was decremented. +/// Result of updating all installed plugins. /// ///
/// @@ -15342,30 +18581,12 @@ pub struct UIUnregisterDirectAutoModeSwitchHandlerRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UIUnregisterDirectAutoModeSwitchHandlerResult { - /// True if the handle was active and decremented the counter; false if the handle was unknown. - pub unregistered: bool, -} - -/// Configured per-agent subagent overrides -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct UpdateSubagentSettingsRequestSubagents { - /// Per-agent settings keyed by subagent agent_type - #[serde(skip_serializing_if = "Option::is_none")] - pub agents: Option>, - /// Names of subagents the user has turned off; they cannot be dispatched - #[serde(skip_serializing_if = "Option::is_none")] - pub disabled_subagents: Option>, - /// Maximum number of subagents that can run concurrently; applies to usage-based billing users only - #[serde(skip_serializing_if = "Option::is_none")] - pub max_concurrency: Option, - /// Maximum subagent nesting depth; applies to usage-based billing users only - #[serde(skip_serializing_if = "Option::is_none")] - pub max_depth: Option, +pub struct PluginsUpdateAllResult { + /// Per-plugin update results in deterministic order. + pub results: Vec, } -/// Subagent settings to apply to the current session +/// All registered marketplaces, including built-in defaults. /// ///
/// @@ -15375,12 +18596,12 @@ pub struct UpdateSubagentSettingsRequestSubagents { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UpdateSubagentSettingsRequest { - /// Subagent settings to apply, or null to clear the live session override - pub subagents: Option, +pub struct PluginsMarketplacesListResult { + /// Registered marketplaces + pub marketplaces: Vec, } -/// Aggregated code change metrics +/// Result of registering a new marketplace. /// ///
/// @@ -15390,18 +18611,12 @@ pub struct UpdateSubagentSettingsRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UsageMetricsCodeChanges { - /// Distinct file paths modified during the session - pub files_modified: Vec, - /// Number of distinct files modified - pub files_modified_count: i64, - /// Total lines of code added - pub lines_added: i64, - /// Total lines of code removed - pub lines_removed: i64, +pub struct PluginsMarketplacesAddResult { + /// Final name of the marketplace as resolved from its manifest + pub name: String, } -/// Request count and cost metrics for this model +/// Outcome of the remove attempt, including dependent-plugin info when applicable. /// ///
/// @@ -15411,14 +18626,15 @@ pub struct UsageMetricsCodeChanges { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UsageMetricsModelMetricRequests { - /// User-initiated premium request cost (with multiplier applied) - pub cost: f64, - /// Number of API requests made with this model - pub count: i64, +pub struct PluginsMarketplacesRemoveResult { + /// Names of installed plugins that prevented removal. Populated only when `removed=false`. + #[serde(skip_serializing_if = "Option::is_none")] + pub dependent_plugins: Option>, + /// True when the marketplace was actually removed. False when removal was skipped because the marketplace has dependent plugins and `force` was not set. + pub removed: bool, } -/// Per-model token-detail entry containing the accumulated token count for one token type. +/// Plugins advertised by the marketplace. /// ///
/// @@ -15428,12 +18644,12 @@ pub struct UsageMetricsModelMetricRequests { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UsageMetricsModelMetricTokenDetail { - /// Accumulated token count for this token type - pub token_count: i64, +pub struct PluginsMarketplacesBrowseResult { + /// Plugins advertised by the marketplace + pub plugins: Vec, } -/// Token usage metrics for this model +/// Result of refreshing one or more marketplace catalogs. /// ///
/// @@ -15443,21 +18659,12 @@ pub struct UsageMetricsModelMetricTokenDetail { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UsageMetricsModelMetricUsage { - /// Total tokens read from prompt cache - pub cache_read_tokens: i64, - /// Total tokens written to prompt cache - pub cache_write_tokens: i64, - /// Total input tokens consumed - pub input_tokens: i64, - /// Total output tokens produced - pub output_tokens: i64, - /// Total output tokens used for reasoning - #[serde(skip_serializing_if = "Option::is_none")] - pub reasoning_tokens: Option, +pub struct PluginsMarketplacesRefreshResult { + /// Per-marketplace refresh results in deterministic order. + pub results: Vec, } -/// Per-model usage metrics, including request counts/costs, token usage, nano-AI units, and per-token-type details. +/// Skills discovered across global and project sources. /// ///
/// @@ -15467,23 +18674,15 @@ pub struct UsageMetricsModelMetricUsage { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UsageMetricsModelMetric { - /// Latest known prompt-cache expiration for this model. A timestamp in the past indicates that the observed cache has expired. - #[serde(skip_serializing_if = "Option::is_none")] - pub cache_expires_at: Option, - /// Request count and cost metrics for this model - pub requests: UsageMetricsModelMetricRequests, - /// Token count details per type - #[serde(skip_serializing_if = "Option::is_none")] - pub token_details: Option>, - /// Accumulated nano-AI units cost for this model +pub struct SkillsDiscoverResult { + /// Messages for skills that failed to load (e.g. malformed SKILL.md). Empty when host skills are excluded so host-local paths are not disclosed to multitenant callers. #[serde(skip_serializing_if = "Option::is_none")] - pub total_nano_aiu: Option, - /// Token usage metrics for this model - pub usage: UsageMetricsModelMetricUsage, + pub errors: Option>, + /// All discovered skills across all sources + pub skills: Vec, } -/// Session-wide token-detail entry containing the accumulated token count for one token type. +/// Canonical locations where skills can be created so the runtime will recognize them. /// ///
/// @@ -15493,12 +18692,12 @@ pub struct UsageMetricsModelMetric { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UsageMetricsTokenDetail { - /// Accumulated token count for this token type - pub token_count: i64, +pub struct SkillsGetDiscoveryPathsResult { + /// Canonical skill create/discovery directories, in priority order + pub paths: Vec, } -/// Accumulated session usage metrics, including premium request cost, token counts, model breakdown, and code-change totals. +/// Agents discovered across user, project, plugin, and remote sources. /// ///
/// @@ -15508,35 +18707,12 @@ pub struct UsageMetricsTokenDetail { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UsageGetMetricsResult { - /// Aggregated code change metrics - pub code_changes: UsageMetricsCodeChanges, - /// Currently active model identifier - #[serde(skip_serializing_if = "Option::is_none")] - pub current_model: Option, - /// Input tokens from the most recent main-agent API call - pub last_call_input_tokens: i64, - /// Output tokens from the most recent main-agent API call - pub last_call_output_tokens: i64, - /// Per-model token and request metrics, keyed by model identifier - pub model_metrics: HashMap, - /// ISO 8601 timestamp when the session started - pub session_start_time: String, - /// Session-wide per-token-type accumulated token counts - #[serde(skip_serializing_if = "Option::is_none")] - pub token_details: Option>, - /// Total time spent in model API calls (milliseconds) - pub total_api_duration_ms: i64, - /// Session-wide accumulated nano-AI units cost - #[serde(skip_serializing_if = "Option::is_none")] - pub total_nano_aiu: Option, - /// Total user-initiated premium request cost across all models (may be fractional due to multipliers) - pub total_premium_request_cost: f64, - /// Raw count of user-initiated API requests - pub total_user_requests: i64, +pub struct AgentsDiscoverResult { + /// All discovered agents across all sources + pub agents: Vec, } -/// Authentication-info variant for OAuth user auth, with host and login; the token remains in the runtime secret store. +/// Canonical locations where custom agents can be created so the runtime will recognize them. /// ///
/// @@ -15546,19 +18722,12 @@ pub struct UsageGetMetricsResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UserAuthInfo { - /// Snapshot of the authenticated user's Copilot subscription info, if known. Mirrors the GitHub API `/copilot_internal/v2/token` user response shape — the runtime trusts this verbatim and does not re-fetch when set. - #[serde(skip_serializing_if = "Option::is_none")] - pub copilot_user: Option, - /// Authentication host. - pub host: String, - /// OAuth user login. - pub login: String, - /// OAuth user authentication. The token itself is held in the runtime's secret token store (keyed by host+login) and is NOT carried in this struct. - pub r#type: UserAuthInfoType, +pub struct AgentsGetDiscoveryPathsResult { + /// Canonical agent create/discovery directories, in priority order + pub paths: Vec, } -/// Result of a user-requested shell command. +/// Instruction sources discovered across user, repository, and plugin sources. /// ///
/// @@ -15568,22 +18737,12 @@ pub struct UserAuthInfo { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UserRequestedShellCommandResult { - /// Error output when the execution failed - #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, - /// Process exit code, when available - #[serde(skip_serializing_if = "Option::is_none")] - pub exit_code: Option, - /// Captured command output - pub output: String, - /// Whether the command completed successfully - pub success: bool, - /// Tool call id emitted for the shell execution - pub tool_call_id: String, +pub struct InstructionsDiscoverResult { + /// All discovered instruction sources + pub sources: Vec, } -/// A single user setting's effective value alongside its default, so consumers can render settings left at their default. +/// Canonical files and directories where custom instructions can be created so the runtime will recognize them. /// ///
/// @@ -15593,16 +18752,12 @@ pub struct UserRequestedShellCommandResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UserSettingMetadata { - /// The centrally-known default for this setting (null when no default is registered). - pub default: serde_json::Value, - /// True when the user has not set an explicit value for this setting (i.e. it is left at its default). Reflects whether the user has overridden the key, not whether the effective value happens to equal the default — a key explicitly set to a value identical to the default still reports false. - pub is_default: bool, - /// The effective value: the user's value if set, otherwise the default. - pub value: serde_json::Value, +pub struct InstructionsGetDiscoveryPathsResult { + /// Canonical instruction create/discovery files and directories, in priority order + pub paths: Vec, } -/// Per-key metadata for every known user setting (settings.json overlaid with the legacy config.json, config.json wins), including settings left at their default. Excludes repository- and enterprise-managed overrides. +/// Slash commands available in the session, after applying any include/exclude filters. /// ///
/// @@ -15612,12 +18767,12 @@ pub struct UserSettingMetadata { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UserSettingsGetResult { - /// Every known user setting keyed by setting name, each with its effective value, default, and whether it is at the default. - pub settings: HashMap, +pub struct CommandsListResult { + /// Commands available in this session + pub commands: Vec, } -/// Partial user settings to write to settings.json. Each top-level key is written individually, replacing the existing value; a key whose value is null is removed. +/// Result of opening a session. /// ///
/// @@ -15627,12 +18782,31 @@ pub struct UserSettingsGetResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UserSettingsSetRequest { - /// Partial user settings to write, as a free-form object keyed by setting name - pub settings: serde_json::Value, +pub struct SessionsOpenResult { + /// Remote session metadata, present when status is `connected`. + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option, + /// Handoff progress steps, present when status is `handed_off`. + #[serde(skip_serializing_if = "Option::is_none")] + pub progress: Option>, + /// Remote session ID, present when status is `connected`. + #[serde(skip_serializing_if = "Option::is_none")] + pub remote_session_id: Option, + /// In-process SessionClientApi handle for the opened session, returned to CLI callers as a transitional shortcut. Marked internal so the public SDK surface does not expose it; SDK consumers should construct per-session clients from `sessionId` instead. + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) session_api: Option, + /// Opened session ID. Omitted when status is `not_found`. + #[serde(skip_serializing_if = "Option::is_none")] + pub session_id: Option, + /// Startup prompts queued by user-level hook configs at session creation. Only populated when status is `created`; resumed sessions return an empty array. + #[serde(skip_serializing_if = "Option::is_none")] + pub startup_prompts: Option>, + /// Outcome of the open request. + pub status: SessionsOpenStatus, } -/// Outcome of writing user settings. +/// Remote session connection result. /// ///
/// @@ -15642,12 +18816,14 @@ pub struct UserSettingsSetRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UserSettingsSetResult { - /// Top-level keys whose write landed in settings.json but is shadowed by a value still present in the legacy config.json (config.json wins on read). The write does not take effect until the legacy value is removed. - pub shadowed_keys: Vec, +pub struct SessionsConnectResult { + /// Metadata for a connected remote session. + pub metadata: ConnectedRemoteSessionMetadata, + /// SDK session ID for the connected remote session. + pub session_id: SessionId, } -/// Current sharing status and shareable GitHub URL for a session. +/// Sessions matching the filter, ordered most-recently-modified first. /// ///
/// @@ -15657,18 +18833,12 @@ pub struct UserSettingsSetResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct VisibilityGetResult { - /// Shareable GitHub URL for the session. Present when the session is synced and the URL can be resolved. - #[serde(skip_serializing_if = "Option::is_none")] - pub share_url: Option, - /// Current sharing status. Absent when the session is not synced or the status could not be retrieved (e.g. the user is not authenticated). - #[serde(skip_serializing_if = "Option::is_none")] - pub status: Option, - /// Whether the session has been synced to Mission Control (i.e. has a GitHub task). When false, the session cannot be shared and `status`/`shareUrl` are absent. - pub synced: bool, +pub struct SessionsListResult { + /// Sessions ordered most-recently-modified first. Discriminated by `isRemote`. + pub sessions: Vec, } -/// Desired sharing status for the session. +/// ID of the local session bound to the given GitHub task, or omitted when none. /// ///
/// @@ -15678,12 +18848,13 @@ pub struct VisibilityGetResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct VisibilitySetRequest { - /// Sharing status to apply. "repo" makes the session visible to repository readers; "unshared" restricts it to the creator and collaborators. - pub status: SessionVisibilityStatus, +pub struct SessionsFindByTaskIdResult { + /// Omitted when no local session is bound to that GitHub task + #[serde(skip_serializing_if = "Option::is_none")] + pub session_id: Option, } -/// Effective sharing status and shareable GitHub URL after updating session visibility. +/// Map of sessionId -> on-disk size in bytes for each session's workspace directory. /// ///
/// @@ -15693,18 +18864,12 @@ pub struct VisibilitySetRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct VisibilitySetResult { - /// Shareable GitHub URL for the session. Present when the session is synced and the URL can be resolved. - #[serde(skip_serializing_if = "Option::is_none")] - pub share_url: Option, - /// Effective sharing status after the update. May differ from the requested status for task types that are already visible to repository readers by default. Absent when the update could not be applied (e.g. the session is not synced or the user is not authenticated). - #[serde(skip_serializing_if = "Option::is_none")] - pub status: Option, - /// Whether the session has been synced to Mission Control (i.e. has a GitHub task). When false, the visibility change could not be applied and `status`/`shareUrl` are absent. - pub synced: bool, +pub struct SessionsGetSizesResult { + /// Map of sessionId -> on-disk size in bytes for the session's workspace directory + pub sizes: HashMap, } -/// A single changed file and its unified diff. +/// Map of sessionId -> bytes freed by removing the session's workspace directory. /// ///
/// @@ -15714,22 +18879,12 @@ pub struct VisibilitySetResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct WorkspaceDiffFileChange { - /// Type of change represented by this file diff. - pub change_type: WorkspaceDiffFileChangeType, - /// Unified diff content for the file. Empty when the diff was truncated. - pub diff: String, - /// Whether the diff content was omitted because it exceeded the per-file size limit. - #[serde(skip_serializing_if = "Option::is_none")] - pub is_truncated: Option, - /// Original file path for renamed files. - #[serde(skip_serializing_if = "Option::is_none")] - pub old_path: Option, - /// Path to the changed file, relative to the workspace root. - pub path: String, +pub struct SessionsBulkDeleteResult { + /// Map of sessionId -> bytes freed by removing the session's workspace directory. Sessions whose deletion failed are omitted from this map (failures are logged on the server but not surfaced per-id; check the map for absent IDs to detect them). + pub freed_bytes: HashMap, } -/// Workspace diff result for the requested mode. +/// Outcome of the prune operation: deleted IDs, dry-run candidates, skipped IDs, total bytes freed, and the dry-run flag. /// ///
/// @@ -15739,21 +18894,20 @@ pub struct WorkspaceDiffFileChange { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct WorkspaceDiffResult { - /// Default branch used for a branch diff, when branch mode was requested. - #[serde(skip_serializing_if = "Option::is_none")] - pub base_branch: Option, - /// Changed files and their unified diffs. - pub changes: Vec, - /// Whether a requested branch diff fell back to unstaged changes because branch diff failed. - pub is_fallback: bool, - /// Effective mode used for the returned changes. - pub mode: WorkspaceDiffMode, - /// Diff mode requested by the client. - pub requested_mode: WorkspaceDiffMode, +pub struct SessionsPruneOldResult { + /// Session IDs that would be deleted in dry-run mode (always empty otherwise) + pub candidates: Vec, + /// Session IDs that were deleted (always empty in dry-run mode) + pub deleted: Vec, + /// True when no deletions were actually performed + pub dry_run: bool, + /// Total bytes freed (actual when not dry-run, projected when dry-run) + pub freed_bytes: i64, + /// Session IDs that were skipped (e.g., named sessions) + pub skipped: Vec, } -/// Workspace checkpoint metadata with assigned number, human-readable title, and checkpoint filename. +/// The enriched metadata records, with summary and context fields backfilled where available. Sessions confirmed empty and unnamed are omitted. /// ///
/// @@ -15763,16 +18917,12 @@ pub struct WorkspaceDiffResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct WorkspacesCheckpoints { - /// Filename of the checkpoint within the workspace checkpoints directory - pub filename: String, - /// Checkpoint number assigned by the workspace manager - pub number: i64, - /// Human-readable checkpoint title - pub title: String, +pub struct SessionsEnrichMetadataResult { + /// Enriched records, with summary and context backfilled. Sessions confirmed empty and unnamed may be omitted. + pub sessions: Vec, } -/// Relative path and UTF-8 content for the workspace file to create or overwrite. +/// Queued repo-level startup prompts and the total hook command count after loading. /// ///
/// @@ -15782,14 +18932,14 @@ pub struct WorkspacesCheckpoints { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct WorkspacesCreateFileRequest { - /// File content to write as a UTF-8 string - pub content: String, - /// Relative path within the workspace files directory - pub path: String, +pub struct SessionsLoadDeferredRepoHooksResult { + /// Total hook command count (user + plugin + repo) loaded for the session by this call. Captured atomically with startupPrompts so callers don't need to read a separate counter. + pub hook_count: i64, + /// Repo-level startup prompts queued from repo hook configs. Empty on resume, when no repo configs were pending, or when disableAllHooks is set. + pub startup_prompts: Vec, } -/// Parameters for computing a workspace diff. +/// Wrapper for the singleton's current status. /// ///
/// @@ -15799,57 +18949,12 @@ pub struct WorkspacesCreateFileRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct WorkspacesDiffRequest { - /// When true, ignore whitespace-only changes (git `--ignore-all-space`). Defaults to false. - #[serde(skip_serializing_if = "Option::is_none")] - pub ignore_whitespace: Option, - /// Diff mode requested by the client. - pub mode: WorkspaceDiffMode, -} - -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct WorkspacesGetWorkspaceResultWorkspace { - #[serde(skip_serializing_if = "Option::is_none")] - pub branch: Option, - #[serde( - rename = "chronicle_sync_dismissed", - skip_serializing_if = "Option::is_none" - )] - pub chronicle_sync_dismissed: Option, - #[serde(rename = "client_name", skip_serializing_if = "Option::is_none")] - pub client_name: Option, - #[serde(rename = "created_at", skip_serializing_if = "Option::is_none")] - pub created_at: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub cwd: Option, - #[serde(rename = "git_root", skip_serializing_if = "Option::is_none")] - pub git_root: Option, - /// Allowed values for the `WorkspacesWorkspaceDetailsHostType` enumeration. - #[serde(rename = "host_type", skip_serializing_if = "Option::is_none")] - pub host_type: Option, - pub id: String, - #[serde(rename = "mc_last_event_id", skip_serializing_if = "Option::is_none")] - pub mc_last_event_id: Option, - #[serde(rename = "mc_session_id", skip_serializing_if = "Option::is_none")] - pub mc_session_id: Option, - #[serde(rename = "mc_task_id", skip_serializing_if = "Option::is_none")] - pub mc_task_id: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, - #[serde(rename = "remote_steerable", skip_serializing_if = "Option::is_none")] - pub remote_steerable: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub repository: Option, - #[serde(rename = "summary_count", skip_serializing_if = "Option::is_none")] - pub summary_count: Option, - #[serde(rename = "updated_at", skip_serializing_if = "Option::is_none")] - pub updated_at: Option, - #[serde(rename = "user_named", skip_serializing_if = "Option::is_none")] - pub user_named: Option, +pub struct SessionsStartRemoteControlResult { + /// State of the runtime-managed remote-control singleton. + pub status: serde_json::Value, } -/// Current workspace metadata for the session, including its absolute filesystem path when available. +/// Outcome of a transferRemoteControl call. /// ///
/// @@ -15859,15 +18964,14 @@ pub struct WorkspacesGetWorkspaceResultWorkspace { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct WorkspacesGetWorkspaceResult { - /// Absolute filesystem path to the workspace directory. Omitted when the session has no workspace (e.g. remote sessions). - #[serde(skip_serializing_if = "Option::is_none")] - pub path: Option, - /// Current workspace metadata, or null if not available - pub workspace: Option, +pub struct SessionsTransferRemoteControlResult { + /// State of the runtime-managed remote-control singleton. + pub status: serde_json::Value, + /// Whether the rebinding actually happened. + pub transferred: bool, } -/// Workspace checkpoints in chronological order; empty when the workspace is not enabled. +/// Wrapper for the singleton's current status. /// ///
/// @@ -15877,12 +18981,12 @@ pub struct WorkspacesGetWorkspaceResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct WorkspacesListCheckpointsResult { - /// Workspace checkpoints in chronological order. Empty when workspace is not enabled. - pub checkpoints: Vec, +pub struct SessionsSetRemoteControlSteeringResult { + /// State of the runtime-managed remote-control singleton. + pub status: serde_json::Value, } -/// Relative paths of files stored in the session workspace files directory. +/// Outcome of a stopRemoteControl call. /// ///
/// @@ -15892,12 +18996,14 @@ pub struct WorkspacesListCheckpointsResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct WorkspacesListFilesResult { - /// Relative file paths in the workspace files directory - pub files: Vec, +pub struct SessionsStopRemoteControlResult { + /// State of the runtime-managed remote-control singleton. + pub status: serde_json::Value, + /// Whether the singleton was actually torn down by this call. + pub stopped: bool, } -/// Checkpoint number to read. +/// Wrapper for the singleton's current status. /// ///
/// @@ -15907,12 +19013,12 @@ pub struct WorkspacesListFilesResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct WorkspacesReadCheckpointRequest { - /// Checkpoint number to read - pub number: i64, +pub struct SessionsGetRemoteControlStatusResult { + /// State of the runtime-managed remote-control singleton. + pub status: serde_json::Value, } -/// Checkpoint content as a UTF-8 string, or null when the checkpoint or workspace is missing. +/// Handle for releasing the extension tool registration. /// ///
/// @@ -15922,12 +19028,13 @@ pub struct WorkspacesReadCheckpointRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct WorkspacesReadCheckpointResult { - /// Checkpoint content as a UTF-8 string, or null when the checkpoint or workspace is missing - pub content: Option, +pub(crate) struct SessionsRegisterExtensionToolsOnSessionResult { + /// In-process unsubscribe function (CLI-only optimization). Marked internal: replaced by an explicit `extensions.unregister` RPC in the SDK migration. + #[doc(hidden)] + pub(crate) unsubscribe: serde_json::Value, } -/// Relative path of the workspace file to read. +/// Identifies the target session. /// ///
/// @@ -15937,12 +19044,12 @@ pub struct WorkspacesReadCheckpointResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct WorkspacesReadFileRequest { - /// Relative path within the workspace files directory - pub path: String, +pub struct SessionSuspendParams { + /// Target session identifier + pub session_id: SessionId, } -/// Contents of the requested workspace file as a UTF-8 string. +/// Result of sending a user message /// ///
/// @@ -15952,12 +19059,12 @@ pub struct WorkspacesReadFileRequest { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct WorkspacesReadFileResult { - /// File content as a UTF-8 string - pub content: String, +pub struct SessionSendResult { + /// Unique identifier assigned to the message + pub message_id: String, } -/// Pasted content to save as a UTF-8 file in the session workspace. +/// Result of sending zero or more user messages /// ///
/// @@ -15967,23 +19074,30 @@ pub struct WorkspacesReadFileResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct WorkspacesSaveLargePasteRequest { - /// Pasted content to save as a UTF-8 file - pub content: String, +pub struct SessionSendMessagesResult { + /// Unique identifiers assigned to the messages, one per provided message in order. Empty when no messages were provided. + pub message_ids: Vec, } +/// Result of aborting the current turn +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct WorkspacesSaveLargePasteResultSaved { - /// Filename within the workspace files directory - pub filename: String, - /// Absolute filesystem path to the saved paste file - pub file_path: String, - /// Size of the saved file in bytes - pub size_bytes: i64, +pub struct SessionAbortResult { + /// Error message if the abort failed + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + /// Whether the abort completed successfully + pub success: bool, } -/// Descriptor for the saved paste file, or null when the workspace is unavailable. +/// Result of interrupting the main agent turn. /// ///
/// @@ -15993,12 +19107,12 @@ pub struct WorkspacesSaveLargePasteResultSaved { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct WorkspacesSaveLargePasteResult { - /// Saved-paste descriptor, or null when the workspace is unavailable (e.g. CCA runtime, non-infinite sessions, remote sessions) - pub saved: Option, +pub struct SessionInterruptMainTurnResult { + /// Whether an in-flight main agent turn was interrupted. False when the main loop was not processing. + pub interrupted: bool, } -/// Public-facing workspace metadata for this session, or null if the session has no associated workspace. Excludes runtime-internal fields (GitHub IDs, summary count, internal flags). +/// Identifies the target session. /// ///
/// @@ -16008,39 +19122,12 @@ pub struct WorkspacesSaveLargePasteResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct WorkspaceSummary { - /// Branch checked out at session start, if any - #[serde(skip_serializing_if = "Option::is_none")] - pub branch: Option, - /// ISO 8601 timestamp when the workspace was created - #[serde(rename = "created_at", skip_serializing_if = "Option::is_none")] - pub created_at: Option, - /// Current working directory at session start - #[serde(skip_serializing_if = "Option::is_none")] - pub cwd: Option, - /// Resolved git root for cwd, if any - #[serde(rename = "git_root", skip_serializing_if = "Option::is_none")] - pub git_root: Option, - /// Repository host type, if known - #[serde(rename = "host_type", skip_serializing_if = "Option::is_none")] - pub host_type: Option, - /// Workspace identifier (1:1 with sessionId) - pub id: String, - /// Display name for the session, if set - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, - /// Repository identifier in 'owner/repo' or 'org/project/repo' format, if any - #[serde(skip_serializing_if = "Option::is_none")] - pub repository: Option, - /// ISO 8601 timestamp when the workspace was last updated - #[serde(rename = "updated_at", skip_serializing_if = "Option::is_none")] - pub updated_at: Option, - /// Whether the display name was explicitly set by the user - #[serde(rename = "user_named", skip_serializing_if = "Option::is_none")] - pub user_named: Option, +pub struct SessionCancelAllBackgroundAgentsParams { + /// Target session identifier + pub session_id: SessionId, } -/// List of Copilot models available to the resolved user, including capabilities and billing metadata. +/// Identifies the target session. /// ///
/// @@ -16050,12 +19137,12 @@ pub struct WorkspaceSummary { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ModelsListResult { - /// List of available models with full metadata - pub models: Vec, +pub struct SessionGitHubAuthGetStatusParams { + /// Target session identifier + pub session_id: SessionId, } -/// Built-in tools available for the requested model, with their parameters and instructions. +/// Authentication status and account metadata for the session. /// ///
/// @@ -16065,12 +19152,27 @@ pub struct ModelsListResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ToolsListResult { - /// List of available built-in tools with metadata - pub tools: Vec, +pub struct SessionGitHubAuthGetStatusResult { + /// Authentication type + #[serde(skip_serializing_if = "Option::is_none")] + pub auth_type: Option, + /// Copilot plan tier (e.g., individual_pro, business) + #[serde(skip_serializing_if = "Option::is_none")] + pub copilot_plan: Option, + /// Authentication host URL + #[serde(skip_serializing_if = "Option::is_none")] + pub host: Option, + /// Whether the session has resolved authentication + pub is_authenticated: bool, + /// Authenticated login/username, if available + #[serde(skip_serializing_if = "Option::is_none")] + pub login: Option, + /// Human-readable authentication status description + #[serde(skip_serializing_if = "Option::is_none")] + pub status_message: Option, } -/// User-configured MCP servers, keyed by server name. +/// Indicates whether the credential update succeeded. /// ///
/// @@ -16080,12 +19182,15 @@ pub struct ToolsListResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpConfigListResult { - /// All MCP servers from user config, keyed by name - pub servers: HashMap, +pub struct SessionGitHubAuthSetCredentialsResult { + /// Whether the session ended up with a populated `copilotUser` for the installed credentials. `true` when the supplied credential already carried `copilotUser` or it was successfully re-resolved server-side. `false` when the credential is installed without `copilotUser` — either re-resolution failed, or the variant cannot be re-resolved from the credential alone (only the raw-token variants `token`, `env`, and `gh-cli` can). In both `false` cases the token swap still applied, but plan/quota/billing metadata is degraded. Present whenever a credential was supplied; omitted only when no credential was supplied (no-op call). + #[serde(skip_serializing_if = "Option::is_none")] + pub copilot_user_resolved: Option, + /// Whether the operation succeeded + pub success: bool, } -/// Plugins installed in user/global state. +/// Result of collecting a redacted debug bundle. /// ///
/// @@ -16095,12 +19200,19 @@ pub struct McpConfigListResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PluginsListResult { - /// Installed plugins - pub plugins: Vec, +pub struct SessionDebugCollectLogsResult { + /// Files included in the redacted bundle. + pub entries: Vec, + /// Destination kind that was written. + pub kind: DebugCollectLogsResultKind, + /// Actual archive path or staging directory path written. This may differ from the requested path when no-overwrite suffixing or fallback-to-temp-directory was needed. + pub path: String, + /// Optional files or directories that could not be included. + #[serde(skip_serializing_if = "Option::is_none")] + pub skipped_entries: Option>, } -/// Result of installing a plugin. +/// Identifies the target session. /// ///
/// @@ -16110,20 +19222,12 @@ pub struct PluginsListResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PluginsInstallResult { - /// Set when the install path is deprecated (e.g. direct repo / URL / local installs). Callers should surface this to end users. - #[serde(skip_serializing_if = "Option::is_none")] - pub deprecation_warning: Option, - /// The newly installed plugin's metadata - pub plugin: InstalledPluginInfo, - /// Optional post-install message provided by the plugin (e.g. setup instructions) - #[serde(skip_serializing_if = "Option::is_none")] - pub post_install_message: Option, - /// Number of skills discovered and installed from the plugin - pub skills_installed: i64, +pub struct SessionCanvasListParams { + /// Target session identifier + pub session_id: SessionId, } -/// Result of updating a single plugin. +/// Declared canvases available in this session. /// ///
/// @@ -16133,18 +19237,12 @@ pub struct PluginsInstallResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PluginsUpdateResult { - /// Version after the update, when reported by the plugin manifest - #[serde(skip_serializing_if = "Option::is_none")] - pub new_version: Option, - /// Version that was previously installed, when available - #[serde(skip_serializing_if = "Option::is_none")] - pub previous_version: Option, - /// Number of skills discovered and installed after the update - pub skills_installed: i64, +pub struct SessionCanvasListResult { + /// Declared canvases available in this session + pub canvases: Vec, } -/// Result of updating all installed plugins. +/// Identifies the target session. /// ///
/// @@ -16154,12 +19252,12 @@ pub struct PluginsUpdateResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PluginsUpdateAllResult { - /// Per-plugin update results in deterministic order. - pub results: Vec, +pub struct SessionCanvasListOpenParams { + /// Target session identifier + pub session_id: SessionId, } -/// All registered marketplaces, including built-in defaults. +/// Live open-canvas snapshot. /// ///
/// @@ -16169,12 +19267,12 @@ pub struct PluginsUpdateAllResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PluginsMarketplacesListResult { - /// Registered marketplaces - pub marketplaces: Vec, +pub struct SessionCanvasListOpenResult { + /// Currently open canvas instances + pub open_canvases: Vec, } -/// Result of registering a new marketplace. +/// Open canvas instance snapshot. /// ///
/// @@ -16184,12 +19282,34 @@ pub struct PluginsMarketplacesListResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PluginsMarketplacesAddResult { - /// Final name of the marketplace as resolved from its manifest - pub name: String, +pub struct SessionCanvasOpenResult { + /// Provider-local canvas identifier + pub canvas_id: String, + /// Owning provider identifier + pub extension_id: String, + /// Owning extension display name, when available + #[serde(skip_serializing_if = "Option::is_none")] + pub extension_name: Option, + /// Host-local PNG path for the canvas icon, when supplied + #[serde(skip_serializing_if = "Option::is_none")] + pub icon: Option, + /// Input supplied when the instance was opened + #[serde(skip_serializing_if = "Option::is_none")] + pub input: Option, + /// Stable caller-supplied canvas instance identifier + pub instance_id: String, + /// Provider-supplied status text + #[serde(skip_serializing_if = "Option::is_none")] + pub status: Option, + /// Rendered title + #[serde(skip_serializing_if = "Option::is_none")] + pub title: Option, + /// URL for web-rendered canvases + #[serde(skip_serializing_if = "Option::is_none")] + pub url: Option, } -/// Outcome of the remove attempt, including dependent-plugin info when applicable. +/// Canvas action invocation result. /// ///
/// @@ -16199,15 +19319,13 @@ pub struct PluginsMarketplacesAddResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PluginsMarketplacesRemoveResult { - /// Names of installed plugins that prevented removal. Populated only when `removed=false`. +pub struct SessionCanvasActionInvokeResult { + /// Provider-supplied action result #[serde(skip_serializing_if = "Option::is_none")] - pub dependent_plugins: Option>, - /// True when the marketplace was actually removed. False when removal was skipped because the marketplace has dependent plugins and `force` was not set. - pub removed: bool, + pub result: Option, } -/// Plugins advertised by the marketplace. +/// Complete current or terminal factory run envelope. /// ///
/// @@ -16217,12 +19335,29 @@ pub struct PluginsMarketplacesRemoveResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PluginsMarketplacesBrowseResult { - /// Plugins advertised by the marketplace - pub plugins: Vec, +pub struct SessionFactoryRunResult { + /// Error message for an errored run. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + /// Machine-readable failure details for an errored run. + #[serde(skip_serializing_if = "Option::is_none")] + pub failure: Option, + /// Reason for a halted or cancelled run. + #[serde(skip_serializing_if = "Option::is_none")] + pub reason: Option, + /// Completed factory result. + #[serde(skip_serializing_if = "Option::is_none")] + pub result: Option, + /// Factory run identifier. + pub run_id: String, + /// Partial journal and progress snapshot for a halted, cancelled, or errored run. + #[serde(skip_serializing_if = "Option::is_none")] + pub snapshot: Option, + /// Current or terminal factory run status. + pub status: FactoryRunStatus, } -/// Result of refreshing one or more marketplace catalogs. +/// Resolved persisted factory identity and resumed run envelope. /// ///
/// @@ -16232,12 +19367,14 @@ pub struct PluginsMarketplacesBrowseResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct PluginsMarketplacesRefreshResult { - /// Per-marketplace refresh results in deterministic order. - pub results: Vec, +pub struct SessionFactoryResumeResult { + /// Persisted factory name resolved for the resumed run. + pub factory_name: String, + /// Terminal resumed run envelope. + pub run: FactoryRunResult, } -/// Skills discovered across global and project sources. +/// Complete current or terminal factory run envelope. /// ///
/// @@ -16247,15 +19384,29 @@ pub struct PluginsMarketplacesRefreshResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SkillsDiscoverResult { - /// Messages for skills that failed to load (e.g. malformed SKILL.md). Empty when host skills are excluded so host-local paths are not disclosed to multitenant callers. +pub struct SessionFactoryGetRunResult { + /// Error message for an errored run. #[serde(skip_serializing_if = "Option::is_none")] - pub errors: Option>, - /// All discovered skills across all sources - pub skills: Vec, + pub error: Option, + /// Machine-readable failure details for an errored run. + #[serde(skip_serializing_if = "Option::is_none")] + pub failure: Option, + /// Reason for a halted or cancelled run. + #[serde(skip_serializing_if = "Option::is_none")] + pub reason: Option, + /// Completed factory result. + #[serde(skip_serializing_if = "Option::is_none")] + pub result: Option, + /// Factory run identifier. + pub run_id: String, + /// Partial journal and progress snapshot for a halted, cancelled, or errored run. + #[serde(skip_serializing_if = "Option::is_none")] + pub snapshot: Option, + /// Current or terminal factory run status. + pub status: FactoryRunStatus, } -/// Canonical locations where skills can be created so the runtime will recognize them. +/// A page of factory runs in durable creation order. /// ///
/// @@ -16265,12 +19416,23 @@ pub struct SkillsDiscoverResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SkillsGetDiscoveryPathsResult { - /// Canonical skill create/discovery directories, in priority order - pub paths: Vec, +pub struct SessionFactoryListRunsResult { + /// Whether terminal runs newer than this page exist. + #[serde(skip_serializing_if = "Option::is_none")] + pub has_more_newer: Option, + /// Newest terminal-run cursor in this page, or null when the terminal window is empty. + #[serde(skip_serializing_if = "Option::is_none")] + pub newest_seq: Option, + /// Oldest terminal-run cursor in this page, or null when the terminal window is empty. + #[serde(skip_serializing_if = "Option::is_none")] + pub oldest_seq: Option, + /// Number of terminal runs older than this page. + #[serde(skip_serializing_if = "Option::is_none")] + pub omitted_older: Option, + pub runs: Vec, } -/// Agents discovered across user, project, plugin, and remote sources. +/// Full factory run observability detail. /// ///
/// @@ -16280,12 +19442,32 @@ pub struct SkillsGetDiscoveryPathsResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct AgentsDiscoverResult { - /// All discovered agents across all sources - pub agents: Vec, +pub struct SessionFactoryGetRunDetailResult { + pub active_segment_started_at: Option, + pub agents: Vec, + pub approved: Option, + pub completed_at: Option, + pub consumed: FactoryRunConsumed, + pub created_at: i64, + pub current_phase: Option, + pub declared_limits: FactoryDeclaredLimits, + pub declared_phase_count: i64, + pub description: String, + pub factory_name: String, + pub live_agent_count: i64, + pub observed_at: i64, + pub phases: Vec, + pub progress: FactoryProgressPage, + pub revision: i64, + pub run_id: String, + pub started_at: Option, + pub status: FactoryRunStatus, + pub terminal: Option, + pub total_spawned_agent_count: i64, + pub updated_at: i64, } -/// Canonical locations where custom agents can be created so the runtime will recognize them. +/// A bidirectional page of factory progress. /// ///
/// @@ -16295,12 +19477,17 @@ pub struct AgentsDiscoverResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct AgentsGetDiscoveryPathsResult { - /// Canonical agent create/discovery directories, in priority order - pub paths: Vec, +pub struct SessionFactoryGetRunProgressResult { + pub has_more_newer: bool, + pub has_more_older: bool, + pub newest_seq: Option, + pub oldest_seq: Option, + pub records: Vec, + /// Run revision reflected by this page. + pub revision: i64, } -/// Instruction sources discovered across user, repository, and plugin sources. +/// Complete current or terminal factory run envelope. /// ///
/// @@ -16310,12 +19497,29 @@ pub struct AgentsGetDiscoveryPathsResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct InstructionsDiscoverResult { - /// All discovered instruction sources - pub sources: Vec, +pub struct SessionFactoryCancelResult { + /// Error message for an errored run. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + /// Machine-readable failure details for an errored run. + #[serde(skip_serializing_if = "Option::is_none")] + pub failure: Option, + /// Reason for a halted or cancelled run. + #[serde(skip_serializing_if = "Option::is_none")] + pub reason: Option, + /// Completed factory result. + #[serde(skip_serializing_if = "Option::is_none")] + pub result: Option, + /// Factory run identifier. + pub run_id: String, + /// Partial journal and progress snapshot for a halted, cancelled, or errored run. + #[serde(skip_serializing_if = "Option::is_none")] + pub snapshot: Option, + /// Current or terminal factory run status. + pub status: FactoryRunStatus, } -/// Canonical files and directories where custom instructions can be created so the runtime will recognize them. +/// Acknowledgement that a factory request was accepted. /// ///
/// @@ -16325,12 +19529,9 @@ pub struct InstructionsDiscoverResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct InstructionsGetDiscoveryPathsResult { - /// Canonical instruction create/discovery files and directories, in priority order - pub paths: Vec, -} +pub struct SessionFactoryLogResult {} -/// Slash commands available in the session, after applying any include/exclude filters. +/// Result of one factory-scoped subagent call. /// ///
/// @@ -16340,12 +19541,13 @@ pub struct InstructionsGetDiscoveryPathsResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CommandsListResult { - /// Commands available in this session - pub commands: Vec, +pub struct SessionFactoryAgentResult { + /// Agent result, omitted when the agent produced no result. + #[serde(skip_serializing_if = "Option::is_none")] + pub result: Option, } -/// Result of opening a session. +/// Result of reading a factory journal entry. /// ///
/// @@ -16355,31 +19557,15 @@ pub struct CommandsListResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsOpenResult { - /// Remote session metadata, present when status is `connected`. - #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option, - /// Handoff progress steps, present when status is `handed_off`. - #[serde(skip_serializing_if = "Option::is_none")] - pub progress: Option>, - /// Remote session ID, present when status is `connected`. - #[serde(skip_serializing_if = "Option::is_none")] - pub remote_session_id: Option, - /// In-process SessionClientApi handle for the opened session, returned to CLI callers as a transitional shortcut. Marked internal so the public SDK surface does not expose it; SDK consumers should construct per-session clients from `sessionId` instead. - #[doc(hidden)] - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) session_api: Option, - /// Opened session ID. Omitted when status is `not_found`. - #[serde(skip_serializing_if = "Option::is_none")] - pub session_id: Option, - /// Startup prompts queued by user-level hook configs at session creation. Only populated when status is `created`; resumed sessions return an empty array. +pub struct SessionFactoryJournalGetResult { + /// Whether the journal contained the requested key. + pub hit: bool, + /// Cached JSON result. The hit field distinguishes a cached JSON null from a miss. #[serde(skip_serializing_if = "Option::is_none")] - pub startup_prompts: Option>, - /// Outcome of the open request. - pub status: SessionsOpenStatus, + pub result_json: Option, } -/// Remote session connection result. +/// Acknowledgement that a factory request was accepted. /// ///
/// @@ -16389,14 +19575,24 @@ pub struct SessionsOpenResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsConnectResult { - /// Metadata for a connected remote session. - pub metadata: ConnectedRemoteSessionMetadata, - /// SDK session ID for the connected remote session. +pub struct SessionFactoryJournalPutResult {} + +/// Identifies the target session. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionModelGetCurrentParams { + /// Target session identifier pub session_id: SessionId, } -/// Sessions matching the filter, ordered most-recently-modified first. +/// The currently selected model, reasoning effort, and context tier for the session. The context tier reflects `Session.getContextTier()`, restored from the session journal on resume. /// ///
/// @@ -16406,12 +19602,19 @@ pub struct SessionsConnectResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsListResult { - /// Sessions ordered most-recently-modified first. Discriminated by `isRemote`. - pub sessions: Vec, +pub struct SessionModelGetCurrentResult { + /// Context tier for models that support multiple context-window sizes. + #[serde(skip_serializing_if = "Option::is_none")] + pub context_tier: Option, + /// Currently active model identifier + #[serde(skip_serializing_if = "Option::is_none")] + pub model_id: Option, + /// Reasoning effort level currently applied to the active model, when one is set. Reads `Session.getReasoningEffort()` synchronously after `getSelectedModel()` resolves so the two values are reported as a snapshot. + #[serde(skip_serializing_if = "Option::is_none")] + pub reasoning_effort: Option, } -/// ID of the local session bound to the given GitHub task, or omitted when none. +/// The model identifier active on the session after the switch. /// ///
/// @@ -16421,13 +19624,16 @@ pub struct SessionsListResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsFindByTaskIdResult { - /// Omitted when no local session is bound to that GitHub task +pub struct SessionModelSwitchToResult { + /// True when the switch was deferred (enqueued as a cancellable `/model` command) because a turn was active or another model change was already queued, rather than applied immediately. When true, the session's live model is unchanged until the queued change drains. #[serde(skip_serializing_if = "Option::is_none")] - pub session_id: Option, + pub deferred: Option, + /// Currently active model identifier after the switch + #[serde(skip_serializing_if = "Option::is_none")] + pub model_id: Option, } -/// Map of sessionId -> on-disk size in bytes for each session's workspace directory. +/// Update the session's reasoning effort without changing the selected model. Use `switchTo` instead when you also need to change the model. The runtime stores the effort on the session and applies it to subsequent turns. /// ///
/// @@ -16437,12 +19643,12 @@ pub struct SessionsFindByTaskIdResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsGetSizesResult { - /// Map of sessionId -> on-disk size in bytes for the session's workspace directory - pub sizes: HashMap, +pub struct SessionModelSetReasoningEffortResult { + /// Reasoning effort level recorded on the session after the update + pub reasoning_effort: String, } -/// Map of sessionId -> bytes freed by removing the session's workspace directory. +/// The list of models available to this session. /// ///
/// @@ -16452,12 +19658,18 @@ pub struct SessionsGetSizesResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsBulkDeleteResult { - /// Map of sessionId -> bytes freed by removing the session's workspace directory. Sessions whose deletion failed are omitted from this map (failures are logged on the server but not surfaced per-id; check the map for absent IDs to detect them). - pub freed_bytes: HashMap, +pub struct SessionModelListResult { + /// Available models, ordered with the most preferred default first. Includes both Copilot (CAPI) models and any registry BYOK models; a BYOK model appears under its provider-qualified selection id (`provider/id`). + pub list: Vec, + /// Cost categories for the full CAPI catalog, including picker-disabled models that Auto may select. Metadata only; entries absent from `list` are not manually selectable. + #[serde(skip_serializing_if = "Option::is_none")] + pub model_price_categories: Option>, + /// Per-quota snapshots returned alongside the model list, keyed by quota type. + #[serde(skip_serializing_if = "Option::is_none")] + pub quota_snapshots: Option>, } -/// Outcome of the prune operation: deleted IDs, dry-run candidates, skipped IDs, total bytes freed, and the dry-run flag. +/// Identifies the target session. /// ///
/// @@ -16467,20 +19679,12 @@ pub struct SessionsBulkDeleteResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsPruneOldResult { - /// Session IDs that would be deleted in dry-run mode (always empty otherwise) - pub candidates: Vec, - /// Session IDs that were deleted (always empty in dry-run mode) - pub deleted: Vec, - /// True when no deletions were actually performed - pub dry_run: bool, - /// Total bytes freed (actual when not dry-run, projected when dry-run) - pub freed_bytes: i64, - /// Session IDs that were skipped (e.g., named sessions) - pub skipped: Vec, +pub struct SessionModeGetParams { + /// Target session identifier + pub session_id: SessionId, } -/// The enriched metadata records, with summary and context fields backfilled where available. Sessions confirmed empty and unnamed are omitted. +/// Identifies the target session. /// ///
/// @@ -16490,12 +19694,12 @@ pub struct SessionsPruneOldResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsEnrichMetadataResult { - /// Enriched records, with summary and context backfilled. Sessions confirmed empty and unnamed may be omitted. - pub sessions: Vec, +pub struct SessionNameGetParams { + /// Target session identifier + pub session_id: SessionId, } -/// Queued repo-level startup prompts and the total hook command count after loading. +/// The session's friendly name, or null when not yet set. /// ///
/// @@ -16505,14 +19709,12 @@ pub struct SessionsEnrichMetadataResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsLoadDeferredRepoHooksResult { - /// Total hook command count (user + plugin + repo) loaded for the session by this call. Captured atomically with startupPrompts so callers don't need to read a separate counter. - pub hook_count: i64, - /// Repo-level startup prompts queued from repo hook configs. Empty on resume, when no repo configs were pending, or when disableAllHooks is set. - pub startup_prompts: Vec, +pub struct SessionNameGetResult { + /// The session name (user-set or auto-generated), or null if not yet set + pub name: Option, } -/// Wrapper for the singleton's current status. +/// Indicates whether the auto-generated summary was applied as the session's name. /// ///
/// @@ -16522,12 +19724,12 @@ pub struct SessionsLoadDeferredRepoHooksResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsStartRemoteControlResult { - /// State of the runtime-managed remote-control singleton. - pub status: serde_json::Value, +pub struct SessionNameSetAutoResult { + /// Whether the auto-generated summary was persisted. False if the session already has a user-set name, the summary normalized to empty, or the session does not have a workspace. + pub applied: bool, } -/// Outcome of a transferRemoteControl call. +/// Identifies the target session. /// ///
/// @@ -16537,14 +19739,12 @@ pub struct SessionsStartRemoteControlResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsTransferRemoteControlResult { - /// State of the runtime-managed remote-control singleton. - pub status: serde_json::Value, - /// Whether the rebinding actually happened. - pub transferred: bool, +pub struct SessionPlanReadParams { + /// Target session identifier + pub session_id: SessionId, } -/// Wrapper for the singleton's current status. +/// Existence, contents, and resolved path of the session plan file. /// ///
/// @@ -16554,12 +19754,16 @@ pub struct SessionsTransferRemoteControlResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsSetRemoteControlSteeringResult { - /// State of the runtime-managed remote-control singleton. - pub status: serde_json::Value, +pub struct SessionPlanReadResult { + /// The content of the plan file, or null if it does not exist + pub content: Option, + /// Whether the plan file exists in the workspace + pub exists: bool, + /// Absolute file path of the plan file, or null if workspace is not enabled + pub path: Option, } -/// Outcome of a stopRemoteControl call. +/// Identifies the target session. /// ///
/// @@ -16569,14 +19773,12 @@ pub struct SessionsSetRemoteControlSteeringResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsStopRemoteControlResult { - /// State of the runtime-managed remote-control singleton. - pub status: serde_json::Value, - /// Whether the singleton was actually torn down by this call. - pub stopped: bool, +pub struct SessionPlanDeleteParams { + /// Target session identifier + pub session_id: SessionId, } -/// Wrapper for the singleton's current status. +/// Identifies the target session. /// ///
/// @@ -16586,12 +19788,12 @@ pub struct SessionsStopRemoteControlResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionsGetRemoteControlStatusResult { - /// State of the runtime-managed remote-control singleton. - pub status: serde_json::Value, +pub struct SessionPlanReadSqlTodosParams { + /// Target session identifier + pub session_id: SessionId, } -/// Handle for releasing the extension tool registration. +/// Todo rows read from the session SQL database. Empty when no session database is available. /// ///
/// @@ -16601,10 +19803,9 @@ pub struct SessionsGetRemoteControlStatusResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub(crate) struct SessionsRegisterExtensionToolsOnSessionResult { - /// In-process unsubscribe function (CLI-only optimization). Marked internal: replaced by an explicit `extensions.unregister` RPC in the SDK migration. - #[doc(hidden)] - pub(crate) unsubscribe: serde_json::Value, +pub struct SessionPlanReadSqlTodosResult { + /// Rows from the session SQL todos table, ordered by creation time and id. + pub rows: Vec, } /// Identifies the target session. @@ -16617,12 +19818,12 @@ pub(crate) struct SessionsRegisterExtensionToolsOnSessionResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionSuspendParams { +pub struct SessionPlanReadSqlTodosWithDependenciesParams { /// Target session identifier pub session_id: SessionId, } -/// Result of sending a user message +/// Todo rows + dependency edges read from the session SQL database. /// ///
/// @@ -16632,12 +19833,14 @@ pub struct SessionSuspendParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionSendResult { - /// Unique identifier assigned to the message - pub message_id: String, +pub struct SessionPlanReadSqlTodosWithDependenciesResult { + /// Edges from the session SQL todo_deps table. Empty when no database, no todo_deps table, or the SELECT failed. Read independently from `rows`, so a broken todo_deps table does not affect the rows result and vice versa. + pub dependencies: Vec, + /// Rows from the session SQL todos table, ordered by creation time and id. Empty when no database, no todos table, or the SELECT failed. + pub rows: Vec, } -/// Result of sending zero or more user messages +/// Identifies the target session. /// ///
/// @@ -16647,12 +19850,54 @@ pub struct SessionSendResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionSendMessagesResult { - /// Unique identifiers assigned to the messages, one per provided message in order. Empty when no messages were provided. - pub message_ids: Vec, +pub struct SessionWorkspacesGetWorkspaceParams { + /// Target session identifier + pub session_id: SessionId, } -/// Result of aborting the current turn +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionWorkspacesGetWorkspaceResultWorkspace { + #[serde(skip_serializing_if = "Option::is_none")] + pub branch: Option, + #[serde( + rename = "chronicle_sync_dismissed", + skip_serializing_if = "Option::is_none" + )] + pub chronicle_sync_dismissed: Option, + #[serde(rename = "client_name", skip_serializing_if = "Option::is_none")] + pub client_name: Option, + #[serde(rename = "created_at", skip_serializing_if = "Option::is_none")] + pub created_at: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub cwd: Option, + #[serde(rename = "git_root", skip_serializing_if = "Option::is_none")] + pub git_root: Option, + /// Allowed values for the `WorkspacesWorkspaceDetailsHostType` enumeration. + #[serde(rename = "host_type", skip_serializing_if = "Option::is_none")] + pub host_type: Option, + pub id: String, + #[serde(rename = "mc_last_event_id", skip_serializing_if = "Option::is_none")] + pub mc_last_event_id: Option, + #[serde(rename = "mc_session_id", skip_serializing_if = "Option::is_none")] + pub mc_session_id: Option, + #[serde(rename = "mc_task_id", skip_serializing_if = "Option::is_none")] + pub mc_task_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(rename = "remote_steerable", skip_serializing_if = "Option::is_none")] + pub remote_steerable: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub repository: Option, + #[serde(rename = "summary_count", skip_serializing_if = "Option::is_none")] + pub summary_count: Option, + #[serde(rename = "updated_at", skip_serializing_if = "Option::is_none")] + pub updated_at: Option, + #[serde(rename = "user_named", skip_serializing_if = "Option::is_none")] + pub user_named: Option, +} + +/// Current workspace metadata for the session, including its absolute filesystem path when available. /// ///
/// @@ -16662,15 +19907,57 @@ pub struct SessionSendMessagesResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionAbortResult { - /// Error message if the abort failed +pub struct SessionWorkspacesGetWorkspaceResult { + /// Absolute filesystem path to the workspace directory. Omitted when the session has no workspace (e.g. remote sessions). + #[serde(skip_serializing_if = "Option::is_none")] + pub path: Option, + /// Current workspace metadata, or null if not available + pub workspace: Option, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionWorkspacesUpdateMetadataResultWorkspace { + #[serde(skip_serializing_if = "Option::is_none")] + pub branch: Option, + #[serde( + rename = "chronicle_sync_dismissed", + skip_serializing_if = "Option::is_none" + )] + pub chronicle_sync_dismissed: Option, + #[serde(rename = "client_name", skip_serializing_if = "Option::is_none")] + pub client_name: Option, + #[serde(rename = "created_at", skip_serializing_if = "Option::is_none")] + pub created_at: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub cwd: Option, + #[serde(rename = "git_root", skip_serializing_if = "Option::is_none")] + pub git_root: Option, + /// Allowed values for the `WorkspacesWorkspaceDetailsHostType` enumeration. + #[serde(rename = "host_type", skip_serializing_if = "Option::is_none")] + pub host_type: Option, + pub id: String, + #[serde(rename = "mc_last_event_id", skip_serializing_if = "Option::is_none")] + pub mc_last_event_id: Option, + #[serde(rename = "mc_session_id", skip_serializing_if = "Option::is_none")] + pub mc_session_id: Option, + #[serde(rename = "mc_task_id", skip_serializing_if = "Option::is_none")] + pub mc_task_id: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, - /// Whether the abort completed successfully - pub success: bool, + pub name: Option, + #[serde(rename = "remote_steerable", skip_serializing_if = "Option::is_none")] + pub remote_steerable: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub repository: Option, + #[serde(rename = "summary_count", skip_serializing_if = "Option::is_none")] + pub summary_count: Option, + #[serde(rename = "updated_at", skip_serializing_if = "Option::is_none")] + pub updated_at: Option, + #[serde(rename = "user_named", skip_serializing_if = "Option::is_none")] + pub user_named: Option, } -/// Identifies the target session. +/// Current workspace metadata for the session, including its absolute filesystem path when available. /// ///
/// @@ -16680,60 +19967,57 @@ pub struct SessionAbortResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionGitHubAuthGetStatusParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionWorkspacesUpdateMetadataResult { + /// Absolute filesystem path to the workspace directory. Omitted when the session has no workspace (e.g. remote sessions). + #[serde(skip_serializing_if = "Option::is_none")] + pub path: Option, + /// Current workspace metadata, or null if not available + pub workspace: Option, } -/// Authentication status and account metadata for the session. -/// -///
-/// -/// **Experimental.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionGitHubAuthGetStatusResult { - /// Authentication type - #[serde(skip_serializing_if = "Option::is_none")] - pub auth_type: Option, - /// Copilot plan tier (e.g., individual_pro, business) - #[serde(skip_serializing_if = "Option::is_none")] - pub copilot_plan: Option, - /// Authentication host URL +pub struct SessionWorkspacesEnsureResultWorkspace { #[serde(skip_serializing_if = "Option::is_none")] - pub host: Option, - /// Whether the session has resolved authentication - pub is_authenticated: bool, - /// Authenticated login/username, if available + pub branch: Option, + #[serde( + rename = "chronicle_sync_dismissed", + skip_serializing_if = "Option::is_none" + )] + pub chronicle_sync_dismissed: Option, + #[serde(rename = "client_name", skip_serializing_if = "Option::is_none")] + pub client_name: Option, + #[serde(rename = "created_at", skip_serializing_if = "Option::is_none")] + pub created_at: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub login: Option, - /// Human-readable authentication status description + pub cwd: Option, + #[serde(rename = "git_root", skip_serializing_if = "Option::is_none")] + pub git_root: Option, + /// Allowed values for the `WorkspacesWorkspaceDetailsHostType` enumeration. + #[serde(rename = "host_type", skip_serializing_if = "Option::is_none")] + pub host_type: Option, + pub id: String, + #[serde(rename = "mc_last_event_id", skip_serializing_if = "Option::is_none")] + pub mc_last_event_id: Option, + #[serde(rename = "mc_session_id", skip_serializing_if = "Option::is_none")] + pub mc_session_id: Option, + #[serde(rename = "mc_task_id", skip_serializing_if = "Option::is_none")] + pub mc_task_id: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub status_message: Option, -} - -/// Indicates whether the credential update succeeded. -/// -///
-/// -/// **Experimental.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
-#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionGitHubAuthSetCredentialsResult { - /// Whether the session ended up with a populated `copilotUser` for the installed credentials. `true` when the supplied credential already carried `copilotUser` or it was successfully re-resolved server-side. `false` when the credential is installed without `copilotUser` — either re-resolution failed, or the variant cannot be re-resolved from the credential alone (only the raw-token variants `token`, `env`, and `gh-cli` can). In both `false` cases the token swap still applied, but plan/quota/billing metadata is degraded. Present whenever a credential was supplied; omitted only when no credential was supplied (no-op call). + pub name: Option, + #[serde(rename = "remote_steerable", skip_serializing_if = "Option::is_none")] + pub remote_steerable: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub copilot_user_resolved: Option, - /// Whether the operation succeeded - pub success: bool, + pub repository: Option, + #[serde(rename = "summary_count", skip_serializing_if = "Option::is_none")] + pub summary_count: Option, + #[serde(rename = "updated_at", skip_serializing_if = "Option::is_none")] + pub updated_at: Option, + #[serde(rename = "user_named", skip_serializing_if = "Option::is_none")] + pub user_named: Option, } -/// Result of collecting a redacted debug bundle. +/// Current workspace metadata for the session, including its absolute filesystem path when available. /// ///
/// @@ -16743,16 +20027,12 @@ pub struct SessionGitHubAuthSetCredentialsResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionDebugCollectLogsResult { - /// Files included in the redacted bundle. - pub entries: Vec, - /// Destination kind that was written. - pub kind: DebugCollectLogsResultKind, - /// Actual archive path or staging directory path written. This may differ from the requested path when no-overwrite suffixing or fallback-to-temp-directory was needed. - pub path: String, - /// Optional files or directories that could not be included. +pub struct SessionWorkspacesEnsureResult { + /// Absolute filesystem path to the workspace directory. Omitted when the session has no workspace (e.g. remote sessions). #[serde(skip_serializing_if = "Option::is_none")] - pub skipped_entries: Option>, + pub path: Option, + /// Current workspace metadata, or null if not available + pub workspace: Option, } /// Identifies the target session. @@ -16765,12 +20045,12 @@ pub struct SessionDebugCollectLogsResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionCanvasListParams { +pub struct SessionWorkspacesListFilesParams { /// Target session identifier pub session_id: SessionId, } -/// Declared canvases available in this session. +/// Relative paths of files stored in the session workspace files directory. /// ///
/// @@ -16780,12 +20060,12 @@ pub struct SessionCanvasListParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionCanvasListResult { - /// Declared canvases available in this session - pub canvases: Vec, +pub struct SessionWorkspacesListFilesResult { + /// Relative file paths in the workspace files directory + pub files: Vec, } -/// Identifies the target session. +/// Contents of the requested workspace file as a UTF-8 string. /// ///
/// @@ -16795,12 +20075,12 @@ pub struct SessionCanvasListResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionCanvasListOpenParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionWorkspacesReadFileResult { + /// File content as a UTF-8 string + pub content: String, } -/// Live open-canvas snapshot. +/// Identifies the target session. /// ///
/// @@ -16810,12 +20090,12 @@ pub struct SessionCanvasListOpenParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionCanvasListOpenResult { - /// Currently open canvas instances - pub open_canvases: Vec, +pub struct SessionWorkspacesListCheckpointsParams { + /// Target session identifier + pub session_id: SessionId, } -/// Open canvas instance snapshot. +/// Workspace checkpoints in chronological order; empty when the workspace is not enabled. /// ///
/// @@ -16825,34 +20105,12 @@ pub struct SessionCanvasListOpenResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionCanvasOpenResult { - /// Provider-local canvas identifier - pub canvas_id: String, - /// Owning provider identifier - pub extension_id: String, - /// Owning extension display name, when available - #[serde(skip_serializing_if = "Option::is_none")] - pub extension_name: Option, - /// Host-local PNG path for the canvas icon, when supplied - #[serde(skip_serializing_if = "Option::is_none")] - pub icon: Option, - /// Input supplied when the instance was opened - #[serde(skip_serializing_if = "Option::is_none")] - pub input: Option, - /// Stable caller-supplied canvas instance identifier - pub instance_id: String, - /// Provider-supplied status text - #[serde(skip_serializing_if = "Option::is_none")] - pub status: Option, - /// Rendered title - #[serde(skip_serializing_if = "Option::is_none")] - pub title: Option, - /// URL for web-rendered canvases - #[serde(skip_serializing_if = "Option::is_none")] - pub url: Option, +pub struct SessionWorkspacesListCheckpointsResult { + /// Workspace checkpoints in chronological order. Empty when workspace is not enabled. + pub checkpoints: Vec, } -/// Canvas action invocation result. +/// Checkpoint content as a UTF-8 string, or null when the checkpoint or workspace is missing. /// ///
/// @@ -16862,13 +20120,12 @@ pub struct SessionCanvasOpenResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionCanvasActionInvokeResult { - /// Provider-supplied action result - #[serde(skip_serializing_if = "Option::is_none")] - pub result: Option, +pub struct SessionWorkspacesReadCheckpointResult { + /// Checkpoint content as a UTF-8 string, or null when the checkpoint or workspace is missing + pub content: Option, } -/// Complete current or terminal factory run envelope. +/// Persisted summary metadata and refreshed workspace metadata. /// ///
/// @@ -16878,61 +20135,56 @@ pub struct SessionCanvasActionInvokeResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFactoryRunResult { - /// Error message for an errored run. - #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, - /// Machine-readable failure details for an errored run. - #[serde(skip_serializing_if = "Option::is_none")] - pub failure: Option, - /// Reason for a halted or cancelled run. - #[serde(skip_serializing_if = "Option::is_none")] - pub reason: Option, - /// Completed factory result. +pub struct SessionWorkspacesAddSummaryResult { #[serde(skip_serializing_if = "Option::is_none")] - pub result: Option, - /// Factory run identifier. - pub run_id: String, - /// Partial journal and progress snapshot for a halted, cancelled, or errored run. + pub summary: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub snapshot: Option, - /// Current or terminal factory run status. - pub status: FactoryRunStatus, + pub workspace: Option, } -/// Complete current or terminal factory run envelope. -/// -///
-/// -/// **Experimental.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFactoryGetRunResult { - /// Error message for an errored run. - #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, - /// Machine-readable failure details for an errored run. +pub struct SessionWorkspacesTruncateSummariesResultWorkspace { #[serde(skip_serializing_if = "Option::is_none")] - pub failure: Option, - /// Reason for a halted or cancelled run. + pub branch: Option, + #[serde( + rename = "chronicle_sync_dismissed", + skip_serializing_if = "Option::is_none" + )] + pub chronicle_sync_dismissed: Option, + #[serde(rename = "client_name", skip_serializing_if = "Option::is_none")] + pub client_name: Option, + #[serde(rename = "created_at", skip_serializing_if = "Option::is_none")] + pub created_at: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub reason: Option, - /// Completed factory result. + pub cwd: Option, + #[serde(rename = "git_root", skip_serializing_if = "Option::is_none")] + pub git_root: Option, + /// Allowed values for the `WorkspacesWorkspaceDetailsHostType` enumeration. + #[serde(rename = "host_type", skip_serializing_if = "Option::is_none")] + pub host_type: Option, + pub id: String, + #[serde(rename = "mc_last_event_id", skip_serializing_if = "Option::is_none")] + pub mc_last_event_id: Option, + #[serde(rename = "mc_session_id", skip_serializing_if = "Option::is_none")] + pub mc_session_id: Option, + #[serde(rename = "mc_task_id", skip_serializing_if = "Option::is_none")] + pub mc_task_id: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub result: Option, - /// Factory run identifier. - pub run_id: String, - /// Partial journal and progress snapshot for a halted, cancelled, or errored run. + pub name: Option, + #[serde(rename = "remote_steerable", skip_serializing_if = "Option::is_none")] + pub remote_steerable: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub snapshot: Option, - /// Current or terminal factory run status. - pub status: FactoryRunStatus, + pub repository: Option, + #[serde(rename = "summary_count", skip_serializing_if = "Option::is_none")] + pub summary_count: Option, + #[serde(rename = "updated_at", skip_serializing_if = "Option::is_none")] + pub updated_at: Option, + #[serde(rename = "user_named", skip_serializing_if = "Option::is_none")] + pub user_named: Option, } -/// Complete current or terminal factory run envelope. +/// Current workspace metadata for the session, including its absolute filesystem path when available. /// ///
/// @@ -16942,29 +20194,15 @@ pub struct SessionFactoryGetRunResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFactoryCancelResult { - /// Error message for an errored run. - #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, - /// Machine-readable failure details for an errored run. - #[serde(skip_serializing_if = "Option::is_none")] - pub failure: Option, - /// Reason for a halted or cancelled run. - #[serde(skip_serializing_if = "Option::is_none")] - pub reason: Option, - /// Completed factory result. - #[serde(skip_serializing_if = "Option::is_none")] - pub result: Option, - /// Factory run identifier. - pub run_id: String, - /// Partial journal and progress snapshot for a halted, cancelled, or errored run. +pub struct SessionWorkspacesTruncateSummariesResult { + /// Absolute filesystem path to the workspace directory. Omitted when the session has no workspace (e.g. remote sessions). #[serde(skip_serializing_if = "Option::is_none")] - pub snapshot: Option, - /// Current or terminal factory run status. - pub status: FactoryRunStatus, + pub path: Option, + /// Current workspace metadata, or null if not available + pub workspace: Option, } -/// Acknowledgement that a factory request was accepted. +/// Identifies the target session. /// ///
/// @@ -16974,9 +20212,12 @@ pub struct SessionFactoryCancelResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFactoryLogResult {} +pub struct SessionWorkspacesReadAutopilotObjectiveParams { + /// Target session identifier + pub session_id: SessionId, +} -/// Result of one factory-scoped subagent call. +/// Autopilot objective file content, or null when missing. /// ///
/// @@ -16986,13 +20227,12 @@ pub struct SessionFactoryLogResult {} ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFactoryAgentResult { - /// Agent result, omitted when the agent produced no result. - #[serde(skip_serializing_if = "Option::is_none")] - pub result: Option, +pub struct SessionWorkspacesReadAutopilotObjectiveResult { + /// Autopilot objective file content, or null when missing. + pub content: Option, } -/// Result of reading a factory journal entry. +/// Result of writing the autopilot objective file. /// ///
/// @@ -17002,15 +20242,12 @@ pub struct SessionFactoryAgentResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFactoryJournalGetResult { - /// Whether the journal contained the requested key. - pub hit: bool, - /// Cached JSON result. The hit field distinguishes a cached JSON null from a miss. - #[serde(skip_serializing_if = "Option::is_none")] - pub result_json: Option, +pub struct SessionWorkspacesWriteAutopilotObjectiveResult { + /// Filesystem operation performed. + pub operation: String, } -/// Acknowledgement that a factory request was accepted. +/// Identifies the target session. /// ///
/// @@ -17020,9 +20257,12 @@ pub struct SessionFactoryJournalGetResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFactoryJournalPutResult {} +pub struct SessionWorkspacesDeleteAutopilotObjectiveParams { + /// Target session identifier + pub session_id: SessionId, +} -/// Identifies the target session. +/// Result of deleting the autopilot objective file. /// ///
/// @@ -17032,12 +20272,12 @@ pub struct SessionFactoryJournalPutResult {} ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionModelGetCurrentParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionWorkspacesDeleteAutopilotObjectiveResult { + /// True when a file was deleted. + pub deleted: bool, } -/// The currently selected model, reasoning effort, and context tier for the session. The context tier reflects `Session.getContextTier()`, restored from the session journal on resume. +/// Identifies the target session. /// ///
/// @@ -17047,19 +20287,12 @@ pub struct SessionModelGetCurrentParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionModelGetCurrentResult { - /// Context tier for models that support multiple context-window sizes. - #[serde(skip_serializing_if = "Option::is_none")] - pub context_tier: Option, - /// Currently active model identifier - #[serde(skip_serializing_if = "Option::is_none")] - pub model_id: Option, - /// Reasoning effort level currently applied to the active model, when one is set. Reads `Session.getReasoningEffort()` synchronously after `getSelectedModel()` resolves so the two values are reported as a snapshot. - #[serde(skip_serializing_if = "Option::is_none")] - pub reasoning_effort: Option, +pub struct SessionWorkspacesAutopilotObjectiveExistsParams { + /// Target session identifier + pub session_id: SessionId, } -/// The model identifier active on the session after the switch. +/// Whether the autopilot objective file exists. /// ///
/// @@ -17069,13 +20302,23 @@ pub struct SessionModelGetCurrentResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionModelSwitchToResult { - /// Currently active model identifier after the switch - #[serde(skip_serializing_if = "Option::is_none")] - pub model_id: Option, +pub struct SessionWorkspacesAutopilotObjectiveExistsResult { + /// True when the objective file exists. + pub exists: bool, } -/// Update the session's reasoning effort without changing the selected model. Use `switchTo` instead when you also need to change the model. The runtime stores the effort on the session and applies it to subsequent turns. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionWorkspacesSaveLargePasteResultSaved { + /// Filename within the workspace files directory + pub filename: String, + /// Absolute filesystem path to the saved paste file + pub file_path: String, + /// Size of the saved file in bytes + pub size_bytes: i64, +} + +/// Descriptor for the saved paste file, or null when the workspace is unavailable. /// ///
/// @@ -17085,12 +20328,12 @@ pub struct SessionModelSwitchToResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionModelSetReasoningEffortResult { - /// Reasoning effort level recorded on the session after the update - pub reasoning_effort: String, +pub struct SessionWorkspacesSaveLargePasteResult { + /// Saved-paste descriptor, or null when the workspace is unavailable (e.g. CCA runtime, non-infinite sessions, remote sessions) + pub saved: Option, } -/// The list of models available to this session. +/// Workspace diff result for the requested mode. /// ///
/// @@ -17100,15 +20343,21 @@ pub struct SessionModelSetReasoningEffortResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionModelListResult { - /// Available models, ordered with the most preferred default first. Includes both Copilot (CAPI) models and any registry BYOK models; a BYOK model appears under its provider-qualified selection id (`provider/id`). - pub list: Vec, - /// Cost categories for the full CAPI catalog, including picker-disabled models that Auto may select. Metadata only; entries absent from `list` are not manually selectable. +pub struct SessionWorkspacesDiffResult { + /// Default branch used for a branch diff, when branch mode was requested. #[serde(skip_serializing_if = "Option::is_none")] - pub model_price_categories: Option>, - /// Per-quota snapshots returned alongside the model list, keyed by quota type. + pub base_branch: Option, + /// Changed files and their unified diffs. + pub changes: Vec, + /// Whether the requested diff fell back to unstaged changes, either because branch diff failed or session diff was unavailable. + pub is_fallback: bool, + /// Effective mode used for the returned changes. + pub mode: WorkspaceDiffMode, + /// Diff mode requested by the client. + pub requested_mode: WorkspaceDiffMode, + /// Why the session diff could not be produced, when applicable. Set only when `session` mode was requested and `isFallback` is true, so a client can tell the permanent `file-change-tracking-disabled` apart from the transient `session-busy`, which the same request answers once the session settles. Never set for `unstaged` or `branch` mode, and never `unsupported-remote-session`: a remote session's captures live on its own host, so a `session`-mode diff is rejected for one rather than answered with a controller-side fallback. #[serde(skip_serializing_if = "Option::is_none")] - pub quota_snapshots: Option>, + pub unavailable_reason: Option, } /// Identifies the target session. @@ -17121,12 +20370,12 @@ pub struct SessionModelListResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionModeGetParams { +pub struct SessionCompletionsGetTriggerCharactersParams { /// Target session identifier pub session_id: SessionId, } -/// Identifies the target session. +/// Characters that, when typed in the composer, should trigger a `completions.request`. Empty when the session has no host-driven completions (e.g. local sessions, or a relay host that does not advertise `completionTriggerCharacters`). /// ///
/// @@ -17136,12 +20385,12 @@ pub struct SessionModeGetParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionNameGetParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionCompletionsGetTriggerCharactersResult { + /// Trigger characters advertised by the host (e.g. `["@", "#"]`). Empty disables host-driven completions for the session. + pub trigger_characters: Vec, } -/// The session's friendly name, or null when not yet set. +/// Host-driven completion items for the current composer input. Empty when the host returns no items or does not support completions. /// ///
/// @@ -17151,12 +20400,12 @@ pub struct SessionNameGetParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionNameGetResult { - /// The session name (user-set or auto-generated), or null if not yet set - pub name: Option, +pub struct SessionCompletionsRequestResult { + /// Completion items in host-ranked order. + pub items: Vec, } -/// Indicates whether the auto-generated summary was applied as the session's name. +/// Identifies the target session. /// ///
/// @@ -17166,12 +20415,12 @@ pub struct SessionNameGetResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionNameSetAutoResult { - /// Whether the auto-generated summary was persisted. False if the session already has a user-set name, the summary normalized to empty, or the session does not have a workspace. - pub applied: bool, +pub struct SessionInstructionsGetSourcesParams { + /// Target session identifier + pub session_id: SessionId, } -/// Identifies the target session. +/// Instruction sources loaded for the session, in merge order. /// ///
/// @@ -17181,12 +20430,12 @@ pub struct SessionNameSetAutoResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPlanReadParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionInstructionsGetSourcesResult { + /// Instruction sources for the session + pub sources: Vec, } -/// Existence, contents, and resolved path of the session plan file. +/// Indicates whether fleet mode was successfully activated. /// ///
/// @@ -17196,16 +20445,12 @@ pub struct SessionPlanReadParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPlanReadResult { - /// The content of the plan file, or null if it does not exist - pub content: Option, - /// Whether the plan file exists in the workspace - pub exists: bool, - /// Absolute file path of the plan file, or null if workspace is not enabled - pub path: Option, +pub struct SessionFleetStartResult { + /// Whether fleet mode was successfully activated + pub started: bool, } -/// Identifies the target session. +/// Agents available to the session. /// ///
/// @@ -17215,9 +20460,9 @@ pub struct SessionPlanReadResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPlanDeleteParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionAgentListResult { + /// Available agents + pub agents: Vec, } /// Identifies the target session. @@ -17230,12 +20475,12 @@ pub struct SessionPlanDeleteParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPlanReadSqlTodosParams { +pub struct SessionAgentGetCurrentParams { /// Target session identifier pub session_id: SessionId, } -/// Todo rows read from the session SQL database. Empty when no session database is available. +/// The currently selected custom agent, or null when using the default agent. /// ///
/// @@ -17245,12 +20490,12 @@ pub struct SessionPlanReadSqlTodosParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPlanReadSqlTodosResult { - /// Rows from the session SQL todos table, ordered by creation time and id. - pub rows: Vec, +pub struct SessionAgentGetCurrentResult { + /// Currently selected custom agent, or null if using the default agent + pub agent: AgentInfo, } -/// Identifies the target session. +/// The newly selected custom agent. /// ///
/// @@ -17260,12 +20505,12 @@ pub struct SessionPlanReadSqlTodosResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPlanReadSqlTodosWithDependenciesParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionAgentSelectResult { + /// The newly selected custom agent + pub agent: AgentInfo, } -/// Todo rows + dependency edges read from the session SQL database. +/// Identifies the target session. /// ///
/// @@ -17275,11 +20520,9 @@ pub struct SessionPlanReadSqlTodosWithDependenciesParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPlanReadSqlTodosWithDependenciesResult { - /// Edges from the session SQL todo_deps table. Empty when no database, no todo_deps table, or the SELECT failed. Read independently from `rows`, so a broken todo_deps table does not affect the rows result and vice versa. - pub dependencies: Vec, - /// Rows from the session SQL todos table, ordered by creation time and id. Empty when no database, no todos table, or the SELECT failed. - pub rows: Vec, +pub struct SessionAgentDeselectParams { + /// Target session identifier + pub session_id: SessionId, } /// Identifies the target session. @@ -17292,54 +20535,42 @@ pub struct SessionPlanReadSqlTodosWithDependenciesResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionWorkspacesGetWorkspaceParams { +pub struct SessionAgentReloadParams { /// Target session identifier pub session_id: SessionId, } +/// Custom agents available to the session after reloading definitions from disk. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionWorkspacesGetWorkspaceResultWorkspace { - #[serde(skip_serializing_if = "Option::is_none")] - pub branch: Option, - #[serde( - rename = "chronicle_sync_dismissed", - skip_serializing_if = "Option::is_none" - )] - pub chronicle_sync_dismissed: Option, - #[serde(rename = "client_name", skip_serializing_if = "Option::is_none")] - pub client_name: Option, - #[serde(rename = "created_at", skip_serializing_if = "Option::is_none")] - pub created_at: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub cwd: Option, - #[serde(rename = "git_root", skip_serializing_if = "Option::is_none")] - pub git_root: Option, - /// Allowed values for the `WorkspacesWorkspaceDetailsHostType` enumeration. - #[serde(rename = "host_type", skip_serializing_if = "Option::is_none")] - pub host_type: Option, - pub id: String, - #[serde(rename = "mc_last_event_id", skip_serializing_if = "Option::is_none")] - pub mc_last_event_id: Option, - #[serde(rename = "mc_session_id", skip_serializing_if = "Option::is_none")] - pub mc_session_id: Option, - #[serde(rename = "mc_task_id", skip_serializing_if = "Option::is_none")] - pub mc_task_id: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, - #[serde(rename = "remote_steerable", skip_serializing_if = "Option::is_none")] - pub remote_steerable: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub repository: Option, - #[serde(rename = "summary_count", skip_serializing_if = "Option::is_none")] - pub summary_count: Option, - #[serde(rename = "updated_at", skip_serializing_if = "Option::is_none")] - pub updated_at: Option, - #[serde(rename = "user_named", skip_serializing_if = "Option::is_none")] - pub user_named: Option, +pub struct SessionAgentReloadResult { + /// Reloaded custom agents + pub agents: Vec, +} + +/// Identifier assigned to the newly started background agent task. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionTasksStartAgentResult { + /// Generated agent ID for the background task + pub agent_id: String, } -/// Current workspace metadata for the session, including its absolute filesystem path when available. +/// Identifies the target session. /// ///
/// @@ -17349,15 +20580,12 @@ pub struct SessionWorkspacesGetWorkspaceResultWorkspace { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionWorkspacesGetWorkspaceResult { - /// Absolute filesystem path to the workspace directory. Omitted when the session has no workspace (e.g. remote sessions). - #[serde(skip_serializing_if = "Option::is_none")] - pub path: Option, - /// Current workspace metadata, or null if not available - pub workspace: Option, +pub struct SessionTasksListParams { + /// Target session identifier + pub session_id: SessionId, } -/// Identifies the target session. +/// Background tasks currently tracked by the session. /// ///
/// @@ -17367,12 +20595,12 @@ pub struct SessionWorkspacesGetWorkspaceResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionWorkspacesListFilesParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionTasksListResult { + /// Currently tracked tasks + pub tasks: Vec, } -/// Relative paths of files stored in the session workspace files directory. +/// Identifies the target session. /// ///
/// @@ -17382,12 +20610,12 @@ pub struct SessionWorkspacesListFilesParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionWorkspacesListFilesResult { - /// Relative file paths in the workspace files directory - pub files: Vec, +pub struct SessionTasksRefreshParams { + /// Target session identifier + pub session_id: SessionId, } -/// Contents of the requested workspace file as a UTF-8 string. +/// Refresh metadata for any detached background shells the runtime knows about. Use after a long pause to pick up exit/output state for shells running outside the agent loop. /// ///
/// @@ -17397,10 +20625,7 @@ pub struct SessionWorkspacesListFilesResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionWorkspacesReadFileResult { - /// File content as a UTF-8 string - pub content: String, -} +pub struct SessionTasksRefreshResult {} /// Identifies the target session. /// @@ -17412,12 +20637,12 @@ pub struct SessionWorkspacesReadFileResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionWorkspacesListCheckpointsParams { +pub struct SessionTasksWaitForPendingParams { /// Target session identifier pub session_id: SessionId, } -/// Workspace checkpoints in chronological order; empty when the workspace is not enabled. +/// Wait until all in-flight background tasks (agents + shells) and any follow-up turns scheduled by their completions have settled. Returns when the runtime is fully drained or after an internal timeout (default 10 minutes; configurable via COPILOT_TASK_WAIT_TIMEOUT_SECONDS). /// ///
/// @@ -17427,12 +20652,9 @@ pub struct SessionWorkspacesListCheckpointsParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionWorkspacesListCheckpointsResult { - /// Workspace checkpoints in chronological order. Empty when workspace is not enabled. - pub checkpoints: Vec, -} +pub struct SessionTasksWaitForPendingResult {} -/// Checkpoint content as a UTF-8 string, or null when the checkpoint or workspace is missing. +/// Progress information for the task, or null when no task with that ID is tracked. /// ///
/// @@ -17442,23 +20664,12 @@ pub struct SessionWorkspacesListCheckpointsResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionWorkspacesReadCheckpointResult { - /// Checkpoint content as a UTF-8 string, or null when the checkpoint or workspace is missing - pub content: Option, -} - -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionWorkspacesSaveLargePasteResultSaved { - /// Filename within the workspace files directory - pub filename: String, - /// Absolute filesystem path to the saved paste file - pub file_path: String, - /// Size of the saved file in bytes - pub size_bytes: i64, +pub struct SessionTasksGetProgressResult { + /// Progress information for the task, discriminated by type. Returns null when no task with this ID is currently tracked. + pub progress: Option, } -/// Descriptor for the saved paste file, or null when the workspace is unavailable. +/// Identifies the target session. /// ///
/// @@ -17468,12 +20679,12 @@ pub struct SessionWorkspacesSaveLargePasteResultSaved { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionWorkspacesSaveLargePasteResult { - /// Saved-paste descriptor, or null when the workspace is unavailable (e.g. CCA runtime, non-infinite sessions, remote sessions) - pub saved: Option, +pub struct SessionTasksGetCurrentPromotableParams { + /// Target session identifier + pub session_id: SessionId, } -/// Workspace diff result for the requested mode. +/// The first sync-waiting task that can currently be promoted to background mode. /// ///
/// @@ -17483,21 +20694,13 @@ pub struct SessionWorkspacesSaveLargePasteResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionWorkspacesDiffResult { - /// Default branch used for a branch diff, when branch mode was requested. +pub struct SessionTasksGetCurrentPromotableResult { + /// The first sync-waiting task (agent first, then shell) that can currently be promoted to background mode. Omitted if no such task exists. The returned task is guaranteed to have executionMode='sync' and canPromoteToBackground=true at the time of the call. #[serde(skip_serializing_if = "Option::is_none")] - pub base_branch: Option, - /// Changed files and their unified diffs. - pub changes: Vec, - /// Whether a requested branch diff fell back to unstaged changes because branch diff failed. - pub is_fallback: bool, - /// Effective mode used for the returned changes. - pub mode: WorkspaceDiffMode, - /// Diff mode requested by the client. - pub requested_mode: WorkspaceDiffMode, + pub task: Option, } -/// Identifies the target session. +/// Indicates whether the task was successfully promoted to background mode. /// ///
/// @@ -17507,12 +20710,12 @@ pub struct SessionWorkspacesDiffResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionCompletionsGetTriggerCharactersParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionTasksPromoteToBackgroundResult { + /// Whether the task was successfully promoted to background mode + pub promoted: bool, } -/// Characters that, when typed in the composer, should trigger a `completions.request`. Empty when the session has no host-driven completions (e.g. local sessions, or a relay host that does not advertise `completionTriggerCharacters`). +/// Identifies the target session. /// ///
/// @@ -17522,12 +20725,12 @@ pub struct SessionCompletionsGetTriggerCharactersParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionCompletionsGetTriggerCharactersResult { - /// Trigger characters advertised by the host (e.g. `["@", "#"]`). Empty disables host-driven completions for the session. - pub trigger_characters: Vec, +pub struct SessionTasksPromoteCurrentToBackgroundParams { + /// Target session identifier + pub session_id: SessionId, } -/// Host-driven completion items for the current composer input. Empty when the host returns no items or does not support completions. +/// The promoted task as it now exists in background mode, omitted if no promotable task was waiting. /// ///
/// @@ -17537,12 +20740,13 @@ pub struct SessionCompletionsGetTriggerCharactersResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionCompletionsRequestResult { - /// Completion items in host-ranked order. - pub items: Vec, +pub struct SessionTasksPromoteCurrentToBackgroundResult { + /// The promoted task as it now exists in background mode, omitted if no promotable task was waiting. Atomic operation: avoids the race window of getCurrentPromotable + promoteToBackground. + #[serde(skip_serializing_if = "Option::is_none")] + pub task: Option, } -/// Identifies the target session. +/// Indicates whether the background task was successfully cancelled. /// ///
/// @@ -17552,12 +20756,12 @@ pub struct SessionCompletionsRequestResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionInstructionsGetSourcesParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionTasksCancelResult { + /// Whether the task was successfully cancelled + pub cancelled: bool, } -/// Instruction sources loaded for the session, in merge order. +/// Indicates whether the task was removed. False when the task does not exist or is still running/idle. /// ///
/// @@ -17567,12 +20771,12 @@ pub struct SessionInstructionsGetSourcesParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionInstructionsGetSourcesResult { - /// Instruction sources for the session - pub sources: Vec, +pub struct SessionTasksRemoveResult { + /// Whether the task was removed. Returns false if the task does not exist or is still running/idle (cancel it first). + pub removed: bool, } -/// Indicates whether fleet mode was successfully activated. +/// Indicates whether the message was delivered, with an error message when delivery failed. /// ///
/// @@ -17582,9 +20786,12 @@ pub struct SessionInstructionsGetSourcesResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionFleetStartResult { - /// Whether fleet mode was successfully activated - pub started: bool, +pub struct SessionTasksSendMessageResult { + /// Error message if delivery failed + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + /// Whether the message was successfully delivered or steered + pub sent: bool, } /// Identifies the target session. @@ -17597,12 +20804,12 @@ pub struct SessionFleetStartResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionAgentListParams { +pub struct SessionSkillsListParams { /// Target session identifier pub session_id: SessionId, } -/// Custom agents available to the session. +/// Skills available to the session, with their enabled state. /// ///
/// @@ -17612,9 +20819,9 @@ pub struct SessionAgentListParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionAgentListResult { - /// Available custom agents - pub agents: Vec, +pub struct SessionSkillsListResult { + /// Available skills + pub skills: Vec, } /// Identifies the target session. @@ -17627,12 +20834,12 @@ pub struct SessionAgentListResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionAgentGetCurrentParams { +pub struct SessionSkillsGetInvokedParams { /// Target session identifier pub session_id: SessionId, } -/// The currently selected custom agent, or null when using the default agent. +/// Skills invoked during this session, ordered by invocation time (most recent last). /// ///
/// @@ -17642,12 +20849,12 @@ pub struct SessionAgentGetCurrentParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionAgentGetCurrentResult { - /// Currently selected custom agent, or null if using the default agent - pub agent: AgentInfo, +pub struct SessionSkillsGetInvokedResult { + /// Skills invoked during this session, ordered by invocation time (most recent last) + pub skills: Vec, } -/// The newly selected custom agent. +/// Identifies the target session. /// ///
/// @@ -17657,9 +20864,26 @@ pub struct SessionAgentGetCurrentResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionAgentSelectResult { - /// The newly selected custom agent - pub agent: AgentInfo, +pub struct SessionSkillsReloadParams { + /// Target session identifier + pub session_id: SessionId, +} + +/// Diagnostics from reloading skill definitions, with warnings and errors as separate lists. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionSkillsReloadResult { + /// Errors emitted while loading skills (e.g. skills that failed to load entirely) + pub errors: Vec, + /// Warnings emitted while loading skills (e.g. skills that loaded but had issues) + pub warnings: Vec, } /// Identifies the target session. @@ -17672,7 +20896,7 @@ pub struct SessionAgentSelectResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionAgentDeselectParams { +pub struct SessionSkillsEnsureLoadedParams { /// Target session identifier pub session_id: SessionId, } @@ -17687,12 +20911,12 @@ pub struct SessionAgentDeselectParams { /// #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionAgentReloadParams { +pub struct SessionMcpListParams { /// Target session identifier pub session_id: SessionId, } -/// Custom agents available to the session after reloading definitions from disk. +/// MCP servers configured for the session, with their connection status and host-level state. /// ///
/// @@ -17702,12 +20926,15 @@ pub struct SessionAgentReloadParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionAgentReloadResult { - /// Reloaded custom agents - pub agents: Vec, +pub struct SessionMcpListResult { + /// Host-level state, omitted when no MCP host is initialized. + #[serde(skip_serializing_if = "Option::is_none")] + pub host: Option, + /// Configured MCP servers + pub servers: Vec, } -/// Identifier assigned to the newly started background agent task. +/// Tools exposed by the connected MCP server. Throws when the server is not connected. /// ///
/// @@ -17717,9 +20944,9 @@ pub struct SessionAgentReloadResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionTasksStartAgentResult { - /// Generated agent ID for the background task - pub agent_id: String, +pub struct SessionMcpListToolsResult { + /// Tools exposed by the server. + pub tools: Vec, } /// Identifies the target session. @@ -17732,12 +20959,12 @@ pub struct SessionTasksStartAgentResult { /// #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionTasksListParams { +pub struct SessionMcpReloadParams { /// Target session identifier pub session_id: SessionId, } -/// Background tasks currently tracked by the session. +/// MCP server startup filtering result. /// ///
/// @@ -17747,12 +20974,15 @@ pub struct SessionTasksListParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionTasksListResult { - /// Currently tracked tasks - pub tasks: Vec, +pub struct SessionMcpReloadWithConfigResult { + /// Non-default servers allowed by policy + #[serde(skip_serializing_if = "Option::is_none")] + pub allowed_servers: Option>, + /// Servers filtered out before startup + pub filtered_servers: Vec, } -/// Identifies the target session. +/// Outcome of an MCP sampling execution: success result, failure error, or cancellation. /// ///
/// @@ -17762,12 +20992,48 @@ pub struct SessionTasksListResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionTasksRefreshParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionMcpExecuteSamplingResult { + /// Outcome of the sampling inference. 'success' produced a response; 'failure' encountered an error (including agent-side rejection by content filter or criteria); 'cancelled' the caller cancelled this execution via cancelSamplingExecution. + pub action: McpSamplingExecutionAction, + /// Error description, present when action='failure'. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + /// MCP CreateMessageResult payload (with optional 'tools' extension), present when action='success'. Treated as opaque at the schema layer; consumers should construct/consume it per the MCP CreateMessageResult shape. + #[serde(skip_serializing_if = "Option::is_none")] + pub result: Option, +} + +/// Indicates whether an in-flight sampling execution with the given requestId was found and cancelled. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionMcpCancelSamplingExecutionResult { + /// True if an in-flight execution with the given requestId was found and signalled to cancel. False when no such execution is in flight (already completed, never started, or cancelled by another caller). + pub cancelled: bool, +} + +/// Env-value mode recorded on the session after the update. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionMcpSetEnvValueModeResult { + /// Mode recorded on the session after the update + pub mode: McpSetEnvValueModeDetails, } -/// Refresh metadata for any detached background shells the runtime knows about. Use after a long pause to pick up exit/output state for shells running outside the agent loop. +/// Identifies the target session. /// ///
/// @@ -17777,9 +21043,12 @@ pub struct SessionTasksRefreshParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionTasksRefreshResult {} +pub struct SessionMcpRemoveGitHubParams { + /// Target session identifier + pub session_id: SessionId, +} -/// Identifies the target session. +/// Indicates whether the auto-managed `github` MCP server was removed (false when nothing to remove). /// ///
/// @@ -17789,12 +21058,12 @@ pub struct SessionTasksRefreshResult {} ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionTasksWaitForPendingParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionMcpRemoveGitHubResult { + /// True when the auto-managed `github` MCP server was removed; false when no removal happened (e.g. user has explicitly configured a `github` server, or the server was not registered). + pub removed: bool, } -/// Wait until all in-flight background tasks (agents + shells) and any follow-up turns scheduled by their completions have settled. Returns when the runtime is fully drained or after an internal timeout (default 10 minutes; configurable via COPILOT_TASK_WAIT_TIMEOUT_SECONDS). +/// Result of configuring GitHub MCP. /// ///
/// @@ -17804,9 +21073,12 @@ pub struct SessionTasksWaitForPendingParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionTasksWaitForPendingResult {} +pub struct SessionMcpConfigureGitHubResult { + /// Whether GitHub MCP configuration changed. + pub changed: bool, +} -/// Progress information for the task, or null when no task with that ID is tracked. +/// Whether the named MCP server is running. /// ///
/// @@ -17816,12 +21088,12 @@ pub struct SessionTasksWaitForPendingResult {} ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionTasksGetProgressResult { - /// Progress information for the task, discriminated by type. Returns null when no task with this ID is currently tracked. - pub progress: Option, +pub struct SessionMcpIsServerRunningResult { + /// True if the server has an active client and transport. + pub running: bool, } -/// Identifies the target session. +/// Indicates whether the pending MCP OAuth response was accepted. /// ///
/// @@ -17831,12 +21103,12 @@ pub struct SessionTasksGetProgressResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionTasksGetCurrentPromotableParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionMcpOauthHandlePendingRequestResult { + /// Whether the response was accepted. False if the request was unknown, timed out, or already resolved. + pub success: bool, } -/// The first sync-waiting task that can currently be promoted to background mode. +/// OAuth authorization URL the caller should open, or empty when cached tokens already authenticated the server. /// ///
/// @@ -17846,13 +21118,13 @@ pub struct SessionTasksGetCurrentPromotableParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionTasksGetCurrentPromotableResult { - /// The first sync-waiting task (agent first, then shell) that can currently be promoted to background mode. Omitted if no such task exists. The returned task is guaranteed to have executionMode='sync' and canPromoteToBackground=true at the time of the call. +pub struct SessionMcpOauthLoginResult { + /// URL the caller should open in a browser to complete OAuth. Omitted when cached tokens were still valid and no browser interaction was needed — the server is already reconnected in that case. When present, the runtime starts the callback listener before returning and continues the flow in the background; completion is signaled via session.mcp_server_status_changed. #[serde(skip_serializing_if = "Option::is_none")] - pub task: Option, + pub authorization_url: Option, } -/// Indicates whether the task was successfully promoted to background mode. +/// Indicates whether the pending MCP OAuth response was accepted. /// ///
/// @@ -17862,12 +21134,12 @@ pub struct SessionTasksGetCurrentPromotableResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionTasksPromoteToBackgroundResult { - /// Whether the task was successfully promoted to background mode - pub promoted: bool, +pub struct SessionMcpOauthRespondResult { + /// Whether the response was accepted. False if the request was unknown, timed out, or already resolved. + pub success: bool, } -/// Identifies the target session. +/// Indicates whether the pending MCP headers refresh response was accepted. /// ///
/// @@ -17877,12 +21149,12 @@ pub struct SessionTasksPromoteToBackgroundResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionTasksPromoteCurrentToBackgroundParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionMcpHeadersHandlePendingHeadersRefreshRequestResult { + /// Whether the response was accepted. False if the request was unknown, timed out, or already resolved. + pub success: bool, } -/// The promoted task as it now exists in background mode, omitted if no promotable task was waiting. +/// Resource contents returned by the MCP server. /// ///
/// @@ -17892,13 +21164,12 @@ pub struct SessionTasksPromoteCurrentToBackgroundParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionTasksPromoteCurrentToBackgroundResult { - /// The promoted task as it now exists in background mode, omitted if no promotable task was waiting. Atomic operation: avoids the race window of getCurrentPromotable + promoteToBackground. - #[serde(skip_serializing_if = "Option::is_none")] - pub task: Option, +pub struct SessionMcpAppsReadResourceResult { + /// Resource contents returned by the server + pub contents: Vec, } -/// Indicates whether the background task was successfully cancelled. +/// App-callable tools from the named MCP server. /// ///
/// @@ -17908,12 +21179,12 @@ pub struct SessionTasksPromoteCurrentToBackgroundResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionTasksCancelResult { - /// Whether the task was successfully cancelled - pub cancelled: bool, +pub struct SessionMcpAppsListToolsResult { + /// App-callable tools from the server + pub tools: Vec>, } -/// Indicates whether the task was removed. False when the task does not exist or is still running/idle. +/// Identifies the target session. /// ///
/// @@ -17923,12 +21194,12 @@ pub struct SessionTasksCancelResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionTasksRemoveResult { - /// Whether the task was removed. Returns false if the task does not exist or is still running/idle (cancel it first). - pub removed: bool, +pub struct SessionMcpAppsGetHostContextParams { + /// Target session identifier + pub session_id: SessionId, } -/// Indicates whether the message was delivered, with an error message when delivery failed. +/// Current host context advertised to MCP App guests. /// ///
/// @@ -17938,15 +21209,12 @@ pub struct SessionTasksRemoveResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionTasksSendMessageResult { - /// Error message if delivery failed - #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, - /// Whether the message was successfully delivered or steered - pub sent: bool, +pub struct SessionMcpAppsGetHostContextResult { + /// Current host context + pub context: McpAppsHostContextDetails, } -/// Identifies the target session. +/// Diagnostic snapshot of MCP Apps wiring for the named server. /// ///
/// @@ -17956,12 +21224,14 @@ pub struct SessionTasksSendMessageResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionSkillsListParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionMcpAppsDiagnoseResult { + /// Capability negotiation snapshot + pub capability: McpAppsDiagnoseCapability, + /// What the server returned for this session + pub server: McpAppsDiagnoseServer, } -/// Skills available to the session, with their enabled state. +/// Resource contents returned by the MCP server. /// ///
/// @@ -17971,12 +21241,12 @@ pub struct SessionSkillsListParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionSkillsListResult { - /// Available skills - pub skills: Vec, +pub struct SessionMcpResourcesReadResult { + /// Resource contents returned by the server + pub contents: Vec, } -/// Identifies the target session. +/// One page of resources advertised by the named MCP server. /// ///
/// @@ -17986,12 +21256,15 @@ pub struct SessionSkillsListResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionSkillsGetInvokedParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionMcpResourcesListResult { + /// Opaque cursor for the next page, if the server has more resources + #[serde(skip_serializing_if = "Option::is_none")] + pub next_cursor: Option, + /// Resources advertised by the server (proxied MCP `resources/list`) + pub resources: Vec, } -/// Skills invoked during this session, ordered by invocation time (most recent last). +/// One page of resource templates advertised by the named MCP server. /// ///
/// @@ -18001,9 +21274,12 @@ pub struct SessionSkillsGetInvokedParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionSkillsGetInvokedResult { - /// Skills invoked during this session, ordered by invocation time (most recent last) - pub skills: Vec, +pub struct SessionMcpResourcesListTemplatesResult { + /// Opaque cursor for the next page, if the server has more resource templates + #[serde(skip_serializing_if = "Option::is_none")] + pub next_cursor: Option, + /// Resource templates advertised by the server (proxied MCP `resources/templates/list`) + pub resource_templates: Vec, } /// Identifies the target session. @@ -18016,12 +21292,12 @@ pub struct SessionSkillsGetInvokedResult { /// #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionSkillsReloadParams { +pub struct SessionPluginsListParams { /// Target session identifier pub session_id: SessionId, } -/// Diagnostics from reloading skill definitions, with warnings and errors as separate lists. +/// Plugins installed for the session, with their enabled state and version metadata. /// ///
/// @@ -18031,14 +21307,12 @@ pub struct SessionSkillsReloadParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionSkillsReloadResult { - /// Errors emitted while loading skills (e.g. skills that failed to load entirely) - pub errors: Vec, - /// Warnings emitted while loading skills (e.g. skills that loaded but had issues) - pub warnings: Vec, +pub struct SessionPluginsListResult { + /// Installed plugins + pub plugins: Vec, } -/// Identifies the target session. +/// A snapshot of the provider endpoint the session is currently configured to talk to. /// ///
/// @@ -18048,12 +21322,28 @@ pub struct SessionSkillsReloadResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionSkillsEnsureLoadedParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionProviderGetEndpointResult { + /// A credential the caller should use with this endpoint. Omitted only when the endpoint accepts unauthenticated requests. + #[serde(skip_serializing_if = "Option::is_none")] + pub api_key: Option, + /// Base URL to pass to the LLM client library. + pub base_url: String, + /// HTTP headers the caller must include on every outbound request. + pub headers: HashMap, + /// Short-lived, rotating credential the caller must send on every request, in addition to `apiKey` if one is present. Omitted when the endpoint does not require one. + #[serde(skip_serializing_if = "Option::is_none")] + pub session_token: Option, + /// Transport to be used for provider requests. + #[serde(skip_serializing_if = "Option::is_none")] + pub transport: Option, + /// Provider family. Matches the `type` field of a BYOK provider config. + pub r#type: ProviderEndpointType, + /// Wire API to be used, when required for the provider type. + #[serde(skip_serializing_if = "Option::is_none")] + pub wire_api: Option, } -/// Identifies the target session. +/// The selectable model entries synthesized for the models added by this call. /// ///
/// @@ -18063,12 +21353,12 @@ pub struct SessionSkillsEnsureLoadedParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMcpListParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionProviderAddResult { + /// Synthesized selectable model entries for the newly added BYOK models, each under its provider-qualified selection id (`provider/id`). Empty when only providers were added. + pub models: Vec, } -/// MCP servers configured for the session, with their connection status and host-level state. +/// Indicates whether the session options patch was applied successfully. /// ///
/// @@ -18078,15 +21368,15 @@ pub struct SessionMcpListParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMcpListResult { - /// Host-level state, omitted when no MCP host is initialized. +pub struct SessionOptionsUpdateResult { + /// Number of hooks loaded from installed plugins, returned when installedPlugins is updated #[serde(skip_serializing_if = "Option::is_none")] - pub host: Option, - /// Configured MCP servers - pub servers: Vec, + pub plugin_hook_count: Option, + /// Whether the operation succeeded + pub success: bool, } -/// Tools exposed by the connected MCP server. Throws when the server is not connected. +/// Identifies the target session. /// ///
/// @@ -18096,12 +21386,12 @@ pub struct SessionMcpListResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMcpListToolsResult { - /// Tools exposed by the server. - pub tools: Vec, +pub struct SessionExtensionsListParams { + /// Target session identifier + pub session_id: SessionId, } -/// Identifies the target session. +/// Extensions discovered for the session, with their current status. /// ///
/// @@ -18111,12 +21401,12 @@ pub struct SessionMcpListToolsResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMcpReloadParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionExtensionsListResult { + /// Discovered extensions and their current status + pub extensions: Vec, } -/// MCP server startup filtering result. +/// Identifies the target session. /// ///
/// @@ -18126,15 +21416,12 @@ pub struct SessionMcpReloadParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMcpReloadWithConfigResult { - /// Non-default servers allowed by policy - #[serde(skip_serializing_if = "Option::is_none")] - pub allowed_servers: Option>, - /// Servers filtered out before startup - pub filtered_servers: Vec, +pub struct SessionExtensionsReloadParams { + /// Target session identifier + pub session_id: SessionId, } -/// Outcome of an MCP sampling execution: success result, failure error, or cancellation. +/// Indicates whether the external tool call result was handled successfully. /// ///
/// @@ -18144,18 +21431,12 @@ pub struct SessionMcpReloadWithConfigResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMcpExecuteSamplingResult { - /// Outcome of the sampling inference. 'success' produced a response; 'failure' encountered an error (including agent-side rejection by content filter or criteria); 'cancelled' the caller cancelled this execution via cancelSamplingExecution. - pub action: McpSamplingExecutionAction, - /// Error description, present when action='failure'. - #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, - /// MCP CreateMessageResult payload (with optional 'tools' extension), present when action='success'. Treated as opaque at the schema layer; consumers should construct/consume it per the MCP CreateMessageResult shape. - #[serde(skip_serializing_if = "Option::is_none")] - pub result: Option, +pub struct SessionToolsHandlePendingToolCallResult { + /// Whether the tool call result was handled successfully + pub success: bool, } -/// Indicates whether an in-flight sampling execution with the given requestId was found and cancelled. +/// Identifies the target session. /// ///
/// @@ -18165,12 +21446,12 @@ pub struct SessionMcpExecuteSamplingResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMcpCancelSamplingExecutionResult { - /// True if an in-flight execution with the given requestId was found and signalled to cancel. False when no such execution is in flight (already completed, never started, or cancelled by another caller). - pub cancelled: bool, +pub struct SessionToolsInitializeAndValidateParams { + /// Target session identifier + pub session_id: SessionId, } -/// Env-value mode recorded on the session after the update. +/// Resolve, build, and validate the runtime tool list for this session. Subagent sessions and consumer flows that need an initialized tool set before `send` invoke this. Default base-class implementation is a no-op for sessions that don't support tool validation. /// ///
/// @@ -18180,10 +21461,7 @@ pub struct SessionMcpCancelSamplingExecutionResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMcpSetEnvValueModeResult { - /// Mode recorded on the session after the update - pub mode: McpSetEnvValueModeDetails, -} +pub struct SessionToolsInitializeAndValidateResult {} /// Identifies the target session. /// @@ -18195,12 +21473,12 @@ pub struct SessionMcpSetEnvValueModeResult { /// #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMcpRemoveGitHubParams { +pub struct SessionToolsGetCurrentMetadataParams { /// Target session identifier pub session_id: SessionId, } -/// Indicates whether the auto-managed `github` MCP server was removed (false when nothing to remove). +/// Current lightweight tool metadata snapshot for the session. /// ///
/// @@ -18210,12 +21488,12 @@ pub struct SessionMcpRemoveGitHubParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMcpRemoveGitHubResult { - /// True when the auto-managed `github` MCP server was removed; false when no removal happened (e.g. user has explicitly configured a `github` server, or the server was not registered). - pub removed: bool, +pub struct SessionToolsGetCurrentMetadataResult { + /// Current tool metadata, or null when tools have not been initialized yet + pub tools: Option>, } -/// Result of configuring GitHub MCP. +/// Empty result after applying subagent settings /// ///
/// @@ -18225,12 +21503,9 @@ pub struct SessionMcpRemoveGitHubResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMcpConfigureGitHubResult { - /// Whether GitHub MCP configuration changed. - pub changed: bool, -} +pub struct SessionToolsUpdateSubagentSettingsResult {} -/// Whether the named MCP server is running. +/// Slash commands available in the session, after applying any include/exclude filters. /// ///
/// @@ -18240,12 +21515,12 @@ pub struct SessionMcpConfigureGitHubResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMcpIsServerRunningResult { - /// True if the server has an active client and transport. - pub running: bool, +pub struct SessionCommandsListResult { + /// Commands available in this session + pub commands: Vec, } -/// Indicates whether the pending MCP OAuth response was accepted. +/// Indicates whether the pending client-handled command was completed successfully. /// ///
/// @@ -18255,12 +21530,12 @@ pub struct SessionMcpIsServerRunningResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMcpOauthHandlePendingRequestResult { - /// Whether the response was accepted. False if the request was unknown, timed out, or already resolved. +pub struct SessionCommandsHandlePendingCommandResult { + /// Whether the command was handled successfully pub success: bool, } -/// OAuth authorization URL the caller should open, or empty when cached tokens already authenticated the server. +/// Error message produced while executing the command, if any. /// ///
/// @@ -18270,13 +21545,13 @@ pub struct SessionMcpOauthHandlePendingRequestResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMcpOauthLoginResult { - /// URL the caller should open in a browser to complete OAuth. Omitted when cached tokens were still valid and no browser interaction was needed — the server is already reconnected in that case. When present, the runtime starts the callback listener before returning and continues the flow in the background; completion is signaled via session.mcp_server_status_changed. +pub struct SessionCommandsExecuteResult { + /// Error message produced while executing the command, if any. Omitted when the handler succeeded. #[serde(skip_serializing_if = "Option::is_none")] - pub authorization_url: Option, + pub error: Option, } -/// Indicates whether the pending MCP headers refresh response was accepted. +/// Indicates whether the command was accepted into the local execution queue. /// ///
/// @@ -18286,12 +21561,12 @@ pub struct SessionMcpOauthLoginResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMcpHeadersHandlePendingHeadersRefreshRequestResult { - /// Whether the response was accepted. False if the request was unknown, timed out, or already resolved. - pub success: bool, +pub struct SessionCommandsEnqueueResult { + /// True when the command was accepted into the local execution queue. False when the call targets a session that does not support local command queueing (e.g. remote sessions). + pub queued: bool, } -/// Resource contents returned by the MCP server. +/// Indicates whether the queued-command response was matched to a pending request. /// ///
/// @@ -18301,12 +21576,12 @@ pub struct SessionMcpHeadersHandlePendingHeadersRefreshRequestResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMcpAppsReadResourceResult { - /// Resource contents returned by the server - pub contents: Vec, +pub struct SessionCommandsRespondToQueuedCommandResult { + /// Whether a pending queued command with the given request ID was found and resolved. False when the request was already resolved, cancelled, or unknown. + pub success: bool, } -/// App-callable tools from the named MCP server. +/// Identifies the target session. /// ///
/// @@ -18316,12 +21591,12 @@ pub struct SessionMcpAppsReadResourceResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMcpAppsListToolsResult { - /// App-callable tools from the server - pub tools: Vec>, +pub struct SessionTelemetryGetEngagementIdParams { + /// Target session identifier + pub session_id: SessionId, } -/// Identifies the target session. +/// Telemetry engagement ID for the session, when available. /// ///
/// @@ -18331,12 +21606,13 @@ pub struct SessionMcpAppsListToolsResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMcpAppsGetHostContextParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionTelemetryGetEngagementIdResult { + /// Current telemetry engagement ID, when available. + #[serde(skip_serializing_if = "Option::is_none")] + pub engagement_id: Option, } -/// Current host context advertised to MCP App guests. +/// Transient answer generated from current conversation context. /// ///
/// @@ -18346,12 +21622,12 @@ pub struct SessionMcpAppsGetHostContextParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMcpAppsGetHostContextResult { - /// Current host context - pub context: McpAppsHostContextDetails, +pub struct SessionUiEphemeralQueryResult { + /// Full assistant response text. + pub answer: String, } -/// Diagnostic snapshot of MCP Apps wiring for the named server. +/// The elicitation response (accept with form values, decline, or cancel) /// ///
/// @@ -18361,14 +21637,15 @@ pub struct SessionMcpAppsGetHostContextResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMcpAppsDiagnoseResult { - /// Capability negotiation snapshot - pub capability: McpAppsDiagnoseCapability, - /// What the server returned for this session - pub server: McpAppsDiagnoseServer, +pub struct SessionUiElicitationResult { + /// The user's response: accept (submitted), decline (rejected), or cancel (dismissed) + pub action: UIElicitationResponseAction, + /// The form values submitted by the user (present when action is 'accept') + #[serde(skip_serializing_if = "Option::is_none")] + pub content: Option>, } -/// Resource contents returned by the MCP server. +/// Indicates whether the elicitation response was accepted; false if it was already resolved by another client. /// ///
/// @@ -18378,12 +21655,12 @@ pub struct SessionMcpAppsDiagnoseResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMcpResourcesReadResult { - /// Resource contents returned by the server - pub contents: Vec, +pub struct SessionUiHandlePendingElicitationResult { + /// Whether the response was accepted. False if the request was already resolved by another client. + pub success: bool, } -/// One page of resources advertised by the named MCP server. +/// Indicates whether the pending UI request was resolved by this call. /// ///
/// @@ -18393,15 +21670,12 @@ pub struct SessionMcpResourcesReadResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMcpResourcesListResult { - /// Opaque cursor for the next page, if the server has more resources - #[serde(skip_serializing_if = "Option::is_none")] - pub next_cursor: Option, - /// Resources advertised by the server (proxied MCP `resources/list`) - pub resources: Vec, +pub struct SessionUiHandlePendingUserInputResult { + /// True if the request was still pending and was resolved by this call. False if the request ID was unknown, already resolved by another client (e.g. GitHub), expired, or otherwise no longer pending. + pub success: bool, } -/// One page of resource templates advertised by the named MCP server. +/// Indicates whether the pending UI request was resolved by this call. /// ///
/// @@ -18411,15 +21685,12 @@ pub struct SessionMcpResourcesListResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMcpResourcesListTemplatesResult { - /// Opaque cursor for the next page, if the server has more resource templates - #[serde(skip_serializing_if = "Option::is_none")] - pub next_cursor: Option, - /// Resource templates advertised by the server (proxied MCP `resources/templates/list`) - pub resource_templates: Vec, +pub struct SessionUiHandlePendingSamplingResult { + /// True if the request was still pending and was resolved by this call. False if the request ID was unknown, already resolved by another client (e.g. GitHub), expired, or otherwise no longer pending. + pub success: bool, } -/// Identifies the target session. +/// Indicates whether the pending UI request was resolved by this call. /// ///
/// @@ -18429,12 +21700,12 @@ pub struct SessionMcpResourcesListTemplatesResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPluginsListParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionUiHandlePendingAutoModeSwitchResult { + /// True if the request was still pending and was resolved by this call. False if the request ID was unknown, already resolved by another client (e.g. GitHub), expired, or otherwise no longer pending. + pub success: bool, } -/// Plugins installed for the session, with their enabled state and version metadata. +/// Indicates whether the pending UI request was resolved by this call. /// ///
/// @@ -18444,12 +21715,12 @@ pub struct SessionPluginsListParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPluginsListResult { - /// Installed plugins - pub plugins: Vec, +pub struct SessionUiHandlePendingSessionLimitsExhaustedResult { + /// True if the request was still pending and was resolved by this call. False if the request ID was unknown, already resolved by another client (e.g. GitHub), expired, or otherwise no longer pending. + pub success: bool, } -/// A snapshot of the provider endpoint the session is currently configured to talk to. +/// Indicates whether the pending UI request was resolved by this call. /// ///
/// @@ -18459,28 +21730,12 @@ pub struct SessionPluginsListResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionProviderGetEndpointResult { - /// A credential the caller should use with this endpoint. Omitted only when the endpoint accepts unauthenticated requests. - #[serde(skip_serializing_if = "Option::is_none")] - pub api_key: Option, - /// Base URL to pass to the LLM client library. - pub base_url: String, - /// HTTP headers the caller must include on every outbound request. - pub headers: HashMap, - /// Short-lived, rotating credential the caller must send on every request, in addition to `apiKey` if one is present. Omitted when the endpoint does not require one. - #[serde(skip_serializing_if = "Option::is_none")] - pub session_token: Option, - /// Transport to be used for provider requests. - #[serde(skip_serializing_if = "Option::is_none")] - pub transport: Option, - /// Provider family. Matches the `type` field of a BYOK provider config. - pub r#type: ProviderEndpointType, - /// Wire API to be used, when required for the provider type. - #[serde(skip_serializing_if = "Option::is_none")] - pub wire_api: Option, +pub struct SessionUiHandlePendingExitPlanModeResult { + /// True if the request was still pending and was resolved by this call. False if the request ID was unknown, already resolved by another client (e.g. GitHub), expired, or otherwise no longer pending. + pub success: bool, } -/// The selectable model entries synthesized for the models added by this call. +/// Identifies the target session. /// ///
/// @@ -18490,12 +21745,12 @@ pub struct SessionProviderGetEndpointResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionProviderAddResult { - /// Synthesized selectable model entries for the newly added BYOK models, each under its provider-qualified selection id (`provider/id`). Empty when only providers were added. - pub models: Vec, +pub struct SessionUiRegisterDirectAutoModeSwitchHandlerParams { + /// Target session identifier + pub session_id: SessionId, } -/// Indicates whether the session options patch was applied successfully. +/// Register an in-process handler for `auto_mode_switch.requested` events. The caller still attaches the actual listener via the standard event-subscription mechanism; this registration solely tells the server bridge to skip its own dispatch (so a remote client doesn't race the in-process handler for the same requestId). /// ///
/// @@ -18505,15 +21760,12 @@ pub struct SessionProviderAddResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionOptionsUpdateResult { - /// Number of hooks loaded from installed plugins, returned when installedPlugins is updated - #[serde(skip_serializing_if = "Option::is_none")] - pub plugin_hook_count: Option, - /// Whether the operation succeeded - pub success: bool, +pub struct SessionUiRegisterDirectAutoModeSwitchHandlerResult { + /// Opaque handle representing the registration. Pass this same handle to `unregisterDirectAutoModeSwitchHandler` when the in-process handler is no longer active. Multiple registrations are reference-counted; the server bridge will only dispatch auto-mode-switch requests when no handles are active. + pub handle: String, } -/// Identifies the target session. +/// Indicates whether the handle was active and the registration count was decremented. /// ///
/// @@ -18523,12 +21775,12 @@ pub struct SessionOptionsUpdateResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionExtensionsListParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionUiUnregisterDirectAutoModeSwitchHandlerResult { + /// True if the handle was active and decremented the counter; false if the handle was unknown. + pub unregistered: bool, } -/// Extensions discovered for the session, with their current status. +/// Indicates whether the operation succeeded. /// ///
/// @@ -18538,12 +21790,12 @@ pub struct SessionExtensionsListParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionExtensionsListResult { - /// Discovered extensions and their current status - pub extensions: Vec, +pub struct SessionPermissionsConfigureResult { + /// Whether the operation succeeded + pub success: bool, } -/// Identifies the target session. +/// Indicates whether the permission decision was applied; false when the request was already resolved. /// ///
/// @@ -18553,12 +21805,12 @@ pub struct SessionExtensionsListResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionExtensionsReloadParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionPermissionsHandlePendingPermissionRequestResult { + /// Whether the permission request was handled successfully + pub success: bool, } -/// Indicates whether the external tool call result was handled successfully. +/// List of pending permission requests reconstructed from event history. /// ///
/// @@ -18568,12 +21820,12 @@ pub struct SessionExtensionsReloadParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionToolsHandlePendingToolCallResult { - /// Whether the tool call result was handled successfully - pub success: bool, +pub struct SessionPermissionsPendingRequestsResult { + /// Pending permission prompts reconstructed from the session's event history. Equivalent to the set of `permission.requested` events that have not yet been followed by a matching `permission.completed` event. Used by clients (e.g. the CLI) to hydrate UI for prompts that were emitted before the client attached to the session. + pub items: Vec, } -/// Identifies the target session. +/// Indicates whether the operation succeeded. /// ///
/// @@ -18583,12 +21835,12 @@ pub struct SessionToolsHandlePendingToolCallResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionToolsInitializeAndValidateParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionPermissionsSetApproveAllResult { + /// Whether the operation succeeded + pub success: bool, } -/// Resolve, build, and validate the runtime tool list for this session. Subagent sessions and consumer flows that need an initialized tool set before `send` invoke this. Default base-class implementation is a no-op for sessions that don't support tool validation. +/// Indicates whether the operation succeeded and reports the post-mutation state. /// ///
/// @@ -18598,9 +21850,17 @@ pub struct SessionToolsInitializeAndValidateParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionToolsInitializeAndValidateResult {} +pub struct SessionPermissionsSetAllowAllResult { + /// Authoritative full allow-all state after the mutation + pub enabled: bool, + /// Authoritative allow-all mode after the mutation + #[serde(skip_serializing_if = "Option::is_none")] + pub mode: Option, + /// Whether the operation succeeded + pub success: bool, +} -/// Identifies the target session. +/// Current allow-all permission mode. /// ///
/// @@ -18610,12 +21870,15 @@ pub struct SessionToolsInitializeAndValidateResult {} ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionToolsGetCurrentMetadataParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionPermissionsGetAllowAllResult { + /// Whether full allow-all permissions are currently active + pub enabled: bool, + /// Current allow-all mode + #[serde(skip_serializing_if = "Option::is_none")] + pub mode: Option, } -/// Current lightweight tool metadata snapshot for the session. +/// Indicates whether the operation succeeded. /// ///
/// @@ -18625,12 +21888,12 @@ pub struct SessionToolsGetCurrentMetadataParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionToolsGetCurrentMetadataResult { - /// Current tool metadata, or null when tools have not been initialized yet - pub tools: Option>, +pub struct SessionPermissionsModifyRulesResult { + /// Whether the operation succeeded + pub success: bool, } -/// Empty result after applying subagent settings +/// Indicates whether the operation succeeded. /// ///
/// @@ -18640,9 +21903,12 @@ pub struct SessionToolsGetCurrentMetadataResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionToolsUpdateSubagentSettingsResult {} +pub struct SessionPermissionsSetRequiredResult { + /// Whether the operation succeeded + pub success: bool, +} -/// Slash commands available in the session, after applying any include/exclude filters. +/// Indicates whether the operation succeeded. /// ///
/// @@ -18652,12 +21918,12 @@ pub struct SessionToolsUpdateSubagentSettingsResult {} ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionCommandsListResult { - /// Commands available in this session - pub commands: Vec, +pub struct SessionPermissionsResetSessionApprovalsResult { + /// Whether the operation succeeded + pub success: bool, } -/// Indicates whether the pending client-handled command was completed successfully. +/// Indicates whether the operation succeeded. /// ///
/// @@ -18667,12 +21933,12 @@ pub struct SessionCommandsListResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionCommandsHandlePendingCommandResult { - /// Whether the command was handled successfully +pub struct SessionPermissionsNotifyPromptShownResult { + /// Whether the operation succeeded pub success: bool, } -/// Error message produced while executing the command, if any. +/// Snapshot of the session's allow-listed directories and primary working directory. /// ///
/// @@ -18682,13 +21948,14 @@ pub struct SessionCommandsHandlePendingCommandResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionCommandsExecuteResult { - /// Error message produced while executing the command, if any. Omitted when the handler succeeded. - #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, +pub struct SessionPermissionsPathsListResult { + /// All directories currently allowed for tool access on this session. + pub directories: Vec, + /// The primary working directory for this session. + pub primary: String, } -/// Indicates whether the command was accepted into the local execution queue. +/// Indicates whether the operation succeeded. /// ///
/// @@ -18698,12 +21965,12 @@ pub struct SessionCommandsExecuteResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionCommandsEnqueueResult { - /// True when the command was accepted into the local execution queue. False when the call targets a session that does not support local command queueing (e.g. remote sessions). - pub queued: bool, +pub struct SessionPermissionsPathsAddResult { + /// Whether the operation succeeded + pub success: bool, } -/// Indicates whether the queued-command response was matched to a pending request. +/// Indicates whether the operation succeeded. /// ///
/// @@ -18713,12 +21980,12 @@ pub struct SessionCommandsEnqueueResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionCommandsRespondToQueuedCommandResult { - /// Whether a pending queued command with the given request ID was found and resolved. False when the request was already resolved, cancelled, or unknown. +pub struct SessionPermissionsPathsUpdatePrimaryResult { + /// Whether the operation succeeded pub success: bool, } -/// Identifies the target session. +/// Indicates whether the supplied path is within the session's allowed directories. /// ///
/// @@ -18728,12 +21995,12 @@ pub struct SessionCommandsRespondToQueuedCommandResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionTelemetryGetEngagementIdParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionPermissionsPathsIsPathWithinAllowedDirectoriesResult { + /// Whether the path is within the session's allowed directories + pub allowed: bool, } -/// Telemetry engagement ID for the session, when available. +/// Indicates whether the supplied path is within the session's workspace directory. /// ///
/// @@ -18743,13 +22010,12 @@ pub struct SessionTelemetryGetEngagementIdParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionTelemetryGetEngagementIdResult { - /// Current telemetry engagement ID, when available. - #[serde(skip_serializing_if = "Option::is_none")] - pub engagement_id: Option, +pub struct SessionPermissionsPathsIsPathWithinWorkspaceResult { + /// Whether the path is within the session workspace directory + pub allowed: bool, } -/// Transient answer generated from current conversation context. +/// Resolved location-permissions key and type. /// ///
/// @@ -18759,12 +22025,14 @@ pub struct SessionTelemetryGetEngagementIdResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionUiEphemeralQueryResult { - /// Full assistant response text. - pub answer: String, +pub struct SessionPermissionsLocationsResolveResult { + /// Location key used in the location-permissions store + pub location_key: String, + /// Whether the location is a git repo or directory + pub location_type: PermissionLocationType, } -/// The elicitation response (accept with form values, decline, or cancel) +/// Summary of persisted location permissions applied to the session. /// ///
/// @@ -18774,15 +22042,22 @@ pub struct SessionUiEphemeralQueryResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionUiElicitationResult { - /// The user's response: accept (submitted), decline (rejected), or cancel (dismissed) - pub action: UIElicitationResponseAction, - /// The form values submitted by the user (present when action is 'accept') - #[serde(skip_serializing_if = "Option::is_none")] - pub content: Option>, +pub struct SessionPermissionsLocationsApplyResult { + /// Number of persisted allowed directories added to the live path manager + pub applied_directory_count: i64, + /// Number of location-scoped rules added to the live permission service + pub applied_rule_count: i64, + /// Location-scoped rules applied to the live permission service + pub applied_rules: Vec, + /// Whether a different location was applied since the previous apply call + pub changed: bool, + /// Location key used in the location-permissions store + pub location_key: String, + /// Whether the location is a git repo or directory + pub location_type: PermissionLocationType, } -/// Indicates whether the elicitation response was accepted; false if it was already resolved by another client. +/// Indicates whether the operation succeeded. /// ///
/// @@ -18792,12 +22067,12 @@ pub struct SessionUiElicitationResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionUiHandlePendingElicitationResult { - /// Whether the response was accepted. False if the request was already resolved by another client. +pub struct SessionPermissionsLocationsAddToolApprovalResult { + /// Whether the operation succeeded pub success: bool, } -/// Indicates whether the pending UI request was resolved by this call. +/// Folder trust check result. /// ///
/// @@ -18807,12 +22082,12 @@ pub struct SessionUiHandlePendingElicitationResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionUiHandlePendingUserInputResult { - /// True if the request was still pending and was resolved by this call. False if the request ID was unknown, already resolved by another client (e.g. GitHub), expired, or otherwise no longer pending. - pub success: bool, +pub struct SessionPermissionsFolderTrustIsTrustedResult { + /// Whether the folder is trusted + pub trusted: bool, } -/// Indicates whether the pending UI request was resolved by this call. +/// Indicates whether the operation succeeded. /// ///
/// @@ -18822,12 +22097,12 @@ pub struct SessionUiHandlePendingUserInputResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionUiHandlePendingSamplingResult { - /// True if the request was still pending and was resolved by this call. False if the request ID was unknown, already resolved by another client (e.g. GitHub), expired, or otherwise no longer pending. +pub struct SessionPermissionsFolderTrustAddTrustedResult { + /// Whether the operation succeeded pub success: bool, } -/// Indicates whether the pending UI request was resolved by this call. +/// Indicates whether the operation succeeded. /// ///
/// @@ -18837,12 +22112,12 @@ pub struct SessionUiHandlePendingSamplingResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionUiHandlePendingAutoModeSwitchResult { - /// True if the request was still pending and was resolved by this call. False if the request ID was unknown, already resolved by another client (e.g. GitHub), expired, or otherwise no longer pending. +pub struct SessionPermissionsUrlsSetUnrestrictedModeResult { + /// Whether the operation succeeded pub success: bool, } -/// Indicates whether the pending UI request was resolved by this call. +/// Identifier of the session event that was emitted for the log message. /// ///
/// @@ -18852,12 +22127,12 @@ pub struct SessionUiHandlePendingAutoModeSwitchResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionUiHandlePendingSessionLimitsExhaustedResult { - /// True if the request was still pending and was resolved by this call. False if the request ID was unknown, already resolved by another client (e.g. GitHub), expired, or otherwise no longer pending. - pub success: bool, +pub struct SessionLogResult { + /// The unique identifier of the emitted session event + pub event_id: String, } -/// Indicates whether the pending UI request was resolved by this call. +/// Identifies the target session. /// ///
/// @@ -18867,12 +22142,47 @@ pub struct SessionUiHandlePendingSessionLimitsExhaustedResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionUiHandlePendingExitPlanModeResult { - /// True if the request was still pending and was resolved by this call. False if the request ID was unknown, already resolved by another client (e.g. GitHub), expired, or otherwise no longer pending. - pub success: bool, +pub struct SessionMetadataSnapshotParams { + /// Target session identifier + pub session_id: SessionId, } -/// Identifies the target session. +/// Public-facing projection of workspace metadata for SDK / TUI consumers +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionMetadataSnapshotResultWorkspace { + /// Branch checked out at session start, if any + #[serde(skip_serializing_if = "Option::is_none")] + pub branch: Option, + /// ISO 8601 timestamp when the workspace was created + #[serde(rename = "created_at", skip_serializing_if = "Option::is_none")] + pub created_at: Option, + /// Current working directory at session start + #[serde(skip_serializing_if = "Option::is_none")] + pub cwd: Option, + /// Resolved git root for cwd, if any + #[serde(rename = "git_root", skip_serializing_if = "Option::is_none")] + pub git_root: Option, + /// Repository host type, if known + #[serde(rename = "host_type", skip_serializing_if = "Option::is_none")] + pub host_type: Option, + /// Workspace identifier (1:1 with sessionId) + pub id: String, + /// Display name for the session, if set + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + /// Repository identifier in 'owner/repo' or 'org/project/repo' format, if any + #[serde(skip_serializing_if = "Option::is_none")] + pub repository: Option, + /// ISO 8601 timestamp when the workspace was last updated + #[serde(rename = "updated_at", skip_serializing_if = "Option::is_none")] + pub updated_at: Option, + /// Whether the display name was explicitly set by the user + #[serde(rename = "user_named", skip_serializing_if = "Option::is_none")] + pub user_named: Option, +} + +/// Point-in-time snapshot of slow-changing session identifier and state fields /// ///
/// @@ -18882,12 +22192,45 @@ pub struct SessionUiHandlePendingExitPlanModeResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionUiRegisterDirectAutoModeSwitchHandlerParams { - /// Target session identifier +pub struct SessionMetadataSnapshotResult { + /// True when the session was detected to be in use by another process at construction time. Local consumers may surface a confirmation prompt before fully attaching. Always false for new sessions. + pub already_in_use: bool, + /// Runtime client name associated with the session (telemetry identifier). + #[serde(skip_serializing_if = "Option::is_none")] + pub client_name: Option, + /// The current agent mode for this session (e.g., 'interactive', 'plan', 'autopilot') + pub current_mode: MetadataSnapshotCurrentMode, + /// User-provided name supplied at session construction (via `--name`), if any. Immutable after construction. + #[serde(skip_serializing_if = "Option::is_none")] + pub initial_name: Option, + /// Whether this is a remote session (i.e., one whose runtime executes elsewhere and is steered through this process) + pub is_remote: bool, + /// ISO 8601 timestamp of when the session's persisted state was last modified on disk. For new sessions, equals startTime. For resumed sessions, reflects the previous modification time at construction. + pub modified_time: String, + /// Remote-session-specific metadata. Populated only when `isRemote` is true. Fields are immutable for the lifetime of the session. + #[serde(skip_serializing_if = "Option::is_none")] + pub remote_metadata: Option, + /// Currently selected model identifier, if any + #[serde(skip_serializing_if = "Option::is_none")] + pub selected_model: Option, + /// The unique identifier of the session pub session_id: SessionId, + /// Current session limits, or null when no limits are active + pub session_limits: Option, + /// ISO 8601 timestamp of when the session started + pub start_time: String, + /// Short human-readable summary of the session, if known. Omitted when no summary has been generated. + #[serde(skip_serializing_if = "Option::is_none")] + pub summary: Option, + /// Absolute path to the session's current working directory + pub working_directory: String, + /// Public-facing workspace metadata for this session, or null if the session has no associated workspace. Excludes runtime-internal fields (GitHub IDs, summary count, internal flags). + pub workspace: Option, + /// Absolute path to the session's workspace directory on disk, or null if the session has no associated workspace + pub workspace_path: Option, } -/// Register an in-process handler for `auto_mode_switch.requested` events. The caller still attaches the actual listener via the standard event-subscription mechanism; this registration solely tells the server bridge to skip its own dispatch (so a remote client doesn't race the in-process handler for the same requestId). +/// Identifies the target session. /// ///
/// @@ -18897,12 +22240,12 @@ pub struct SessionUiRegisterDirectAutoModeSwitchHandlerParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionUiRegisterDirectAutoModeSwitchHandlerResult { - /// Opaque handle representing the registration. Pass this same handle to `unregisterDirectAutoModeSwitchHandler` when the in-process handler is no longer active. Multiple registrations are reference-counted; the server bridge will only dispatch auto-mode-switch requests when no handles are active. - pub handle: String, +pub struct SessionMetadataIsProcessingParams { + /// Target session identifier + pub session_id: SessionId, } -/// Indicates whether the handle was active and the registration count was decremented. +/// Indicates whether the local session is currently processing a turn or background continuation. /// ///
/// @@ -18912,12 +22255,12 @@ pub struct SessionUiRegisterDirectAutoModeSwitchHandlerResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionUiUnregisterDirectAutoModeSwitchHandlerResult { - /// True if the handle was active and decremented the counter; false if the handle was unknown. - pub unregistered: bool, +pub struct SessionMetadataIsProcessingResult { + /// Whether the session is currently processing user/agent messages. False for non-local sessions (which don't run a local agentic loop). Reflects an in-flight turn or background continuation. + pub processing: bool, } -/// Indicates whether the operation succeeded. +/// Identifies the target session. /// ///
/// @@ -18927,12 +22270,12 @@ pub struct SessionUiUnregisterDirectAutoModeSwitchHandlerResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPermissionsConfigureResult { - /// Whether the operation succeeded - pub success: bool, +pub struct SessionMetadataActivityParams { + /// Target session identifier + pub session_id: SessionId, } -/// Indicates whether the permission decision was applied; false when the request was already resolved. +/// Current activity flags for the session. /// ///
/// @@ -18942,12 +22285,40 @@ pub struct SessionPermissionsConfigureResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPermissionsHandlePendingPermissionRequestResult { - /// Whether the permission request was handled successfully - pub success: bool, +pub struct SessionMetadataActivityResult { + /// Whether an in-flight operation can currently be aborted. + pub abortable: bool, + /// Whether the session currently has active work, including running turns or tasks. + pub has_active_work: bool, +} + +/// Token-usage breakdown for the session's current context window +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionMetadataContextInfoResultContextInfo { + /// Output reserve plus tokens after the buffer-exhaustion blocking threshold (default 95%) + pub buffer_tokens: i64, + /// Token count at which background compaction starts (configurable percentage of promptTokenLimit) + pub compaction_threshold: i64, + /// Tokens consumed by user/assistant/tool messages + pub conversation_tokens: i64, + /// Prompt token limit plus the model's full output token limit. + pub limit: i64, + /// Tokens consumed by MCP tool definitions (subset of toolDefinitionsTokens, excludes deferred tools) + pub mcp_tools_tokens: i64, + /// The model used for token counting + pub model_name: String, + /// Maximum prompt tokens allowed by the model (or DEFAULT_TOKEN_LIMIT if unspecified) + pub prompt_token_limit: i64, + /// Tokens consumed by the system prompt + pub system_tokens: i64, + /// Tokens consumed by tool definitions sent to the model (excludes deferred tools) + pub tool_definitions_tokens: i64, + /// Sum of system, conversation and tool-definition tokens + pub total_tokens: i64, } -/// List of pending permission requests reconstructed from event history. +/// Token breakdown for the session's current context window, or null if uninitialized. /// ///
/// @@ -18957,12 +22328,12 @@ pub struct SessionPermissionsHandlePendingPermissionRequestResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPermissionsPendingRequestsResult { - /// Pending permission prompts reconstructed from the session's event history. Equivalent to the set of `permission.requested` events that have not yet been followed by a matching `permission.completed` event. Used by clients (e.g. the CLI) to hydrate UI for prompts that were emitted before the client attached to the session. - pub items: Vec, +pub struct SessionMetadataContextInfoResult { + /// Token breakdown for the current context window, or null if the session has not yet been initialized (no system prompt or tool metadata cached). + pub context_info: Option, } -/// Indicates whether the operation succeeded. +/// Identifies the target session. /// ///
/// @@ -18972,80 +22343,85 @@ pub struct SessionPermissionsPendingRequestsResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPermissionsSetApproveAllResult { - /// Whether the operation succeeded - pub success: bool, +pub struct SessionMetadataGetContextAttributionParams { + /// Target session identifier + pub session_id: SessionId, } -/// Indicates whether the operation succeeded and reports the post-mutation state. -/// -///
-/// -/// **Experimental.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
+/// The six normalized `/context` header buckets, computed from the same tokenization as `entries` so the two never disagree. Convenience rollups: `freeSpace` and `buffer` describe window capacity rather than occupied context, so the values do not sum to `totalTokens`. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPermissionsSetAllowAllResult { - /// Authoritative full allow-all state after the mutation - pub enabled: bool, - /// Authoritative allow-all mode after the mutation - #[serde(skip_serializing_if = "Option::is_none")] - pub mode: Option, - /// Whether the operation succeeded - pub success: bool, +pub struct SessionMetadataGetContextAttributionResultContextAttributionCategories { + /// Output reserve plus post-blocking-threshold buffer. + pub buffer: i64, + /// Custom-instructions tokens (0 when none are configured). + pub custom_instructions: i64, + /// Remaining unused window capacity (clamped at 0). + pub free_space: i64, + /// MCP tool-definition tokens. + pub mcp_tools: i64, + /// Conversation (user/assistant/tool) message tokens. + pub messages: i64, + /// System prompt tokens, excluding custom instructions. + pub system_prompt: i64, + /// Non-MCP tool-definition tokens. + pub system_tools: i64, } -/// Current allow-all permission mode. -/// -///
-/// -/// **Experimental.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
+/// Successful compaction history for the session. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPermissionsGetAllowAllResult { - /// Whether full allow-all permissions are currently active - pub enabled: bool, - /// Current allow-all mode - #[serde(skip_serializing_if = "Option::is_none")] - pub mode: Option, +pub struct SessionMetadataGetContextAttributionResultContextAttributionCompactions { + /// Number of successful compactions in this session. + pub count: i64, } -/// Indicates whether the operation succeeded. -/// -///
-/// -/// **Experimental.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPermissionsModifyRulesResult { - /// Whether the operation succeeded - pub success: bool, +pub struct SessionMetadataGetContextAttributionResultContextAttributionEntriesItem { + /// Supplementary per-entry metadata (e.g. `messageCount`, `role`, `evictable`, `pluginSource`). Values are stringified; parse as needed and ignore unrecognized keys. + #[serde(skip_serializing_if = "Option::is_none")] + pub attributes: Option>, + /// Identifier for this entry, formed by joining its `kind` and source name (e.g. `tool:bash`, `skill:tmux`, `toolDefinition:bash`); unique within the snapshot. Use it to match the same entry across snapshots, to correlate with other APIs (skill/agent/MCP registries), and as the `parentId` target for nesting. Distinct from the human-facing `label`. + pub id: String, + /// Source category for this entry. Not a closed set — tolerate unknown values. Known values today: `skill`, `subagent`, `mcpServer`, `tool`, `system`, `toolDefinition`, `plugin`. + pub kind: String, + /// Human-readable display label, e.g. `bash` or `skill: tmux`. Presentation-only; may be localized/reformatted without notice — do not key off it. + pub label: String, + /// Optional `id` of the parent entry: e.g. a `plugin` entry parenting its `skill`/`mcpServer` entries, or the `system` entry parenting `toolDefinition` entries. Omitted for top-level entries. + #[serde(skip_serializing_if = "Option::is_none")] + pub parent_id: Option, + /// Token count currently in context attributable to this entry. + pub tokens: i64, } -/// Indicates whether the operation succeeded. -/// -///
-/// -/// **Experimental.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
+/// Per-source token attribution snapshot for the current context window. The heaviest individual messages are available separately via `metadata.getContextHeaviestMessages`. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPermissionsSetRequiredResult { - /// Whether the operation succeeded - pub success: bool, +pub struct SessionMetadataGetContextAttributionResultContextAttribution { + /// Output reserve plus the tokens past the buffer-exhaustion blocking threshold. Mirrors `SessionContextInfo.bufferTokens`. + pub buffer_tokens: i64, + /// The six normalized `/context` header buckets, computed from the same tokenization as `entries` so the two never disagree. Convenience rollups: `freeSpace` and `buffer` describe window capacity rather than occupied context, so the values do not sum to `totalTokens`. + pub categories: SessionMetadataGetContextAttributionResultContextAttributionCategories, + /// Successful compaction history for the session. + pub compactions: SessionMetadataGetContextAttributionResultContextAttributionCompactions, + /// Token count at which background compaction starts. Mirrors `SessionContextInfo.compactionThreshold`. + pub compaction_threshold: i64, + /// Flat list of per-source attribution entries. Group by `kind` and render unrecognized kinds generically. Nesting and rollups are expressed via `parentId`. + pub entries: Vec, + /// Prompt limit plus the model's output reserve: the full context window `categories.freeSpace` and `categories.buffer` are measured against. Mirrors `SessionContextInfo.limit`. + pub limit: i64, + /// The concrete model id the entire breakdown was tokenized against (feeds the per-model token multiplier). Under `Auto` (Free/Student) this is the resolved model, not the literal `auto` sentinel, so totals are not undercounted. A single-model approximation of a potentially multi-model Auto session. + pub model_id: String, + /// How `modelId` was chosen. Not a closed set — tolerate unknown values. Known values today: `autoResolved` (the model Auto resolved to), `selected` (the user's explicitly selected model), `default` (a fallback before any model is known). + pub model_source: String, + /// Maximum prompt tokens the resolved model accepts — the denominator for a `##k/###k` context-usage display. Mirrors `SessionContextInfo.promptTokenLimit`. + pub prompt_token_limit: i64, + /// Total token count of the current context window the entries are measured against (system message + conversation messages + tool definitions — the same total reported by /context). Divide an entry's `tokens` by this to derive its share. + pub total_tokens: i64, } -/// Indicates whether the operation succeeded. +/// Per-source attribution breakdown for the session's current context window, or null if uninitialized. /// ///
/// @@ -19055,12 +22431,12 @@ pub struct SessionPermissionsSetRequiredResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPermissionsResetSessionApprovalsResult { - /// Whether the operation succeeded - pub success: bool, +pub struct SessionMetadataGetContextAttributionResult { + /// Per-source context-window attribution, or null if the session has not yet been initialized (no system prompt or tool metadata cached). + pub context_attribution: Option, } -/// Indicates whether the operation succeeded. +/// The heaviest individual messages in the session's context window, most-expensive first. /// ///
/// @@ -19070,12 +22446,14 @@ pub struct SessionPermissionsResetSessionApprovalsResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPermissionsNotifyPromptShownResult { - /// Whether the operation succeeded - pub success: bool, +pub struct SessionMetadataGetContextHeaviestMessagesResult { + /// Heaviest messages, most-expensive first. + pub messages: Vec, + /// Total token count of the current context window, so callers can compute each message's share without a second call. + pub total_tokens: i64, } -/// Snapshot of the session's allow-listed directories and primary working directory. +/// Notify the session that its working directory context has changed. Emits a `session.context_changed` event so consumers (telemetry, OTel tracker, ACP, the timeline UI) can react. Use this when the host has detected a cwd/branch/repo change outside the session's normal lifecycle (e.g., after a shell command in interactive mode). For a local session, a report whose `cwd` diverges from the session's current working directory is ignored (the call still succeeds but records nothing and emits no event); move a local session's working directory via `metadata.setWorkingDirectory` instead. /// ///
/// @@ -19085,14 +22463,9 @@ pub struct SessionPermissionsNotifyPromptShownResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPermissionsPathsListResult { - /// All directories currently allowed for tool access on this session. - pub directories: Vec, - /// The primary working directory for this session. - pub primary: String, -} +pub struct SessionMetadataRecordContextChangeResult {} -/// Indicates whether the operation succeeded. +/// Update the session's working directory. Used by the host when the user explicitly changes cwd (e.g., the `/cd` slash command). The host is responsible for any related side-effects (file index, etc.); it does NOT change the process working directory (a session's cwd is per-session, not process-global). For local sessions the runtime validates the target first (an absolute path that exists on disk) and re-bases the permission primary directory; a rejected validation fails the call before anything is mutated, persisted, or emitted. Location-scoped permission rules are then re-keyed to the new directory (best-effort). Remote sessions only record the path. /// ///
/// @@ -19102,12 +22475,12 @@ pub struct SessionPermissionsPathsListResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPermissionsPathsAddResult { - /// Whether the operation succeeded - pub success: bool, +pub struct SessionMetadataSetWorkingDirectoryResult { + /// Working directory after the update + pub working_directory: String, } -/// Indicates whether the operation succeeded. +/// Re-tokenize the session's existing messages against `modelId` and return the token totals. Useful for hosts that want an initial estimate of context usage on session resume, before the next agent turn fires `session.context_info_changed` events. Returns zeros for an empty session. /// ///
/// @@ -19117,12 +22490,16 @@ pub struct SessionPermissionsPathsAddResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPermissionsPathsUpdatePrimaryResult { - /// Whether the operation succeeded - pub success: bool, +pub struct SessionMetadataRecomputeContextTokensResult { + /// Tokens contributed by user/assistant/tool messages (excludes system/developer prompts). + pub messages_token_count: i64, + /// Tokens contributed by system/developer prompt snapshots. + pub system_token_count: i64, + /// Sum of tokens across chat-context and system-context messages currently held by the session. + pub total_tokens: i64, } -/// Indicates whether the supplied path is within the session's allowed directories. +/// Identifies the target session. /// ///
/// @@ -19132,12 +22509,12 @@ pub struct SessionPermissionsPathsUpdatePrimaryResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPermissionsPathsIsPathWithinAllowedDirectoriesResult { - /// Whether the path is within the session's allowed directories - pub allowed: bool, +pub struct SessionSettingsSnapshotParams { + /// Target session identifier + pub session_id: SessionId, } -/// Indicates whether the supplied path is within the session's workspace directory. +/// Redacted, serializable view of session runtime settings for SDK boundary consumers. Secrets and raw feature flags are intentionally excluded. /// ///
/// @@ -19147,12 +22524,23 @@ pub struct SessionPermissionsPathsIsPathWithinAllowedDirectoriesResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPermissionsPathsIsPathWithinWorkspaceResult { - /// Whether the path is within the session workspace directory - pub allowed: bool, +pub struct SessionSettingsSnapshotResult { + #[serde(skip_serializing_if = "Option::is_none")] + pub client_name: Option, + pub job: SessionSettingsJobSnapshot, + pub model: SessionSettingsModelSnapshot, + pub online_evaluation: SessionSettingsOnlineEvaluationSnapshot, + pub repo: SessionSettingsRepoSnapshot, + #[serde(skip_serializing_if = "Option::is_none")] + pub start_time_ms: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub timeout_ms: Option, + pub validation: SessionSettingsValidationSnapshot, + #[serde(skip_serializing_if = "Option::is_none")] + pub version: Option, } -/// Resolved location-permissions key and type. +/// Batch content-exclusion result. Callers must fail closed when policy evaluation is unavailable. /// ///
/// @@ -19162,14 +22550,14 @@ pub struct SessionPermissionsPathsIsPathWithinWorkspaceResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPermissionsLocationsResolveResult { - /// Location key used in the location-permissions store - pub location_key: String, - /// Whether the location is a git repo or directory - pub location_type: PermissionLocationType, +pub struct SessionContentExclusionCheckPathsResult { + /// Whether the session's policy service was available for the complete batch. When false, checks is empty and callers must treat every requested path as excluded. + pub available: bool, + /// Per-path decisions in request order. Empty when available is false. + pub checks: Vec, } -/// Summary of persisted location permissions applied to the session. +/// Identifier of the spawned process, used to correlate streamed output and exit notifications. /// ///
/// @@ -19179,22 +22567,12 @@ pub struct SessionPermissionsLocationsResolveResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPermissionsLocationsApplyResult { - /// Number of persisted allowed directories added to the live path manager - pub applied_directory_count: i64, - /// Number of location-scoped rules added to the live permission service - pub applied_rule_count: i64, - /// Location-scoped rules applied to the live permission service - pub applied_rules: Vec, - /// Whether a different location was applied since the previous apply call - pub changed: bool, - /// Location key used in the location-permissions store - pub location_key: String, - /// Whether the location is a git repo or directory - pub location_type: PermissionLocationType, +pub struct SessionShellExecResult { + /// Unique identifier for tracking streamed output + pub process_id: String, } -/// Indicates whether the operation succeeded. +/// Indicates whether the signal was delivered; false if the process was unknown or already exited. /// ///
/// @@ -19204,12 +22582,12 @@ pub struct SessionPermissionsLocationsApplyResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPermissionsLocationsAddToolApprovalResult { - /// Whether the operation succeeded - pub success: bool, +pub struct SessionShellKillResult { + /// Whether the signal was sent successfully + pub killed: bool, } -/// Folder trust check result. +/// Result of a user-requested shell command. /// ///
/// @@ -19219,12 +22597,22 @@ pub struct SessionPermissionsLocationsAddToolApprovalResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPermissionsFolderTrustIsTrustedResult { - /// Whether the folder is trusted - pub trusted: bool, +pub struct SessionShellExecuteUserRequestedResult { + /// Error output when the execution failed + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + /// Process exit code, when available + #[serde(skip_serializing_if = "Option::is_none")] + pub exit_code: Option, + /// Captured command output + pub output: String, + /// Whether the command completed successfully + pub success: bool, + /// Tool call id emitted for the shell execution + pub tool_call_id: String, } -/// Indicates whether the operation succeeded. +/// Cancellation result for a user-requested shell command. /// ///
/// @@ -19234,12 +22622,12 @@ pub struct SessionPermissionsFolderTrustIsTrustedResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPermissionsFolderTrustAddTrustedResult { - /// Whether the operation succeeded - pub success: bool, +pub struct SessionShellCancelUserRequestedResult { + /// Whether an in-flight execution was found and signalled to cancel + pub cancelled: bool, } -/// Indicates whether the operation succeeded. +/// Compaction outcome with the number of tokens and messages removed, summary text, and the resulting context window breakdown. /// ///
/// @@ -19249,12 +22637,22 @@ pub struct SessionPermissionsFolderTrustAddTrustedResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionPermissionsUrlsSetUnrestrictedModeResult { - /// Whether the operation succeeded +pub struct SessionHistoryCompactResult { + /// Post-compaction context window usage breakdown + #[serde(skip_serializing_if = "Option::is_none")] + pub context_window: Option, + /// Number of messages removed during compaction + pub messages_removed: i64, + /// Whether compaction completed successfully pub success: bool, + /// Summary text produced by compaction. Omitted when compaction did not produce a summary (e.g. failure path). + #[serde(skip_serializing_if = "Option::is_none")] + pub summary_content: Option, + /// Number of tokens freed by compaction + pub tokens_removed: i64, } -/// Identifier of the session event that was emitted for the log message. +/// Number of events that were removed by the truncation. /// ///
/// @@ -19264,9 +22662,15 @@ pub struct SessionPermissionsUrlsSetUnrestrictedModeResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionLogResult { - /// The unique identifier of the emitted session event - pub event_id: String, +pub struct SessionHistoryTruncateResult { + /// Failure detail when checkpointCleanupFailed is true. + #[serde(skip_serializing_if = "Option::is_none")] + pub checkpoint_cleanup_error: Option, + /// True when conversation truncation succeeded but post-truncation workspace checkpoint cleanup failed. History is already truncated; callers may still prune snapshots but should report a checkpoint-cleanup rather than a truncation failure. + #[serde(skip_serializing_if = "Option::is_none")] + pub checkpoint_cleanup_failed: Option, + /// Number of events that were removed + pub events_removed: i64, } /// Identifies the target session. @@ -19279,47 +22683,12 @@ pub struct SessionLogResult { /// #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMetadataSnapshotParams { +pub struct SessionHistoryListRewindPointsParams { /// Target session identifier pub session_id: SessionId, } -/// Public-facing projection of workspace metadata for SDK / TUI consumers -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionMetadataSnapshotResultWorkspace { - /// Branch checked out at session start, if any - #[serde(skip_serializing_if = "Option::is_none")] - pub branch: Option, - /// ISO 8601 timestamp when the workspace was created - #[serde(rename = "created_at", skip_serializing_if = "Option::is_none")] - pub created_at: Option, - /// Current working directory at session start - #[serde(skip_serializing_if = "Option::is_none")] - pub cwd: Option, - /// Resolved git root for cwd, if any - #[serde(rename = "git_root", skip_serializing_if = "Option::is_none")] - pub git_root: Option, - /// Repository host type, if known - #[serde(rename = "host_type", skip_serializing_if = "Option::is_none")] - pub host_type: Option, - /// Workspace identifier (1:1 with sessionId) - pub id: String, - /// Display name for the session, if set - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, - /// Repository identifier in 'owner/repo' or 'org/project/repo' format, if any - #[serde(skip_serializing_if = "Option::is_none")] - pub repository: Option, - /// ISO 8601 timestamp when the workspace was last updated - #[serde(rename = "updated_at", skip_serializing_if = "Option::is_none")] - pub updated_at: Option, - /// Whether the display name was explicitly set by the user - #[serde(rename = "user_named", skip_serializing_if = "Option::is_none")] - pub user_named: Option, -} - -/// Point-in-time snapshot of slow-changing session identifier and state fields +/// Rewind points and file-change-tracking availability for the session. /// ///
/// @@ -19329,45 +22698,17 @@ pub struct SessionMetadataSnapshotResultWorkspace { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMetadataSnapshotResult { - /// True when the session was detected to be in use by another process at construction time. Local consumers may surface a confirmation prompt before fully attaching. Always false for new sessions. - pub already_in_use: bool, - /// Runtime client name associated with the session (telemetry identifier). - #[serde(skip_serializing_if = "Option::is_none")] - pub client_name: Option, - /// The current agent mode for this session (e.g., 'interactive', 'plan', 'autopilot') - pub current_mode: MetadataSnapshotCurrentMode, - /// User-provided name supplied at session construction (via `--name`), if any. Immutable after construction. - #[serde(skip_serializing_if = "Option::is_none")] - pub initial_name: Option, - /// Whether this is a remote session (i.e., one whose runtime executes elsewhere and is steered through this process) - pub is_remote: bool, - /// ISO 8601 timestamp of when the session's persisted state was last modified on disk. For new sessions, equals startTime. For resumed sessions, reflects the previous modification time at construction. - pub modified_time: String, - /// Remote-session-specific metadata. Populated only when `isRemote` is true. Fields are immutable for the lifetime of the session. +pub struct SessionHistoryListRewindPointsResult { + /// Whether this session captured file changes from its first turn. + pub file_change_tracking_enabled: bool, + /// Root user turns in chronological order. Empty when `unavailableReason` is set. + pub points: Vec, + /// Why the listed points could not be produced, when applicable; the points list is empty whenever it is set. `unsupported-remote-session` is permanent for the session and comes with `fileChangeTrackingEnabled: false`. `session-busy` is transient and only ever reported by a session that *is* tracking (`fileChangeTrackingEnabled: true`), because the file-change captures cannot be read while work that may still mutate them is in flight; the same request succeeds once the session settles, so a client that wants points should retry rather than treat it as a failure. It is never `file-change-tracking-disabled`: an untracked local session still lists conversation-only points and reports that through `fileChangeTrackingEnabled: false`. #[serde(skip_serializing_if = "Option::is_none")] - pub remote_metadata: Option, - /// Currently selected model identifier, if any - #[serde(skip_serializing_if = "Option::is_none")] - pub selected_model: Option, - /// The unique identifier of the session - pub session_id: SessionId, - /// Current session limits, or null when no limits are active - pub session_limits: Option, - /// ISO 8601 timestamp of when the session started - pub start_time: String, - /// Short human-readable summary of the session, if known. Omitted when no summary has been generated. - #[serde(skip_serializing_if = "Option::is_none")] - pub summary: Option, - /// Absolute path to the session's current working directory - pub working_directory: String, - /// Public-facing workspace metadata for this session, or null if the session has no associated workspace. Excludes runtime-internal fields (GitHub IDs, summary count, internal flags). - pub workspace: Option, - /// Absolute path to the session's workspace directory on disk, or null if the session has no associated workspace - pub workspace_path: Option, + pub unavailable_reason: Option, } -/// Identifies the target session. +/// Files and aggregate changes for a prospective rewind. /// ///
/// @@ -19377,12 +22718,19 @@ pub struct SessionMetadataSnapshotResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMetadataIsProcessingParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionHistoryPreviewRewindResult { + /// Whether file restore is available for this session. This is authoritative: switch on it and read `reason` only when it is false. + pub available: bool, + /// Number of unique files in the preview. + pub file_count: i64, + /// Files ordered by path. + pub files: Vec, + /// Why file restore is unavailable, when applicable. Populated only when `available` is false and never set when `available` is true. + #[serde(skip_serializing_if = "Option::is_none")] + pub reason: Option, } -/// Indicates whether the local session is currently processing a turn or background continuation. +/// Structured outcome of a rewind request. /// ///
/// @@ -19392,9 +22740,19 @@ pub struct SessionMetadataIsProcessingParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMetadataIsProcessingResult { - /// Whether the session is currently processing user/agent messages. False for non-local sessions (which don't run a local agentic loop). Reflects an in-flight turn or background continuation. - pub processing: bool, +pub struct SessionHistoryRewindResult { + /// Failure detail. Set only for the failure and partial-failure outcomes (`files-rolled-back`, `rollback-incomplete`, `truncation-failed`, `checkpoint-cleanup-failed`, `snapshot-prune-failed`); omitted for `success` and for the unavailable outcomes (`session-busy`, `file-change-tracking-disabled`, `unsupported-remote-session`). + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + /// Number of persisted events removed by conversation truncation. Present only when truncation succeeded (outcomes `success`, `checkpoint-cleanup-failed`, and `snapshot-prune-failed`); omitted for every unavailable outcome (`session-busy`, `file-change-tracking-disabled`, `unsupported-remote-session`) and for `truncation-failed`, `files-rolled-back`, and `rollback-incomplete`. + #[serde(skip_serializing_if = "Option::is_none")] + pub events_removed: Option, + /// Overall rewind outcome. This discriminates the result: it governs which of the remaining fields are populated, so consumers must switch on it before reading `eventsRemoved`, `restoredFiles`, `skippedFiles`, or `error`. See each field for the outcomes that populate it. + pub outcome: HistoryRewindOutcome, + /// Absolute paths restored to their captured preimages. Always empty for conversation-only rewinds and for the unavailable outcomes (`session-busy`, `file-change-tracking-disabled`, `unsupported-remote-session`); only conversation-and-files outcomes that reached the file-restore stage populate it. + pub restored_files: Vec, + /// Captured files intentionally left unchanged. Always empty for conversation-only rewinds and for the unavailable outcomes (`session-busy`, `file-change-tracking-disabled`, `unsupported-remote-session`); only conversation-and-files outcomes that reached the file-restore stage populate it. + pub skipped_files: Vec, } /// Identifies the target session. @@ -19407,12 +22765,12 @@ pub struct SessionMetadataIsProcessingResult { /// #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMetadataActivityParams { +pub struct SessionHistoryCancelBackgroundCompactionParams { /// Target session identifier pub session_id: SessionId, } -/// Current activity flags for the session. +/// Indicates whether an in-progress background compaction was cancelled. /// ///
/// @@ -19422,40 +22780,12 @@ pub struct SessionMetadataActivityParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMetadataActivityResult { - /// Whether an in-flight operation can currently be aborted. - pub abortable: bool, - /// Whether the session currently has active work, including running turns or tasks. - pub has_active_work: bool, -} - -/// Token-usage breakdown for the session's current context window -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionMetadataContextInfoResultContextInfo { - /// Output reserve plus tokens after the buffer-exhaustion blocking threshold (default 95%) - pub buffer_tokens: i64, - /// Token count at which background compaction starts (configurable percentage of promptTokenLimit) - pub compaction_threshold: i64, - /// Tokens consumed by user/assistant/tool messages - pub conversation_tokens: i64, - /// Prompt token limit plus the model's full output token limit. - pub limit: i64, - /// Tokens consumed by MCP tool definitions (subset of toolDefinitionsTokens, excludes deferred tools) - pub mcp_tools_tokens: i64, - /// The model used for token counting - pub model_name: String, - /// Maximum prompt tokens allowed by the model (or DEFAULT_TOKEN_LIMIT if unspecified) - pub prompt_token_limit: i64, - /// Tokens consumed by the system prompt - pub system_tokens: i64, - /// Tokens consumed by tool definitions sent to the model (excludes deferred tools) - pub tool_definitions_tokens: i64, - /// Sum of system, conversation and tool-definition tokens - pub total_tokens: i64, +pub struct SessionHistoryCancelBackgroundCompactionResult { + /// Whether an in-progress background compaction was cancelled. False when no compaction was running, when the session is remote, or when the underlying processor was unavailable. + pub cancelled: bool, } -/// Token breakdown for the session's current context window, or null if uninitialized. +/// Identifies the target session. /// ///
/// @@ -19465,12 +22795,12 @@ pub struct SessionMetadataContextInfoResultContextInfo { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMetadataContextInfoResult { - /// Token breakdown for the current context window, or null if the session has not yet been initialized (no system prompt or tool metadata cached). - pub context_info: Option, +pub struct SessionHistoryAbortManualCompactionParams { + /// Target session identifier + pub session_id: SessionId, } -/// Identifies the target session. +/// Indicates whether an in-progress manual compaction was aborted. /// ///
/// @@ -19480,51 +22810,12 @@ pub struct SessionMetadataContextInfoResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMetadataGetContextAttributionParams { - /// Target session identifier - pub session_id: SessionId, -} - -/// Successful compaction history for the session. -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionMetadataGetContextAttributionResultContextAttributionCompactions { - /// Number of successful compactions in this session. - pub count: i64, -} - -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionMetadataGetContextAttributionResultContextAttributionEntriesItem { - /// Supplementary per-entry metadata (e.g. `messageCount`, `role`, `evictable`, `pluginSource`). Values are stringified; parse as needed and ignore unrecognized keys. - #[serde(skip_serializing_if = "Option::is_none")] - pub attributes: Option>, - /// Identifier for this entry, formed by joining its `kind` and source name (e.g. `tool:bash`, `skill:tmux`, `toolDefinition:bash`); unique within the snapshot. Use it to match the same entry across snapshots, to correlate with other APIs (skill/agent/MCP registries), and as the `parentId` target for nesting. Distinct from the human-facing `label`. - pub id: String, - /// Source category for this entry. Not a closed set — tolerate unknown values. Known values today: `skill`, `subagent`, `mcpServer`, `tool`, `system`, `toolDefinition`, `plugin`. - pub kind: String, - /// Human-readable display label, e.g. `bash` or `skill: tmux`. Presentation-only; may be localized/reformatted without notice — do not key off it. - pub label: String, - /// Optional `id` of the parent entry: e.g. a `plugin` entry parenting its `skill`/`mcpServer` entries, or the `system` entry parenting `toolDefinition` entries. Omitted for top-level entries. - #[serde(skip_serializing_if = "Option::is_none")] - pub parent_id: Option, - /// Token count currently in context attributable to this entry. - pub tokens: i64, -} - -/// Per-source token attribution snapshot for the current context window. The heaviest individual messages are available separately via `metadata.getContextHeaviestMessages`. -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionMetadataGetContextAttributionResultContextAttribution { - /// Successful compaction history for the session. - pub compactions: SessionMetadataGetContextAttributionResultContextAttributionCompactions, - /// Flat list of per-source attribution entries. Group by `kind` and render unrecognized kinds generically. Nesting and rollups are expressed via `parentId`. - pub entries: Vec, - /// Total token count of the current context window the entries are measured against (system message + conversation messages + tool definitions — the same total reported by /context). Divide an entry's `tokens` by this to derive its share. - pub total_tokens: i64, +pub struct SessionHistoryAbortManualCompactionResult { + /// Whether an in-progress manual compaction was aborted. False when no manual compaction was running, when its abort controller was already aborted, or when the session is remote. + pub aborted: bool, } -/// Per-source attribution breakdown for the session's current context window, or null if uninitialized. +/// Identifies the target session. /// ///
/// @@ -19534,12 +22825,12 @@ pub struct SessionMetadataGetContextAttributionResultContextAttribution { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMetadataGetContextAttributionResult { - /// Per-source context-window attribution, or null if the session has not yet been initialized (no system prompt or tool metadata cached). - pub context_attribution: Option, +pub struct SessionHistorySummarizeForHandoffParams { + /// Target session identifier + pub session_id: SessionId, } -/// The heaviest individual messages in the session's context window, most-expensive first. +/// Markdown summary of the conversation context (empty when not available). /// ///
/// @@ -19549,14 +22840,12 @@ pub struct SessionMetadataGetContextAttributionResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMetadataGetContextHeaviestMessagesResult { - /// Heaviest messages, most-expensive first. - pub messages: Vec, - /// Total token count of the current context window, so callers can compute each message's share without a second call. - pub total_tokens: i64, +pub struct SessionHistorySummarizeForHandoffResult { + /// Markdown summary of the conversation context produced by an LLM. Empty string when there are no messages or when the session does not support local summarization. + pub summary: String, } -/// Notify the session that its working directory context has changed. Emits a `session.context_changed` event so consumers (telemetry, OTel tracker, ACP, the timeline UI) can react. Use this when the host has detected a cwd/branch/repo change outside the session's normal lifecycle (e.g., after a shell command in interactive mode). For a local session, a report whose `cwd` diverges from the session's current working directory is ignored (the call still succeeds but records nothing and emits no event); move a local session's working directory via `metadata.setWorkingDirectory` instead. +/// What a successful clear removed. A clear that could not be applied rejects instead of reporting a count. /// ///
/// @@ -19566,9 +22855,12 @@ pub struct SessionMetadataGetContextHeaviestMessagesResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMetadataRecordContextChangeResult {} +pub struct SessionHistoryClearContextResult { + /// Number of non-system, non-developer messages that were removed from the conversation. Zero only when the window already held no conversation. + pub messages_cleared: i64, +} -/// Update the session's working directory. Used by the host when the user explicitly changes cwd (e.g., the `/cd` slash command). The host is responsible for any related side-effects (file index, etc.); it does NOT change the process working directory (a session's cwd is per-session, not process-global). For local sessions the runtime validates the target first (an absolute path that exists on disk) and re-bases the permission primary directory; a rejected validation fails the call before anything is mutated, persisted, or emitted. Location-scoped permission rules are then re-keyed to the new directory (best-effort). Remote sessions only record the path. +/// Identifies the target session. /// ///
/// @@ -19578,12 +22870,12 @@ pub struct SessionMetadataRecordContextChangeResult {} ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMetadataSetWorkingDirectoryResult { - /// Working directory after the update - pub working_directory: String, +pub struct SessionQueuePendingItemsParams { + /// Target session identifier + pub session_id: SessionId, } -/// Re-tokenize the session's existing messages against `modelId` and return the token totals. Useful for hosts that want an initial estimate of context usage on session resume, before the next agent turn fires `session.context_info_changed` events. Returns zeros for an empty session. +/// Snapshot of the session's pending queued items and immediate-steering messages. /// ///
/// @@ -19593,13 +22885,11 @@ pub struct SessionMetadataSetWorkingDirectoryResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionMetadataRecomputeContextTokensResult { - /// Tokens contributed by user/assistant/tool messages (excludes system/developer prompts). - pub messages_token_count: i64, - /// Tokens contributed by system/developer prompt snapshots. - pub system_token_count: i64, - /// Sum of tokens across chat-context and system-context messages currently held by the session. - pub total_tokens: i64, +pub struct SessionQueuePendingItemsResult { + /// Pending queued items in submission order. Includes user messages, queued slash commands, and queued model changes; omits internal system items. + pub items: Vec, + /// Display text for messages currently in the immediate steering queue (interjections sent during a running turn). + pub steering_messages: Vec, } /// Identifies the target session. @@ -19612,12 +22902,12 @@ pub struct SessionMetadataRecomputeContextTokensResult { /// #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionSettingsSnapshotParams { +pub struct SessionQueueSnapshotParams { /// Target session identifier pub session_id: SessionId, } -/// Redacted, serializable view of session runtime settings for SDK boundary consumers. Secrets and raw feature flags are intentionally excluded. +/// Internal snapshot of native queue state for local session orchestration. /// ///
/// @@ -19627,23 +22917,20 @@ pub struct SessionSettingsSnapshotParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionSettingsSnapshotResult { - #[serde(skip_serializing_if = "Option::is_none")] - pub client_name: Option, - pub job: SessionSettingsJobSnapshot, - pub model: SessionSettingsModelSnapshot, - pub online_evaluation: SessionSettingsOnlineEvaluationSnapshot, - pub repo: SessionSettingsRepoSnapshot, +pub struct SessionQueueSnapshotResult { + /// Insertion orders for queued items, aligned with `items`. #[serde(skip_serializing_if = "Option::is_none")] - pub start_time_ms: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub timeout_ms: Option, - pub validation: SessionSettingsValidationSnapshot, + pub item_orders: Option>, + /// User-facing pending items in FIFO order. + pub items: Vec, + /// Insertion orders for immediate steering messages, aligned with `steeringMessages`. #[serde(skip_serializing_if = "Option::is_none")] - pub version: Option, + pub steering_message_orders: Option>, + /// Immediate steering messages waiting for an active turn. + pub steering_messages: Vec, } -/// Identifier of the spawned process, used to correlate streamed output and exit notifications. +/// Result of moving a queued item. /// ///
/// @@ -19653,12 +22940,12 @@ pub struct SessionSettingsSnapshotResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionShellExecResult { - /// Unique identifier for tracking streamed output - pub process_id: String, +pub struct SessionQueueMoveItemResult { + /// True when the item changed position; false when it was already at the requested position. + pub changed: bool, } -/// Indicates whether the signal was delivered; false if the process was unknown or already exited. +/// Result of inserting a queued message. /// ///
/// @@ -19668,12 +22955,12 @@ pub struct SessionShellExecResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionShellKillResult { - /// Whether the signal was sent successfully - pub killed: bool, +pub struct SessionQueueInsertAtResult { + /// Fresh stable opaque id assigned to the inserted item. + pub id: String, } -/// Result of a user-requested shell command. +/// Result of removing a queued item. /// ///
/// @@ -19683,22 +22970,12 @@ pub struct SessionShellKillResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionShellExecuteUserRequestedResult { - /// Error output when the execution failed - #[serde(skip_serializing_if = "Option::is_none")] - pub error: Option, - /// Process exit code, when available - #[serde(skip_serializing_if = "Option::is_none")] - pub exit_code: Option, - /// Captured command output - pub output: String, - /// Whether the command completed successfully - pub success: bool, - /// Tool call id emitted for the shell execution - pub tool_call_id: String, +pub struct SessionQueueRemoveAtResult { + /// True when the addressed item was removed. + pub removed: bool, } -/// Cancellation result for a user-requested shell command. +/// Result of editing a queued message. /// ///
/// @@ -19708,12 +22985,12 @@ pub struct SessionShellExecuteUserRequestedResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionShellCancelUserRequestedResult { - /// Whether an in-flight execution was found and signalled to cancel - pub cancelled: bool, +pub struct SessionQueueUpdateTextResult { + /// True when the stored text changed. + pub updated: bool, } -/// Compaction outcome with the number of tokens and messages removed, summary text, and the resulting context window breakdown. +/// Result of duplicating a queued item. /// ///
/// @@ -19723,22 +23000,12 @@ pub struct SessionShellCancelUserRequestedResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionHistoryCompactResult { - /// Post-compaction context window usage breakdown - #[serde(skip_serializing_if = "Option::is_none")] - pub context_window: Option, - /// Number of messages removed during compaction - pub messages_removed: i64, - /// Whether compaction completed successfully - pub success: bool, - /// Summary text produced by compaction. Omitted when compaction did not produce a summary (e.g. failure path). - #[serde(skip_serializing_if = "Option::is_none")] - pub summary_content: Option, - /// Number of tokens freed by compaction - pub tokens_removed: i64, +pub struct SessionQueueDuplicateAtResult { + /// Fresh stable opaque id assigned to the duplicate. + pub id: String, } -/// Number of events that were removed by the truncation. +/// Result of trying to steer a queued message into a live turn. /// ///
/// @@ -19748,9 +23015,9 @@ pub struct SessionHistoryCompactResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionHistoryTruncateResult { - /// Number of events that were removed - pub events_removed: i64, +pub struct SessionQueueSendNowResult { + /// True when the item was accepted into the steering lane; false when no main turn was live. + pub steered: bool, } /// Identifies the target session. @@ -19763,12 +23030,12 @@ pub struct SessionHistoryTruncateResult { /// #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionHistoryCancelBackgroundCompactionParams { +pub struct SessionQueueHasPendingParams { /// Target session identifier pub session_id: SessionId, } -/// Indicates whether an in-progress background compaction was cancelled. +/// Whether the native queue has pending work. /// ///
/// @@ -19778,12 +23045,12 @@ pub struct SessionHistoryCancelBackgroundCompactionParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionHistoryCancelBackgroundCompactionResult { - /// Whether an in-progress background compaction was cancelled. False when no compaction was running, when the session is remote, or when the underlying processor was unavailable. - pub cancelled: bool, +pub struct SessionQueueHasPendingResult { + /// True when queued or immediate native work is pending. + pub has_pending: bool, } -/// Identifies the target session. +/// Whether a deferred-idle drain should run. /// ///
/// @@ -19793,12 +23060,12 @@ pub struct SessionHistoryCancelBackgroundCompactionResult { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionHistoryAbortManualCompactionParams { - /// Target session identifier - pub session_id: SessionId, +pub struct SessionQueueBeginDeferredIdleDrainResult { + /// True when the host should run finishDeferredIdleDrain asynchronously. + pub should_drain: bool, } -/// Indicates whether an in-progress manual compaction was aborted. +/// Action selected by the native deferred-idle drain. /// ///
/// @@ -19808,9 +23075,11 @@ pub struct SessionHistoryAbortManualCompactionParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionHistoryAbortManualCompactionResult { - /// Whether an in-progress manual compaction was aborted. False when no manual compaction was running, when its abort controller was already aborted, or when the session is remote. +pub struct SessionQueueFinishDeferredIdleDrainResult { + /// Whether the deferred idle was caused by an aborted foreground turn. pub aborted: bool, + /// One of none, processQueue, or emitSessionIdle. + pub action: String, } /// Identifies the target session. @@ -19823,12 +23092,12 @@ pub struct SessionHistoryAbortManualCompactionResult { /// #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionHistorySummarizeForHandoffParams { +pub struct SessionQueueRemoveMostRecentParams { /// Target session identifier pub session_id: SessionId, } -/// Markdown summary of the conversation context (empty when not available). +/// Indicates whether a user-facing pending item was removed. /// ///
/// @@ -19838,9 +23107,9 @@ pub struct SessionHistorySummarizeForHandoffParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionHistorySummarizeForHandoffResult { - /// Markdown summary of the conversation context produced by an LLM. Empty string when there are no messages or when the session does not support local summarization. - pub summary: String, +pub struct SessionQueueRemoveMostRecentResult { + /// True if a user-facing pending item was removed (LIFO across both queues); false when no removable items remained. + pub removed: bool, } /// Identifies the target session. @@ -19853,12 +23122,12 @@ pub struct SessionHistorySummarizeForHandoffResult { /// #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionQueuePendingItemsParams { +pub struct SessionQueueClearParams { /// Target session identifier pub session_id: SessionId, } -/// Snapshot of the session's pending queued items and immediate-steering messages. +/// Indicates whether a user-facing pending item was removed. /// ///
/// @@ -19868,11 +23137,9 @@ pub struct SessionQueuePendingItemsParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionQueuePendingItemsResult { - /// Pending queued items in submission order. Includes user messages, queued slash commands, and queued model changes; omits internal system items. - pub items: Vec, - /// Display text for messages currently in the immediate steering queue (interjections sent during a running turn). - pub steering_messages: Vec, +pub struct SessionQueueConsumeSystemNotificationsResult { + /// True if a user-facing pending item was removed (LIFO across both queues); false when no removable items remained. + pub removed: bool, } /// Identifies the target session. @@ -19885,12 +23152,12 @@ pub struct SessionQueuePendingItemsResult { /// #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionQueueRemoveMostRecentParams { +pub struct SessionQueueEnqueueResumePendingParams { /// Target session identifier pub session_id: SessionId, } -/// Indicates whether a user-facing pending item was removed. +/// Result of enqueueing the resume-pending wake item. /// ///
/// @@ -19900,9 +23167,9 @@ pub struct SessionQueueRemoveMostRecentParams { ///
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionQueueRemoveMostRecentResult { - /// True if a user-facing pending item was removed (LIFO across both queues); false when no removable items remained. - pub removed: bool, +pub struct SessionQueueEnqueueResumePendingResult { + /// True when a wake item was newly queued. + pub queued: bool, } /// Identifies the target session. @@ -19915,7 +23182,7 @@ pub struct SessionQueueRemoveMostRecentResult { /// #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct SessionQueueClearParams { +pub struct SessionQueueProcessParams { /// Target session identifier pub session_id: SessionId, } @@ -19931,13 +23198,13 @@ pub struct SessionQueueClearParams { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionEventLogReadResult { - /// Opaque cursor for the next read. Pass back unchanged in the next read.cursor to continue from where this read left off. Always present, even when no events were returned. + /// Opaque cursor for the next read. Pass back unchanged in the next read.cursor to continue from where this read left off. Always present, even when no events were returned. For a backward read this cursor pages toward OLDER events; keep passing `direction: backward` with it (the cursor is also self-describing, so backward paging continues correctly). pub cursor: String, - /// Cursor status: 'ok' means the cursor was applied successfully; 'expired' means the cursor referred to an event that no longer exists in history (e.g. truncated or compacted away) and the read started from the beginning of the remaining history. + /// Cursor status: 'ok' means the cursor was applied successfully; 'expired' means the cursor referred to an event that no longer exists in history (e.g. truncated or compacted away) and the read fell back to a boundary of the remaining history. For a forward read the fallback starts from the beginning of the remaining history; for a backward read it falls back to the tail (the newest window). Because the fallback page is a fresh boundary snapshot rather than a continuation of the requested cursor, it may overlap events the consumer has already rendered — a backward fallback to the tail in particular can repeat the newest window. On 'expired', consumers should reset or rebase their local pagination state (or deduplicate by event id) before continuing from the returned cursor rather than blindly appending/prepending the fallback page. pub cursor_status: EventsCursorStatus, - /// Events are delivered in two batches per read: persisted events first (in append order), then ephemeral events (in seq order). When `waitMs > 0` and the catch-up batches were empty, post-wait events follow the same two-batch ordering. Persisted and ephemeral events do not interleave within a single read. + /// Session events for this batch, merged into a single stream in creation order: durable (persisted) events and ephemeral events interleave exactly as they were emitted. Set `includeEphemeral: false` to receive only durable events. Ephemeral events are never replayable once pruned from the in-memory ring, so a consumer that needs them should keep reading with a non-zero `waitMs`. For a backward (tail-first) read, the returned window contains persisted events only, still in chronological (oldest-to-newest) append order. pub events: Vec, - /// True when the read returned `max` events and more events are available immediately. When false, the next read with a non-zero `waitMs` will block until a new event arrives or the wait expires. + /// True when more events are available in the read's direction. For a forward read, true means the batch returned `max` events and more are available immediately. For a backward read, true means older persisted events remain before the returned window. pub has_more: bool, } @@ -20186,6 +23453,146 @@ pub struct SessionScheduleListResult { pub entries: Vec, } +/// Identifies the target session. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionScheduleHydrateParams { + /// Target session identifier + pub session_id: SessionId, +} + +/// Identifies the target session. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionScheduleHasSelfPacedParams { + /// Target session identifier + pub session_id: SessionId, +} + +/// Whether the session currently has an active self-paced schedule. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionScheduleHasSelfPacedResult { + /// True when at least one active schedule is self-paced. + pub has_self_paced: bool, +} + +/// Result of registering or re-arming a scheduled prompt. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionScheduleAddResult { + /// The registered or updated schedule entry. + #[serde(skip_serializing_if = "Option::is_none")] + pub entry: Option, + /// User-facing validation error, when registration failed. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, +} + +/// Result of registering or re-arming a scheduled prompt. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionScheduleAddCronResult { + /// The registered or updated schedule entry. + #[serde(skip_serializing_if = "Option::is_none")] + pub entry: Option, + /// User-facing validation error, when registration failed. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, +} + +/// Result of registering or re-arming a scheduled prompt. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionScheduleAddAtResult { + /// The registered or updated schedule entry. + #[serde(skip_serializing_if = "Option::is_none")] + pub entry: Option, + /// User-facing validation error, when registration failed. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, +} + +/// Result of registering or re-arming a scheduled prompt. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionScheduleAddSelfPacedResult { + /// The registered or updated schedule entry. + #[serde(skip_serializing_if = "Option::is_none")] + pub entry: Option, + /// User-facing validation error, when registration failed. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, +} + +/// Result of registering or re-arming a scheduled prompt. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionScheduleRearmSelfPacedResult { + /// The registered or updated schedule entry. + #[serde(skip_serializing_if = "Option::is_none")] + pub entry: Option, + /// User-facing validation error, when registration failed. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, +} + /// Remove a scheduled prompt by id. The result entry is omitted if the id was unknown. /// ///
@@ -20306,6 +23713,16 @@ pub type UIElicitationResponseContent = HashMap; ///
pub type AccountGetAllUsersResult = Vec; +/// The number of running background agents (task-registry agents) that were cancelled. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+pub type SessionCancelAllBackgroundAgentsResult = i64; + /// Standard MCP CallToolResult /// ///
@@ -21098,7 +24515,68 @@ pub enum DebugCollectLogsRedaction { Unknown, } -/// Destination kind that was written. +/// Destination kind that was written. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum DebugCollectLogsResultKind { + /// A .tgz archive was written. + #[serde(rename = "archive")] + Archive, + /// A directory containing redacted files was written. + #[serde(rename = "directory")] + Directory, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum DisableBypassPermissionsMode { + #[serde(rename = "disable")] + Disable, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Persisted extension discovery source +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum DiscoveredExtensionSource { + /// Extension discovered from the user's extensions directory. + #[serde(rename = "user")] + User, + /// Extension contributed by an installed plugin. + #[serde(rename = "plugin")] + Plugin, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Effective extension loading and agent-management mode /// ///
/// @@ -21107,13 +24585,16 @@ pub enum DebugCollectLogsRedaction { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum DebugCollectLogsResultKind { - /// A .tgz archive was written. - #[serde(rename = "archive")] - Archive, - /// A directory containing redacted files was written. - #[serde(rename = "directory")] - Directory, +pub enum DiscoveredExtensionMode { + /// Extensions are not loaded. + #[serde(rename = "disabled")] + Disabled, + /// Extensions are loaded, but the agent cannot create, reload, or manage them. + #[serde(rename = "load_only")] + LoadOnly, + /// Extensions are loaded and the agent can create, reload, and manage them. + #[serde(rename = "load_and_augment")] + LoadAndAugment, /// Unknown variant for forward compatibility. #[default] #[serde(other)] @@ -21178,7 +24659,29 @@ pub enum EventsAgentScope { Unknown, } -/// Cursor status: 'ok' means the cursor was applied successfully; 'expired' means the cursor referred to an event that no longer exists in history (e.g. truncated or compacted away) and the read started from the beginning of the remaining history. +/// Direction to page through the session's persisted event history. 'forward' pages from the cursor toward newer events; 'backward' returns the newest window first (tail-first) and pages toward older events. Events within a returned batch are always chronological (oldest-to-newest), even for a backward read. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum EventsReadDirection { + /// Page from the cursor toward newer events (default). + #[serde(rename = "forward")] + Forward, + /// Tail-first: return the newest events and page toward older events. + #[serde(rename = "backward")] + Backward, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Cursor status: 'ok' means the cursor was applied successfully; 'expired' means the cursor referred to an event that no longer exists in history (e.g. truncated or compacted away) and the read fell back to a boundary of the remaining history (the beginning for a forward read, the tail for a backward read). The fallback page is a fresh boundary snapshot, not a continuation of the requested cursor, so it may overlap already-rendered events; on 'expired' a consumer should reset/rebase its pagination state (or deduplicate by event id) before continuing from the returned cursor. /// ///
/// @@ -21364,6 +24867,86 @@ pub enum ExternalToolTextResultForLlmContentTextType { Text, } +/// Execution-critical factory storage operation. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum FactoryDurableOperation { + /// Creating the durable run and declared phases. + #[serde(rename = "createRun")] + CreateRun, + /// Persisting the transition to running. + #[serde(rename = "markRunStarted")] + MarkRunStarted, + /// Persisting the terminal run envelope. + #[serde(rename = "finishRun")] + FinishRun, + /// Persisting subagent admission accounting. + #[serde(rename = "reserveAgent")] + ReserveAgent, + /// Rolling back an uncommitted subagent admission. + #[serde(rename = "releaseAgent")] + ReleaseAgent, + /// Persisting an idempotent model-usage charge. + #[serde(rename = "chargeCredit")] + ChargeCredit, + /// Persisting active execution time. + #[serde(rename = "addElapsed")] + AddElapsed, + /// Reading the authoritative AI-credit total. + #[serde(rename = "reconcileCreditTotal")] + ReconcileCreditTotal, + /// Reading a journal entry without treating storage failure as a cache miss. + #[serde(rename = "journalGet")] + JournalGet, + /// Persisting a journal entry before reporting success. + #[serde(rename = "journalPut")] + JournalPut, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Current or terminal state of a factory run. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum FactoryRunStatus { + /// The run was minted and is awaiting approval. + #[serde(rename = "pending")] + Pending, + /// The run is executing. + #[serde(rename = "running")] + Running, + /// The run completed successfully. + #[serde(rename = "completed")] + Completed, + /// The run was interrupted while resource budget remained. + #[serde(rename = "halted")] + Halted, + /// The run was cancelled before completion. + #[serde(rename = "cancelled")] + Cancelled, + /// The factory body failed or reached a cumulative resource ceiling. + #[serde(rename = "error")] + Error, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// Kind of factory progress line. /// ///
@@ -21386,6 +24969,34 @@ pub enum FactoryLogLineKind { Unknown, } +/// Derived lifecycle state of a factory phase. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum FactoryPhaseStatus { + /// The phase has not been entered yet. + #[serde(rename = "pending")] + Pending, + /// The phase is currently entered and accumulating active time. + #[serde(rename = "active")] + Active, + /// The phase was entered and has since been closed. + #[serde(rename = "completed")] + Completed, + /// The phase was never entered because a later phase was entered or the run reached a terminal state. + #[serde(rename = "skipped")] + Skipped, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// Cumulative resource ceiling that stopped a factory run. /// ///
@@ -21399,16 +25010,42 @@ pub enum FactoryRunFailureKind { /// The run admitted the approved maximum total number of subagents. #[serde(rename = "maxTotalSubagents")] MaxTotalSubagents, - /// The run reached the approved timeout deadline. - #[serde(rename = "timeout")] - Timeout, + /// The run reached the approved accumulated active-execution time in seconds. + #[serde(rename = "timeoutSeconds")] + TimeoutSeconds, + /// The run's settled subagent model usage exceeded the approved AI-credit ceiling, or no headroom remained for another subagent. + #[serde(rename = "maxAiCredits")] + MaxAiCredits, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Current or terminal state of a factory run. +/// Authentication via the `gh` CLI's saved credentials. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum GhCliAuthInfoType { + #[serde(rename = "gh-cli")] + #[default] + GhCli, +} + +/// What initiated this compaction request, recorded as the `trigger` on the persisted `session.compaction_start` / `session.compaction_complete` events. When absent, the compaction is persisted without trigger attribution (initiator unknown). +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum HistoryCompactRequestTrigger { + /// User-requested compaction, e.g. the /compact command or a direct history.compact call. + #[serde(rename = "manual")] + Manual, + /// Compaction requested while switching to a model with a smaller context window. + #[serde(rename = "model_switch")] + ModelSwitch, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Reason a captured file was not restored. /// ///
/// @@ -21417,37 +25054,132 @@ pub enum FactoryRunFailureKind { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum FactoryRunStatus { - /// The run was minted and is awaiting approval. - #[serde(rename = "pending")] - Pending, - /// The run is executing. - #[serde(rename = "running")] - Running, - /// The run completed successfully. - #[serde(rename = "completed")] - Completed, - /// The run was interrupted while resource budget remained. - #[serde(rename = "halted")] - Halted, - /// The run was cancelled before completion. - #[serde(rename = "cancelled")] - Cancelled, - /// The factory body failed or reached a cumulative resource ceiling. - #[serde(rename = "error")] - Error, +pub enum HistoryFileRestoreSkipReason { + /// The file changed after Copilot's last captured write. + #[serde(rename = "user-modified")] + UserModified, + /// A faithful preimage was not captured. + #[serde(rename = "skipped-capture")] + SkippedCapture, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Authentication via the `gh` CLI's saved credentials. +/// Reason a rewind read (rewind points, file-restore preview, or session diff) could not be answered from the session's file-change captures. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum GhCliAuthInfoType { - #[serde(rename = "gh-cli")] +pub enum HistoryRewindUnavailableReason { + /// The session did not opt into file-change tracking before its first turn. + #[serde(rename = "file-change-tracking-disabled")] + FileChangeTrackingDisabled, + /// The session still has work that may mutate files or history. Transient: the same request succeeds once the session settles, so callers should retry rather than treat it as a failure. + #[serde(rename = "session-busy")] + SessionBusy, + /// Remote-backed rewind routing is not supported. + #[serde(rename = "unsupported-remote-session")] + UnsupportedRemoteSession, + /// Unknown variant for forward compatibility. #[default] - GhCli, + #[serde(other)] + Unknown, +} + +/// Aggregate file change represented by a rewind preview. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum HistoryRewindChangeType { + /// The discarded turns created the file. + #[serde(rename = "created")] + Created, + /// The discarded turns deleted the file. + #[serde(rename = "deleted")] + Deleted, + /// The discarded turns modified the file. + #[serde(rename = "modified")] + Modified, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Scope of a rewind operation. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum HistoryRewindMode { + /// Discard conversation events while leaving files unchanged. + #[serde(rename = "conversation")] + Conversation, + /// Discard conversation events and restore captured files changed by those turns. + #[serde(rename = "conversation-and-files")] + ConversationAndFiles, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Outcome of a rewind request. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum HistoryRewindOutcome { + /// The requested rewind completed; reachable in either mode. + #[serde(rename = "success")] + Success, + /// The session still has work that may mutate files or history; reachable in either mode. + #[serde(rename = "session-busy")] + SessionBusy, + /// A conversation-and-files rewind was requested for a session that did not enable capture; conversation-only rewinds never produce this. + #[serde(rename = "file-change-tracking-disabled")] + FileChangeTrackingDisabled, + /// Remote-backed rewind routing is not supported; reachable in either mode. + #[serde(rename = "unsupported-remote-session")] + UnsupportedRemoteSession, + /// File restore failed and all applied file changes were rolled back; only conversation-and-files rewinds produce this. + #[serde(rename = "files-rolled-back")] + FilesRolledBack, + /// File restore failed and its rollback could not fully restore the pre-rewind state; only conversation-and-files rewinds produce this. + #[serde(rename = "rollback-incomplete")] + RollbackIncomplete, + /// Conversation truncation failed. In conversation-and-files mode any files that were restored are left in place because conversation history cannot be un-truncated; in conversation-only mode no files are restored. Consult restoredFiles for what, if anything, was applied. + #[serde(rename = "truncation-failed")] + TruncationFailed, + /// The conversation was rewound (and, in conversation-and-files mode, captured files were restored), but persisted checkpoints could not be cleaned up; reachable in either mode. + #[serde(rename = "checkpoint-cleanup-failed")] + CheckpointCleanupFailed, + /// Files and conversation were rewound, but obsolete file snapshots could not be removed; only conversation-and-files rewinds produce this. + #[serde(rename = "snapshot-prune-failed")] + SnapshotPruneFailed, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, } /// Authentication host. HMAC auth always targets the public GitHub host. @@ -22537,6 +26269,14 @@ pub enum PermissionDecisionApproveForSessionApprovalExtensionManagementKind { ExtensionManagement, } +/// Approval covering factory operations. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum PermissionDecisionApproveForSessionApprovalFactoryKind { + #[serde(rename = "factory")] + #[default] + Factory, +} + /// Approval covering an extension's request to access a permission-gated capability. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum PermissionDecisionApproveForSessionApprovalExtensionPermissionAccessKind { @@ -22564,6 +26304,7 @@ pub enum PermissionDecisionApproveForSessionApproval { Memory(PermissionDecisionApproveForSessionApprovalMemory), CustomTool(PermissionDecisionApproveForSessionApprovalCustomTool), ExtensionManagement(PermissionDecisionApproveForSessionApprovalExtensionManagement), + Factory(PermissionDecisionApproveForSessionApprovalFactory), ExtensionPermissionAccess(PermissionDecisionApproveForSessionApprovalExtensionPermissionAccess), } @@ -22639,6 +26380,14 @@ pub enum PermissionDecisionApproveForLocationApprovalExtensionManagementKind { ExtensionManagement, } +/// Approval covering factory operations. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum PermissionDecisionApproveForLocationApprovalFactoryKind { + #[serde(rename = "factory")] + #[default] + Factory, +} + /// Approval covering an extension's request to access a permission-gated capability. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum PermissionDecisionApproveForLocationApprovalExtensionPermissionAccessKind { @@ -22666,6 +26415,7 @@ pub enum PermissionDecisionApproveForLocationApproval { Memory(PermissionDecisionApproveForLocationApprovalMemory), CustomTool(PermissionDecisionApproveForLocationApprovalCustomTool), ExtensionManagement(PermissionDecisionApproveForLocationApprovalExtensionManagement), + Factory(PermissionDecisionApproveForLocationApprovalFactory), ExtensionPermissionAccess( PermissionDecisionApproveForLocationApprovalExtensionPermissionAccess, ), @@ -22772,10 +26522,93 @@ pub enum PermissionDecisionDeniedByContentExclusionPolicyKind { pub enum PermissionDecisionDeniedByPermissionRequestHookKind { #[serde(rename = "denied-by-permission-request-hook")] #[default] - DeniedByPermissionRequestHook, + DeniedByPermissionRequestHook, +} + +/// The client's response to the pending permission prompt +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum PermissionDecision { + ApproveOnce(PermissionDecisionApproveOnce), + ApproveForSession(PermissionDecisionApproveForSession), + ApproveForLocation(PermissionDecisionApproveForLocation), + ApprovePermanently(PermissionDecisionApprovePermanently), + Reject(PermissionDecisionReject), + UserNotAvailable(PermissionDecisionUserNotAvailable), + Approved(PermissionDecisionApproved), + ApprovedForSession(PermissionDecisionApprovedForSession), + ApprovedForLocation(PermissionDecisionApprovedForLocation), + Cancelled(PermissionDecisionCancelled), + DeniedByRules(PermissionDecisionDeniedByRules), + DeniedNoApprovalRuleAndCouldNotRequestFromUser( + PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser, + ), + DeniedInteractivelyByUser(PermissionDecisionDeniedInteractivelyByUser), + DeniedByContentExclusionPolicy(PermissionDecisionDeniedByContentExclusionPolicy), + DeniedByPermissionRequestHook(PermissionDecisionDeniedByPermissionRequestHook), +} + +/// Disposition of a permission request as observed by the responding client. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum PermissionDecisionOutcome { + /// The request was approved automatically without a new human decision. + #[serde(rename = "auto_approved")] + AutoApproved, + /// The request was denied without an interactive user decision; source records why. + #[serde(rename = "autopilot_denied")] + AutopilotDenied, + /// The response came from an interactive user prompt. + #[serde(rename = "prompted_user")] + PromptedUser, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Controlled reason or actor responsible for a permission response. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum PermissionDecisionSource { + /// The response followed the auto-approval judge recommendation. + #[serde(rename = "judge_recommendation")] + JudgeRecommendation, + /// A human supplied the response through an interactive prompt. + #[serde(rename = "human_response")] + HumanResponse, + /// The host applied a standing policy or override rather than a judge recommendation or human decision. + #[serde(rename = "host_policy")] + HostPolicy, + /// The host denied the request because no interactive user response was available. + #[serde(rename = "unattended_fallback")] + UnattendedFallback, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, } -/// The client's response to the pending permission prompt +/// Client surface that submitted a permission response. /// ///
/// @@ -22783,26 +26616,24 @@ pub enum PermissionDecisionDeniedByPermissionRequestHookKind { /// and may change or be removed in future SDK or CLI releases. /// ///
-#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub enum PermissionDecision { - ApproveOnce(PermissionDecisionApproveOnce), - ApproveForSession(PermissionDecisionApproveForSession), - ApproveForLocation(PermissionDecisionApproveForLocation), - ApprovePermanently(PermissionDecisionApprovePermanently), - Reject(PermissionDecisionReject), - UserNotAvailable(PermissionDecisionUserNotAvailable), - Approved(PermissionDecisionApproved), - ApprovedForSession(PermissionDecisionApprovedForSession), - ApprovedForLocation(PermissionDecisionApprovedForLocation), - Cancelled(PermissionDecisionCancelled), - DeniedByRules(PermissionDecisionDeniedByRules), - DeniedNoApprovalRuleAndCouldNotRequestFromUser( - PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser, - ), - DeniedInteractivelyByUser(PermissionDecisionDeniedInteractivelyByUser), - DeniedByContentExclusionPolicy(PermissionDecisionDeniedByContentExclusionPolicy), - DeniedByPermissionRequestHook(PermissionDecisionDeniedByPermissionRequestHook), +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum PermissionDecisionSurface { + /// The interactive Copilot CLI terminal UI. + #[serde(rename = "tui")] + Tui, + /// The non-interactive Copilot CLI prompt mode. + #[serde(rename = "prompt_mode")] + PromptMode, + /// The Copilot App client. + #[serde(rename = "copilot_app")] + CopilotApp, + /// A generic Copilot SDK client. + #[serde(rename = "sdk")] + Sdk, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, } /// Approval scoped to specific command identifiers. @@ -22869,6 +26700,14 @@ pub enum PermissionsLocationsAddToolApprovalDetailsExtensionManagementKind { ExtensionManagement, } +/// Approval covering factory operations. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum PermissionsLocationsAddToolApprovalDetailsFactoryKind { + #[serde(rename = "factory")] + #[default] + Factory, +} + /// Approval covering an extension's request to access a permission-gated capability. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccessKind { @@ -22896,6 +26735,7 @@ pub enum PermissionsLocationsAddToolApprovalDetails { Memory(PermissionsLocationsAddToolApprovalDetailsMemory), CustomTool(PermissionsLocationsAddToolApprovalDetailsCustomTool), ExtensionManagement(PermissionsLocationsAddToolApprovalDetailsExtensionManagement), + Factory(PermissionsLocationsAddToolApprovalDetailsFactory), ExtensionPermissionAccess(PermissionsLocationsAddToolApprovalDetailsExtensionPermissionAccess), } @@ -23219,6 +27059,56 @@ pub enum PushAttachmentSelectionType { Selection, } +/// The UI mode the agent was in when this message was sent. Defaults to the session's current mode. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum SendAgentMode { + /// The agent is responding interactively to the user. + #[serde(rename = "interactive")] + Interactive, + /// The agent is preparing a plan before making changes. + #[serde(rename = "plan")] + Plan, + /// The agent is working autonomously toward task completion. + #[serde(rename = "autopilot")] + Autopilot, + /// The agent is in shell-focused UI mode. + #[serde(rename = "shell")] + Shell, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// How to deliver the message. `enqueue` (default) appends to the message queue. `immediate` interjects during an in-progress turn. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum SendMode { + /// Append the message to the normal session queue. + #[serde(rename = "enqueue")] + Enqueue, + /// Interject the message during the in-progress turn. + #[serde(rename = "immediate")] + Immediate, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// Whether this item is a queued user message or a queued slash command / model change /// ///
@@ -23320,56 +27210,6 @@ pub enum RemoteSessionMetadataTaskType { Unknown, } -/// The UI mode the agent was in when this message was sent. Defaults to the session's current mode. -/// -///
-/// -/// **Experimental.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
-#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum SendAgentMode { - /// The agent is responding interactively to the user. - #[serde(rename = "interactive")] - Interactive, - /// The agent is preparing a plan before making changes. - #[serde(rename = "plan")] - Plan, - /// The agent is working autonomously toward task completion. - #[serde(rename = "autopilot")] - Autopilot, - /// The agent is in shell-focused UI mode. - #[serde(rename = "shell")] - Shell, - /// Unknown variant for forward compatibility. - #[default] - #[serde(other)] - Unknown, -} - -/// How to deliver the message. `enqueue` (default) appends to the message queue. `immediate` interjects during an in-progress turn. -/// -///
-/// -/// **Experimental.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
-#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum SendMode { - /// Append the message to the normal session queue. - #[serde(rename = "enqueue")] - Enqueue, - /// Interject the message during the in-progress turn. - #[serde(rename = "immediate")] - Immediate, - /// Unknown variant for forward compatibility. - #[default] - #[serde(other)] - Unknown, -} - /// Session capability enabled for this session /// ///
@@ -23508,6 +27348,31 @@ pub enum SessionFsSqliteQueryType { Unknown, } +/// SQLite transaction failure classification. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum SessionFsSqliteTransactionErrorClass { + /// SQLite reported BUSY or LOCKED before commit; the transaction was rolled back and may be retried. + #[serde(rename = "busyOrLocked")] + BusyOrLocked, + /// The statement, database, or provider failed definitively and must not be retried automatically. + #[serde(rename = "fatal")] + Fatal, + /// The transport failed after the provider may have committed; retrying could duplicate effects. + #[serde(rename = "postCommitAmbiguous")] + PostCommitAmbiguous, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// Constant value. Always "github". #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum SessionInstalledPluginSourceGitHubSource { @@ -23532,6 +27397,132 @@ pub enum SessionInstalledPluginSourceUrlSource { Url, } +/// Client population used for the prediction baseline. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum SessionLimitPredictionClientType { + /// Interactive CLI sessions where a user can accept, edit, or top up the limit. + #[serde(rename = "cli-interactive")] + CliInteractive, + /// Prompt/non-interactive CLI sessions where the initial limit must cover more of the run. + #[serde(rename = "cli-prompt")] + CliPrompt, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Semantic usage tier used for a recommended cap or additional headroom. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum SessionLimitPredictionTier { + /// Recommended starting tier. + #[serde(rename = "recommended")] + Recommended, + /// Additional headroom for longer-running sessions. + #[serde(rename = "additional_headroom")] + AdditionalHeadroom, + /// Generous headroom for unusually high usage. + #[serde(rename = "generous_headroom")] + GenerousHeadroom, + /// Maximum available headroom tier. + #[serde(rename = "maximum_headroom")] + MaximumHeadroom, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Baseline fallback level used to create the prediction. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum SessionLimitPredictionSource { + /// The prediction used the exact resolved model's baseline cell. + #[serde(rename = "model")] + Model, + /// The exact model was unavailable, so the prediction used the model family's baseline cell. + #[serde(rename = "family")] + Family, + /// No model or family cell was available, so the prediction used the global client-type baseline cell. + #[serde(rename = "global")] + Global, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum SessionLimitPredictionResultAvailableKind { + #[serde(rename = "available")] + #[default] + Available, +} + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum SessionLimitPredictionResultUnavailableKind { + #[serde(rename = "unavailable")] + #[default] + Unavailable, +} + +/// Reason a prediction could not be computed. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum SessionLimitPredictionUnavailableReason { + /// The current model is auto and has not resolved to a concrete model yet. + #[serde(rename = "auto_unresolved")] + AutoUnresolved, + /// No model was provided and the session does not currently have a selected model. + #[serde(rename = "no_model")] + NoModel, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Prediction result. Available results include prediction details; unavailable results include an explicit reason. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum SessionLimitPredictionResult { + Available(SessionLimitPredictionResultAvailable), + Unavailable(SessionLimitPredictionResultUnavailable), +} + /// Repository host type, if known /// ///
@@ -23623,6 +27614,50 @@ pub enum SessionOpenOptionsReasoningSummary { Unknown, } +/// Controls automatic non-interactive profile loading where supported. Explicit initScripts are unaffected. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum ShellInitProfile { + /// Disable automatic non-interactive profile loading. Explicit initScripts still run. + #[serde(rename = "none")] + None, + /// Allow automatic non-interactive profile loading when supported. Explicit initScripts still run. + #[serde(rename = "non-interactive")] + NonInteractive, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Supported built-in shells for initialization scripts. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum ShellInitScriptShell { + /// Source the script in the built-in Bash shell on macOS and Linux. + #[serde(rename = "bash")] + Bash, + /// Source the script in the built-in PowerShell shell on Windows. + #[serde(rename = "powershell")] + Powershell, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// Create a new local session. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum SessionsOpenCreateKind { diff --git a/rust/src/generated/rpc.rs b/rust/src/generated/rpc.rs index 431ffa3ae..fae4d7e16 100644 --- a/rust/src/generated/rpc.rs +++ b/rust/src/generated/rpc.rs @@ -50,6 +50,13 @@ impl<'a> ClientRpc<'a> { } } + /// `extensions.*` sub-namespace. + pub fn extensions(&self) -> ClientRpcExtensions<'a> { + ClientRpcExtensions { + client: self.client, + } + } + /// `instructions.*` sub-namespace. pub fn instructions(&self) -> ClientRpcInstructions<'a> { ClientRpcInstructions { @@ -64,6 +71,13 @@ impl<'a> ClientRpc<'a> { } } + /// `managedSettings.*` sub-namespace. + pub fn managed_settings(&self) -> ClientRpcManagedSettings<'a> { + ClientRpcManagedSettings { + client: self.client, + } + } + /// `mcp.*` sub-namespace. pub fn mcp(&self) -> ClientRpcMcp<'a> { ClientRpcMcp { @@ -189,6 +203,29 @@ impl<'a> ClientRpc<'a> { .await?; Ok(serde_json::from_value(_value)?) } + + /// Registers the calling SDK client as the per-entrypoint extension launch provider. Call before creating any sessions. When omitted, the runtime temporarily falls back to its built-in Node launcher for backward compatibility. + /// + /// Wire method: `registerExtensionLaunchProvider`. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn register_extension_launch_provider(&self) -> Result<(), Error> { + let wire_params = serde_json::json!({}); + let _value = self + .client + .call( + rpc_methods::REGISTEREXTENSIONLAUNCHPROVIDER, + Some(wire_params), + ) + .await?; + Ok(()) + } } /// `account.*` RPCs. @@ -496,6 +533,86 @@ impl<'a> ClientRpcCommands<'a> { } } +/// `extensions.*` RPCs. +#[derive(Clone, Copy)] +pub struct ClientRpcExtensions<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientRpcExtensions<'a> { + /// Discovers user and enabled installed-plugin extensions from persisted Copilot home state, including enablement preferences. Launch-scoped additional plugins are not included. + /// + /// Wire method: `extensions.discover`. + /// + /// # Returns + /// + /// Extensions discovered from persisted Copilot home state and their effective loading mode. Launch-scoped additional plugins are not included. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn discover(&self) -> Result { + let wire_params = serde_json::json!({}); + let _value = self + .client + .call(rpc_methods::EXTENSIONS_DISCOVER, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Persistently enables extension IDs for future sessions. Active sessions are unchanged; use session.extensions.enable to update them. + /// + /// Wire method: `extensions.enable`. + /// + /// # Parameters + /// + /// * `params` - Source-qualified extension identifiers to persistently enable for future sessions. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn enable(&self, params: DiscoveredExtensionsEnableRequest) -> Result<(), Error> { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::EXTENSIONS_ENABLE, Some(wire_params)) + .await?; + Ok(()) + } + + /// Persistently disables extension IDs for future sessions. Active sessions are unchanged; use session.extensions.disable to update them. + /// + /// Wire method: `extensions.disable`. + /// + /// # Parameters + /// + /// * `params` - Source-qualified extension identifiers to persistently disable for future sessions. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn disable(&self, params: DiscoveredExtensionsDisableRequest) -> Result<(), Error> { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::EXTENSIONS_DISABLE, Some(wire_params)) + .await?; + Ok(()) + } +} + /// `instructions.*` RPCs. #[derive(Clone, Copy)] pub struct ClientRpcInstructions<'a> { @@ -669,6 +786,38 @@ impl<'a> ClientRpcLlmInference<'a> { } } +/// `managedSettings.*` RPCs. +#[derive(Clone, Copy)] +pub struct ClientRpcManagedSettings<'a> { + pub(crate) client: &'a Client, +} + +impl<'a> ClientRpcManagedSettings<'a> { + /// Discovers device-managed settings from production MDM and managed-file sources, validates them against the runtime-owned managed-settings schema, and returns the canonical JSON without requiring a session. + /// + /// Wire method: `managedSettings.read`. + /// + /// # Returns + /// + /// Validated device-managed settings discovered before a session exists. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn read(&self) -> Result { + let wire_params = serde_json::json!({}); + let _value = self + .client + .call(rpc_methods::MANAGEDSETTINGS_READ, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } +} + /// `mcp.*` RPCs. #[derive(Clone, Copy)] pub struct ClientRpcMcp<'a> { @@ -942,6 +1091,30 @@ impl<'a> ClientRpcModels<'a> { .await?; Ok(serde_json::from_value(_value)?) } + + /// Returns the running runtime's complete catalog of well-known built-in model IDs without authentication or network access. + /// + /// Wire method: `models.getBuiltInCatalog`. + /// + /// # Returns + /// + /// The running runtime's complete catalog of well-known built-in model IDs, including supported models and additional IDs with built-in metadata. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn get_built_in_catalog(&self) -> Result { + let wire_params = serde_json::json!({}); + let _value = self + .client + .call(rpc_methods::MODELS_GETBUILTINCATALOG, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } } /// `plugins.*` RPCs. @@ -1569,6 +1742,71 @@ impl<'a> ClientRpcSessions<'a> { Ok(serde_json::from_value(_value)?) } + /// Reads lightweight persisted metadata for one local session without opening it. + /// + /// Wire method: `sessions.getMetadata`. + /// + /// # Parameters + /// + /// * `params` - Session ID whose persisted metadata should be read. + /// + /// # Returns + /// + /// Persisted local session metadata when the session exists. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub(crate) async fn get_metadata( + &self, + params: SessionsGetMetadataRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::SESSIONS_GETMETADATA, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Lists recent local session IDs that contain user-visible history, omitting housekeeping-only sessions. + /// + /// Wire method: `sessions.listNonEmptySessionIds`. + /// + /// # Parameters + /// + /// * `params` - Limit for non-empty local session IDs. + /// + /// # Returns + /// + /// Recent local session IDs that contain user-visible history. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub(crate) async fn list_non_empty_session_ids( + &self, + params: SessionsListNonEmptySessionIdsRequest, + ) -> Result { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call( + rpc_methods::SESSIONS_LISTNONEMPTYSESSIONIDS, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + /// Finds the local session bound to a GitHub task ID, if any. /// /// Wire method: `sessions.findByTaskId`. @@ -1841,6 +2079,30 @@ impl<'a> ClientRpcSessions<'a> { Ok(serde_json::from_value(_value)?) } + /// Deletes one local session from disk after running the same lifecycle hooks as the session manager. + /// + /// Wire method: `sessions.delete`. + /// + /// # Parameters + /// + /// * `params` - Session ID to delete from disk. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub(crate) async fn delete(&self, params: SessionsDeleteRequest) -> Result<(), Error> { + let wire_params = serde_json::to_value(params)?; + let _value = self + .client + .call(rpc_methods::SESSIONS_DELETE, Some(wire_params)) + .await?; + Ok(()) + } + /// Deletes sessions older than the given threshold, with optional dry-run and exclusion list. /// /// Wire method: `sessions.pruneOld`. @@ -2619,6 +2881,13 @@ impl<'a> SessionRpc<'a> { } } + /// `session.contentExclusion.*` sub-namespace. + pub fn content_exclusion(&self) -> SessionRpcContentExclusion<'a> { + SessionRpcContentExclusion { + session: self.session, + } + } + /// `session.debug.*` sub-namespace. pub fn debug(&self) -> SessionRpcDebug<'a> { SessionRpcDebug { @@ -2675,6 +2944,13 @@ impl<'a> SessionRpc<'a> { } } + /// `session.limitPrediction.*` sub-namespace. + pub fn limit_prediction(&self) -> SessionRpcLimitPrediction<'a> { + SessionRpcLimitPrediction { + session: self.session, + } + } + /// `session.lsp.*` sub-namespace. pub fn lsp(&self) -> SessionRpcLsp<'a> { SessionRpcLsp { @@ -2927,17 +3203,13 @@ impl<'a> SessionRpc<'a> { Ok(serde_json::from_value(_value)?) } - /// Aborts the current agent turn. + /// Queues or sends an internal system notification to the session according to its passive policy. /// - /// Wire method: `session.abort`. + /// Wire method: `session.sendSystemNotification`. /// /// # Parameters /// - /// * `params` - Parameters for aborting the current turn - /// - /// # Returns - /// - /// Result of aborting the current turn + /// * `params` - Internal request for sending a system notification. /// ///
/// @@ -2946,24 +3218,34 @@ impl<'a> SessionRpc<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn abort(&self, params: AbortRequest) -> Result { + pub(crate) async fn send_system_notification( + &self, + params: SendSystemNotificationRequest, + ) -> Result<(), Error> { let mut wire_params = serde_json::to_value(params)?; wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); let _value = self .session .client() - .call(rpc_methods::SESSION_ABORT, Some(wire_params)) + .call( + rpc_methods::SESSION_SENDSYSTEMNOTIFICATION, + Some(wire_params), + ) .await?; - Ok(serde_json::from_value(_value)?) + Ok(()) } - /// Shuts down the session and persists its final state. Awaits any deferred sessionEnd hooks before resolving so user-supplied hook scripts complete before the runtime tears down. + /// Aborts the current agent turn. /// - /// Wire method: `session.shutdown`. + /// Wire method: `session.abort`. /// /// # Parameters /// - /// * `params` - Parameters for shutting down the session + /// * `params` - Parameters for aborting the current turn + /// + /// # Returns + /// + /// Result of aborting the current turn /// ///
/// @@ -2972,28 +3254,28 @@ impl<'a> SessionRpc<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn shutdown(&self, params: ShutdownRequest) -> Result<(), Error> { + pub async fn abort(&self, params: AbortRequest) -> Result { let mut wire_params = serde_json::to_value(params)?; wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); let _value = self .session .client() - .call(rpc_methods::SESSION_SHUTDOWN, Some(wire_params)) + .call(rpc_methods::SESSION_ABORT, Some(wire_params)) .await?; - Ok(()) + Ok(serde_json::from_value(_value)?) } - /// Emits a user-visible session log event. + /// Interrupts the current main agent turn while leaving running background work (subagents, sidekicks, and promoted attached shells) alive. No-op when the main loop is not processing. /// - /// Wire method: `session.log`. + /// Wire method: `session.interruptMainTurn`. /// /// # Parameters /// - /// * `params` - Message text, optional severity level, persistence flag, optional follow-up URL, and optional tip. + /// * `params` - Parameters for interrupting the main agent turn. /// /// # Returns /// - /// Identifier of the session event that was emitted for the log message. + /// Result of interrupting the main agent turn. /// ///
/// @@ -3002,32 +3284,121 @@ impl<'a> SessionRpc<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn log(&self, params: LogRequest) -> Result { + pub async fn interrupt_main_turn( + &self, + params: InterruptMainTurnRequest, + ) -> Result { let mut wire_params = serde_json::to_value(params)?; wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); let _value = self .session .client() - .call(rpc_methods::SESSION_LOG, Some(wire_params)) + .call(rpc_methods::SESSION_INTERRUPTMAINTURN, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } -} -/// `session.agent.*` RPCs. -#[derive(Clone, Copy)] -pub struct SessionRpcAgent<'a> { - pub(crate) session: &'a Session, -} - -impl<'a> SessionRpcAgent<'a> { - /// Lists custom agents available to the session. + /// Cancels every running background agent (task-registry subagents plus sidekick agents) without interrupting the main agent loop. Promoted attached shells are left running. /// - /// Wire method: `session.agent.list`. + /// Wire method: `session.cancelAllBackgroundAgents`. /// /// # Returns /// - /// Custom agents available to the session. + /// The number of running background agents (task-registry agents) that were cancelled. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn cancel_all_background_agents( + &self, + ) -> Result { + let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + let _value = self + .session + .client() + .call( + rpc_methods::SESSION_CANCELALLBACKGROUNDAGENTS, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Shuts down the session and persists its final state. Awaits any deferred sessionEnd hooks before resolving so user-supplied hook scripts complete before the runtime tears down. + /// + /// Wire method: `session.shutdown`. + /// + /// # Parameters + /// + /// * `params` - Parameters for shutting down the session + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn shutdown(&self, params: ShutdownRequest) -> Result<(), Error> { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_SHUTDOWN, Some(wire_params)) + .await?; + Ok(()) + } + + /// Emits a user-visible session log event. + /// + /// Wire method: `session.log`. + /// + /// # Parameters + /// + /// * `params` - Message text, optional severity level, persistence flag, optional follow-up URL, and optional tip. + /// + /// # Returns + /// + /// Identifier of the session event that was emitted for the log message. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn log(&self, params: LogRequest) -> Result { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_LOG, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } +} + +/// `session.agent.*` RPCs. +#[derive(Clone, Copy)] +pub struct SessionRpcAgent<'a> { + pub(crate) session: &'a Session, +} + +impl<'a> SessionRpcAgent<'a> { + /// Lists agents available to the session. Defaults to custom agents only; pass includeBuiltInAgents to include the effective built-in agents. + /// + /// Wire method: `session.agent.list`. + /// + /// # Returns + /// + /// Agents available to the session. /// ///
/// @@ -3046,6 +3417,62 @@ impl<'a> SessionRpcAgent<'a> { Ok(serde_json::from_value(_value)?) } + /// Lists agents available to the session. Defaults to custom agents only; pass includeBuiltInAgents to include the effective built-in agents. + /// + /// Wire method: `session.agent.list`. + /// + /// # Parameters + /// + /// * `params` - Controls whether built-in agents and authored prompt text are included. + /// + /// # Returns + /// + /// Agents available to the session. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn list_with_params(&self, params: AgentListRequest) -> Result { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_AGENT_LIST, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Sets an in-memory authored prompt override for an available agent. For built-in agents, this replaces only the static base prompt while preserving runtime-owned dynamic prompt composition and behavior. The special `general-purpose` agent is not overrideable. Overrides are not persisted; resumed and forked sessions start without them, so the host must re-apply them. + /// + /// Wire method: `session.agent.setPrompt`. + /// + /// # Parameters + /// + /// * `params` - An in-memory authored prompt override for an available agent. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn set_prompt(&self, params: AgentSetPromptRequest) -> Result<(), Error> { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_AGENT_SETPROMPT, Some(wire_params)) + .await?; + Ok(()) + } + /// Gets the currently selected custom agent for the session. /// /// Wire method: `session.agent.getCurrent`. @@ -3618,6 +4045,50 @@ impl<'a> SessionRpcCompletions<'a> { } } +/// `session.contentExclusion.*` RPCs. +#[derive(Clone, Copy)] +pub struct SessionRpcContentExclusion<'a> { + pub(crate) session: &'a Session, +} + +impl<'a> SessionRpcContentExclusion<'a> { + /// Checks local file system absolute paths within the session working directory against its content-exclusion policy. Results preserve input order. Unsupported paths/filesystems and unavailable policy evaluation return available false, and callers must treat every requested path as excluded. + /// + /// Wire method: `session.contentExclusion.checkPaths`. + /// + /// # Parameters + /// + /// * `params` - Local file system absolute paths within the session working directory to check against its content-exclusion policy. + /// + /// # Returns + /// + /// Batch content-exclusion result. Callers must fail closed when policy evaluation is unavailable. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn check_paths( + &self, + params: ContentExclusionCheckPathsRequest, + ) -> Result { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + let _value = self + .session + .client() + .call( + rpc_methods::SESSION_CONTENTEXCLUSION_CHECKPATHS, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } +} + /// `session.debug.*` RPCs. #[derive(Clone, Copy)] pub struct SessionRpcDebug<'a> { @@ -3666,7 +4137,7 @@ pub struct SessionRpcEventLog<'a> { } impl<'a> SessionRpcEventLog<'a> { - /// Reads a batch of session events from a cursor, optionally waiting for new events. + /// Reads a batch of session events from a cursor, optionally waiting for new events. Supports tail-first reads via `direction: backward`. /// /// Wire method: `session.eventLog.read`. /// @@ -3976,17 +4447,17 @@ impl<'a> SessionRpcFactory<'a> { Ok(serde_json::from_value(_value)?) } - /// Gets the current or settled envelope for a factory run. + /// Resumes a factory run using its persisted name, arguments, journal, and accounting. /// - /// Wire method: `session.factory.getRun`. + /// Wire method: `session.factory.resume`. /// /// # Parameters /// - /// * `params` - Parameters for retrieving a factory run. + /// * `params` - Parameters for resuming a factory run from its persisted identity. /// /// # Returns /// - /// Complete current or terminal factory run envelope. + /// Resolved persisted factory identity and resumed run envelope. /// ///
/// @@ -3995,24 +4466,24 @@ impl<'a> SessionRpcFactory<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn get_run(&self, params: FactoryGetRunRequest) -> Result { + pub async fn resume(&self, params: FactoryResumeRequest) -> Result { let mut wire_params = serde_json::to_value(params)?; wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); let _value = self .session .client() - .call(rpc_methods::SESSION_FACTORY_GETRUN, Some(wire_params)) + .call(rpc_methods::SESSION_FACTORY_RESUME, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Requests cancellation of a factory run and returns its run envelope. + /// Gets the current or settled envelope for a factory run. /// - /// Wire method: `session.factory.cancel`. + /// Wire method: `session.factory.getRun`. /// /// # Parameters /// - /// * `params` - Parameters for cancelling a factory run. + /// * `params` - Parameters for retrieving a factory run. /// /// # Returns /// @@ -4025,28 +4496,28 @@ impl<'a> SessionRpcFactory<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn cancel(&self, params: FactoryCancelRequest) -> Result { + pub async fn get_run(&self, params: FactoryGetRunRequest) -> Result { let mut wire_params = serde_json::to_value(params)?; wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); let _value = self .session .client() - .call(rpc_methods::SESSION_FACTORY_CANCEL, Some(wire_params)) + .call(rpc_methods::SESSION_FACTORY_GETRUN, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Records a batch of ordered factory progress lines. + /// Lists durable factory runs for this session in creation order. /// - /// Wire method: `session.factory.log`. + /// Wire method: `session.factory.listRuns`. /// /// # Parameters /// - /// * `params` - Parameters for recording factory progress. + /// * `params` - Parameters for paging factory runs. /// /// # Returns /// - /// Acknowledgement that a factory request was accepted. + /// A page of factory runs in durable creation order. /// ///
/// @@ -4055,28 +4526,31 @@ impl<'a> SessionRpcFactory<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn log(&self, params: FactoryLogRequest) -> Result { + pub async fn list_runs( + &self, + params: FactoryListRunsRequest, + ) -> Result { let mut wire_params = serde_json::to_value(params)?; wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); let _value = self .session .client() - .call(rpc_methods::SESSION_FACTORY_LOG, Some(wire_params)) + .call(rpc_methods::SESSION_FACTORY_LISTRUNS, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Runs one factory-scoped subagent and returns its result. + /// Gets durable and live observability detail for one factory run. /// - /// Wire method: `session.factory.agent`. + /// Wire method: `session.factory.getRunDetail`. /// /// # Parameters /// - /// * `params` - Parameters for one factory-scoped subagent call. + /// * `params` - Parameters for retrieving a factory run. /// /// # Returns /// - /// Result of one factory-scoped subagent call. + /// Full factory run observability detail. /// ///
/// @@ -4085,36 +4559,31 @@ impl<'a> SessionRpcFactory<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn agent(&self, params: FactoryAgentRequest) -> Result { + pub async fn get_run_detail( + &self, + params: FactoryGetRunRequest, + ) -> Result { let mut wire_params = serde_json::to_value(params)?; wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); let _value = self .session .client() - .call(rpc_methods::SESSION_FACTORY_AGENT, Some(wire_params)) + .call(rpc_methods::SESSION_FACTORY_GETRUNDETAIL, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } -} - -/// `session.factory.journal.*` RPCs. -#[derive(Clone, Copy)] -pub struct SessionRpcFactoryJournal<'a> { - pub(crate) session: &'a Session, -} -impl<'a> SessionRpcFactoryJournal<'a> { - /// Reads a memoized factory journal entry. + /// Pages durable progress for one factory run. /// - /// Wire method: `session.factory.journal.get`. + /// Wire method: `session.factory.getRunProgress`. /// /// # Parameters /// - /// * `params` - Parameters for reading a factory journal entry. + /// * `params` - Parameters for paging factory progress. /// /// # Returns /// - /// Result of reading a factory journal entry. + /// A bidirectional page of factory progress. /// ///
/// @@ -4123,31 +4592,34 @@ impl<'a> SessionRpcFactoryJournal<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn get( + pub async fn get_run_progress( &self, - params: FactoryJournalGetRequest, - ) -> Result { + params: FactoryGetRunProgressRequest, + ) -> Result { let mut wire_params = serde_json::to_value(params)?; wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); let _value = self .session .client() - .call(rpc_methods::SESSION_FACTORY_JOURNAL_GET, Some(wire_params)) + .call( + rpc_methods::SESSION_FACTORY_GETRUNPROGRESS, + Some(wire_params), + ) .await?; Ok(serde_json::from_value(_value)?) } - /// Stores a memoized factory journal entry. + /// Requests cancellation of a factory run and returns its run envelope. /// - /// Wire method: `session.factory.journal.put`. + /// Wire method: `session.factory.cancel`. /// /// # Parameters /// - /// * `params` - Parameters for storing a factory journal entry. + /// * `params` - Parameters for cancelling a factory run. /// /// # Returns /// - /// Acknowledgement that a factory request was accepted. + /// Complete current or terminal factory run envelope. /// ///
/// @@ -4156,36 +4628,28 @@ impl<'a> SessionRpcFactoryJournal<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn put(&self, params: FactoryJournalPutRequest) -> Result { + pub async fn cancel(&self, params: FactoryCancelRequest) -> Result { let mut wire_params = serde_json::to_value(params)?; wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); let _value = self .session .client() - .call(rpc_methods::SESSION_FACTORY_JOURNAL_PUT, Some(wire_params)) + .call(rpc_methods::SESSION_FACTORY_CANCEL, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } -} -/// `session.fleet.*` RPCs. -#[derive(Clone, Copy)] -pub struct SessionRpcFleet<'a> { - pub(crate) session: &'a Session, -} - -impl<'a> SessionRpcFleet<'a> { - /// Starts fleet mode by submitting the fleet orchestration prompt to the session. + /// Records a batch of ordered factory progress lines. /// - /// Wire method: `session.fleet.start`. + /// Wire method: `session.factory.log`. /// /// # Parameters /// - /// * `params` - Optional user prompt to combine with the fleet orchestration instructions. + /// * `params` - Parameters for recording factory progress. /// /// # Returns /// - /// Indicates whether fleet mode was successfully activated. + /// Acknowledgement that a factory request was accepted. /// ///
/// @@ -4194,22 +4658,161 @@ impl<'a> SessionRpcFleet<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn start(&self, params: FleetStartRequest) -> Result { + pub async fn log(&self, params: FactoryLogRequest) -> Result { let mut wire_params = serde_json::to_value(params)?; wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); let _value = self .session .client() - .call(rpc_methods::SESSION_FLEET_START, Some(wire_params)) + .call(rpc_methods::SESSION_FACTORY_LOG, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } -} -/// `session.gitHubAuth.*` RPCs. -#[derive(Clone, Copy)] -pub struct SessionRpcGitHubAuth<'a> { - pub(crate) session: &'a Session, + /// Runs one factory-scoped subagent and returns its result. + /// + /// Wire method: `session.factory.agent`. + /// + /// # Parameters + /// + /// * `params` - Parameters for one factory-scoped subagent call. + /// + /// # Returns + /// + /// Result of one factory-scoped subagent call. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn agent(&self, params: FactoryAgentRequest) -> Result { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_FACTORY_AGENT, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } +} + +/// `session.factory.journal.*` RPCs. +#[derive(Clone, Copy)] +pub struct SessionRpcFactoryJournal<'a> { + pub(crate) session: &'a Session, +} + +impl<'a> SessionRpcFactoryJournal<'a> { + /// Reads a memoized factory journal entry. + /// + /// Wire method: `session.factory.journal.get`. + /// + /// # Parameters + /// + /// * `params` - Parameters for reading a factory journal entry. + /// + /// # Returns + /// + /// Result of reading a factory journal entry. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn get( + &self, + params: FactoryJournalGetRequest, + ) -> Result { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_FACTORY_JOURNAL_GET, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Stores a memoized factory journal entry. + /// + /// Wire method: `session.factory.journal.put`. + /// + /// # Parameters + /// + /// * `params` - Parameters for storing a factory journal entry. + /// + /// # Returns + /// + /// Acknowledgement that a factory request was accepted. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn put(&self, params: FactoryJournalPutRequest) -> Result { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_FACTORY_JOURNAL_PUT, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } +} + +/// `session.fleet.*` RPCs. +#[derive(Clone, Copy)] +pub struct SessionRpcFleet<'a> { + pub(crate) session: &'a Session, +} + +impl<'a> SessionRpcFleet<'a> { + /// Starts fleet mode by submitting the fleet orchestration prompt to the session. + /// + /// Wire method: `session.fleet.start`. + /// + /// # Parameters + /// + /// * `params` - Optional user prompt to combine with the fleet orchestration instructions. + /// + /// # Returns + /// + /// Indicates whether fleet mode was successfully activated. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn start(&self, params: FleetStartRequest) -> Result { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_FLEET_START, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } +} + +/// `session.gitHubAuth.*` RPCs. +#[derive(Clone, Copy)] +pub struct SessionRpcGitHubAuth<'a> { + pub(crate) session: &'a Session, } impl<'a> SessionRpcGitHubAuth<'a> { @@ -4373,6 +4976,100 @@ impl<'a> SessionRpcHistory<'a> { Ok(serde_json::from_value(_value)?) } + /// Lists the user turns that the session can rewind to. Never rejects for a busy session: rewind reads need the session's file-change captures to be settled, so a session that still holds active work answers with `unavailableReason: "session-busy"` and no points, which the caller can retry. + /// + /// Wire method: `session.history.listRewindPoints`. + /// + /// # Returns + /// + /// Rewind points and file-change-tracking availability for the session. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn list_rewind_points(&self) -> Result { + let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + let _value = self + .session + .client() + .call( + rpc_methods::SESSION_HISTORY_LISTREWINDPOINTS, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Previews the files that a conversation-and-files rewind would restore. + /// + /// Wire method: `session.history.previewRewind`. + /// + /// # Parameters + /// + /// * `params` - Event boundary to preview for conversation-and-files rewind. + /// + /// # Returns + /// + /// Files and aggregate changes for a prospective rewind. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn preview_rewind( + &self, + params: HistoryPreviewRewindRequest, + ) -> Result { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + let _value = self + .session + .client() + .call( + rpc_methods::SESSION_HISTORY_PREVIEWREWIND, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Rewinds the session conversation, optionally restoring files changed by the discarded turns. Not crash-atomic: file restore and conversation truncation are separate stores, applied in that order, so a process crash between them can leave the workspace rewound while the conversation still contains the discarded turns. There is no recovery journal; re-running the same rewind is the recovery path for a crash before truncation lands, since file restore is idempotent (already-restored files are reported as skipped) and truncation is re-derived from the still-retained boundary event. After truncation lands that boundary no longer exists, so the same request is rejected; the only stage that can still be outstanding is snapshot pruning, whose failure leaves orphan snapshots the capture store tolerates. The reverse inconsistency cannot occur, because truncation is never applied before file restore succeeds. + /// + /// Wire method: `session.history.rewind`. + /// + /// # Parameters + /// + /// * `params` - Boundary and mode for rewinding session history. + /// + /// # Returns + /// + /// Structured outcome of a rewind request. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn rewind(&self, params: HistoryRewindRequest) -> Result { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_HISTORY_REWIND, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + /// Cancels any in-progress background compaction on a local session. /// /// Wire method: `session.history.cancelBackgroundCompaction`. @@ -4460,6 +5157,39 @@ impl<'a> SessionRpcHistory<'a> { .await?; Ok(serde_json::from_value(_value)?) } + + /// Clears the session's conversation history, keeping only system and developer messages, and seeds the fresh context window with a first user message. Must be called from inside a tool handler: the clear has to drop the results of the tool calls its wipe orphans, and it rejects when no tool call is in flight. + /// + /// Wire method: `session.history.clearContext`. + /// + /// # Parameters + /// + /// * `params` - Parameters for clearing the conversation and seeding the window that replaces it. + /// + /// # Returns + /// + /// What a successful clear removed. A clear that could not be applied rejects instead of reporting a count. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn clear_context( + &self, + params: HistoryClearContextRequest, + ) -> Result { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_HISTORY_CLEARCONTEXT, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } } /// `session.instructions.*` RPCs. @@ -4498,20 +5228,20 @@ impl<'a> SessionRpcInstructions<'a> { } } -/// `session.lsp.*` RPCs. +/// `session.limitPrediction.*` RPCs. #[derive(Clone, Copy)] -pub struct SessionRpcLsp<'a> { +pub struct SessionRpcLimitPrediction<'a> { pub(crate) session: &'a Session, } -impl<'a> SessionRpcLsp<'a> { - /// Loads the merged LSP configuration set for the session's working directory. +impl<'a> SessionRpcLimitPrediction<'a> { + /// Predicts an AI-credit session limit for the session's resolved model. Returns an unavailable result instead of falling back when the current model is unresolved auto. /// - /// Wire method: `session.lsp.initialize`. + /// Wire method: `session.limitPrediction.predict`. /// - /// # Parameters + /// # Returns /// - /// * `params` - Parameters for (re)loading the merged LSP configuration set. + /// Prediction result. Available results include prediction details; unavailable results include an explicit reason. /// ///
/// @@ -4520,9 +5250,81 @@ impl<'a> SessionRpcLsp<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn initialize(&self, params: LspInitializeRequest) -> Result<(), Error> { - let mut wire_params = serde_json::to_value(params)?; - wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + pub async fn predict(&self) -> Result { + let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + let _value = self + .session + .client() + .call( + rpc_methods::SESSION_LIMITPREDICTION_PREDICT, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Predicts an AI-credit session limit for the session's resolved model. Returns an unavailable result instead of falling back when the current model is unresolved auto. + /// + /// Wire method: `session.limitPrediction.predict`. + /// + /// # Parameters + /// + /// * `params` - Parameters for predicting an AI-credit session limit. Omitting `modelId` uses the session's currently selected model. + /// + /// # Returns + /// + /// Prediction result. Available results include prediction details; unavailable results include an explicit reason. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn predict_with_params( + &self, + params: SessionLimitPredictionRequest, + ) -> Result { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + let _value = self + .session + .client() + .call( + rpc_methods::SESSION_LIMITPREDICTION_PREDICT, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } +} + +/// `session.lsp.*` RPCs. +#[derive(Clone, Copy)] +pub struct SessionRpcLsp<'a> { + pub(crate) session: &'a Session, +} + +impl<'a> SessionRpcLsp<'a> { + /// Loads the merged LSP configuration set for the session's working directory. + /// + /// Wire method: `session.lsp.initialize`. + /// + /// # Parameters + /// + /// * `params` - Parameters for (re)loading the merged LSP configuration set. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn initialize(&self, params: LspInitializeRequest) -> Result<(), Error> { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); let _value = self .session .client() @@ -4891,13 +5693,13 @@ impl<'a> SessionRpcMcp<'a> { Ok(serde_json::from_value(_value)?) } - /// Starts an individual MCP server on the live session from a caller-supplied config. Session-scoped and ephemeral: the server is added to this session's running set only and is reaped when the session ends. Does NOT modify persistent user configuration (`mcp.config.*`), so it does not affect future sessions. The server surfaces through `session.mcp.list` and the `session.mcp_servers_loaded` / `session.mcp_server_status_changed` events like any other server. + /// Starts an individual MCP server on the live session. Omit `config` for a config-free start-by-name of an already-configured server (reuses the server's already-registered configuration); supply `config` to start from a caller-supplied configuration. Session-scoped and ephemeral: the server is added to this session's running set only and is reaped when the session ends. Does NOT modify persistent user configuration (`mcp.config.*`), so it does not affect future sessions. The server surfaces through `session.mcp.list` and the `session.mcp_servers_loaded` / `session.mcp_server_status_changed` events like any other server. /// /// Wire method: `session.mcp.startServer`. /// /// # Parameters /// - /// * `params` - Server name and configuration for an individual MCP server start. + /// * `params` - Server name and optional configuration for an individual MCP server start. Omit `config` for a config-free start-by-name of an already-configured server. /// ///
/// @@ -5357,6 +6159,38 @@ impl<'a> SessionRpcMcpOauth<'a> { Ok(serde_json::from_value(_value)?) } + /// Notifies the session that MCP OAuth authentication succeeded and updated credentials were persisted, so cached tool definitions can be refreshed. + /// + /// Wire method: `session.mcp.oauth.authenticationStateChanged`. + /// + /// # Parameters + /// + /// * `params` - Identifies the MCP server whose persisted OAuth credentials were updated. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn authentication_state_changed( + &self, + params: McpOauthAuthenticationStateChangedRequest, + ) -> Result<(), Error> { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + let _value = self + .session + .client() + .call( + rpc_methods::SESSION_MCP_OAUTH_AUTHENTICATIONSTATECHANGED, + Some(wire_params), + ) + .await?; + Ok(()) + } + /// Starts OAuth authentication for a remote MCP server. /// /// Wire method: `session.mcp.oauth.login`. @@ -5386,6 +6220,39 @@ impl<'a> SessionRpcMcpOauth<'a> { .await?; Ok(serde_json::from_value(_value)?) } + + /// Responds to a pending MCP OAuth authorization request by its request id. + /// + /// Wire method: `session.mcp.oauth.respond`. + /// + /// # Parameters + /// + /// * `params` - Pending MCP OAuth request id to respond to. + /// + /// # Returns + /// + /// Indicates whether the pending MCP OAuth response was accepted. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn respond( + &self, + params: McpOauthRespondRequest, + ) -> Result { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_MCP_OAUTH_RESPOND, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } } /// `session.mcp.resources.*` RPCs. @@ -6449,6 +7316,10 @@ impl<'a> SessionRpcPermissions<'a> { /// /// Wire method: `session.permissions.resetSessionApprovals`. /// + /// # Parameters + /// + /// * `params` - Clears session-scoped tool permission approvals, and optionally the location-scoped ones. + /// /// # Returns /// /// Indicates whether the operation succeeded. @@ -6462,8 +7333,10 @@ impl<'a> SessionRpcPermissions<'a> { ///
pub async fn reset_session_approvals( &self, + params: PermissionsResetSessionApprovalsRequest, ) -> Result { - let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); let _value = self .session .client() @@ -6894,18 +7767,669 @@ pub struct SessionRpcPermissionsUrls<'a> { pub(crate) session: &'a Session, } -impl<'a> SessionRpcPermissionsUrls<'a> { - /// Toggles the runtime's URL-permission policy between unrestricted and restricted modes. +impl<'a> SessionRpcPermissionsUrls<'a> { + /// Toggles the runtime's URL-permission policy between unrestricted and restricted modes. + /// + /// Wire method: `session.permissions.urls.setUnrestrictedMode`. + /// + /// # Parameters + /// + /// * `params` - Whether the URL-permission policy should run in unrestricted mode. + /// + /// # Returns + /// + /// Indicates whether the operation succeeded. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn set_unrestricted_mode( + &self, + params: PermissionUrlsSetUnrestrictedModeParams, + ) -> Result { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + let _value = self + .session + .client() + .call( + rpc_methods::SESSION_PERMISSIONS_URLS_SETUNRESTRICTEDMODE, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } +} + +/// `session.plan.*` RPCs. +#[derive(Clone, Copy)] +pub struct SessionRpcPlan<'a> { + pub(crate) session: &'a Session, +} + +impl<'a> SessionRpcPlan<'a> { + /// Reads the session plan file from the workspace. + /// + /// Wire method: `session.plan.read`. + /// + /// # Returns + /// + /// Existence, contents, and resolved path of the session plan file. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn read(&self) -> Result { + let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_PLAN_READ, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Writes new content to the session plan file. + /// + /// Wire method: `session.plan.update`. + /// + /// # Parameters + /// + /// * `params` - Replacement contents to write to the session plan file. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn update(&self, params: PlanUpdateRequest) -> Result<(), Error> { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_PLAN_UPDATE, Some(wire_params)) + .await?; + Ok(()) + } + + /// Deletes the session plan file from the workspace. + /// + /// Wire method: `session.plan.delete`. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn delete(&self) -> Result<(), Error> { + let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_PLAN_DELETE, Some(wire_params)) + .await?; + Ok(()) + } + + /// Reads todo rows from the session SQL database for plan rendering. + /// + /// Wire method: `session.plan.readSqlTodos`. + /// + /// # Returns + /// + /// Todo rows read from the session SQL database. Empty when no session database is available. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn read_sql_todos(&self) -> Result { + let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_PLAN_READSQLTODOS, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Reads todo rows AND dependency edges from the session SQL database for structured progress UI. Same defensive behavior as readSqlTodos — returns empty arrays when the database, tables, or columns aren't available. Clients should call this on session start and after every `session.todos_changed` event to refresh structured-UI rendering. + /// + /// Wire method: `session.plan.readSqlTodosWithDependencies`. + /// + /// # Returns + /// + /// Todo rows + dependency edges read from the session SQL database. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn read_sql_todos_with_dependencies( + &self, + ) -> Result { + let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + let _value = self + .session + .client() + .call( + rpc_methods::SESSION_PLAN_READSQLTODOSWITHDEPENDENCIES, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } +} + +/// `session.plugins.*` RPCs. +#[derive(Clone, Copy)] +pub struct SessionRpcPlugins<'a> { + pub(crate) session: &'a Session, +} + +impl<'a> SessionRpcPlugins<'a> { + /// Lists plugins installed for the session. + /// + /// Wire method: `session.plugins.list`. + /// + /// # Returns + /// + /// Plugins installed for the session, with their enabled state and version metadata. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn list(&self) -> Result { + let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_PLUGINS_LIST, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Reloads the session's plugin set, refreshing MCP servers, custom agents, hooks, and skills cache so SDK-driven changes via `server.plugins.*` take effect immediately. + /// + /// Wire method: `session.plugins.reload`. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn reload(&self) -> Result<(), Error> { + let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_PLUGINS_RELOAD, Some(wire_params)) + .await?; + Ok(()) + } + + /// Reloads the session's plugin set, refreshing MCP servers, custom agents, hooks, and skills cache so SDK-driven changes via `server.plugins.*` take effect immediately. + /// + /// Wire method: `session.plugins.reload`. + /// + /// # Parameters + /// + /// * `params` - Optional flags controlling which side effects the reload performs. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn reload_with_params(&self, params: PluginsReloadRequest) -> Result<(), Error> { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_PLUGINS_RELOAD, Some(wire_params)) + .await?; + Ok(()) + } +} + +/// `session.provider.*` RPCs. +#[derive(Clone, Copy)] +pub struct SessionRpcProvider<'a> { + pub(crate) session: &'a Session, +} + +impl<'a> SessionRpcProvider<'a> { + /// Returns the provider endpoint and credentials the session is currently configured to talk to, so the caller can make inference calls directly against the same backend the session uses. + /// + /// Wire method: `session.provider.getEndpoint`. + /// + /// # Returns + /// + /// A snapshot of the provider endpoint the session is currently configured to talk to. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn get_endpoint(&self) -> Result { + let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_PROVIDER_GETENDPOINT, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Returns the provider endpoint and credentials the session is currently configured to talk to, so the caller can make inference calls directly against the same backend the session uses. + /// + /// Wire method: `session.provider.getEndpoint`. + /// + /// # Parameters + /// + /// * `params` - Optional model identifier to scope the endpoint snapshot to. + /// + /// # Returns + /// + /// A snapshot of the provider endpoint the session is currently configured to talk to. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn get_endpoint_with_params( + &self, + params: ProviderGetEndpointRequest, + ) -> Result { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_PROVIDER_GETENDPOINT, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Adds BYOK providers and/or models to the session's registry at runtime, extending the additive registry built from the session's `providers`/`models` options. Both fields are optional, so a call may add providers only, models only, or both. Within a single call providers are registered before models, so a model may reference a provider added in the same call; across calls a model may reference any provider already registered (from session creation or a prior add). A model whose referenced provider is not registered by the end of the call is rejected. Newly added models become selectable via `model.list` / `model.switchTo` and are inherited by sub-agents spawned afterwards. + /// + /// Wire method: `session.provider.add`. + /// + /// # Parameters + /// + /// * `params` - BYOK providers and/or models to add to the session's registry at runtime. Both fields are optional; provide providers, models, or both. + /// + /// # Returns + /// + /// The selectable model entries synthesized for the models added by this call. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn add(&self, params: ProviderAddRequest) -> Result { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_PROVIDER_ADD, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } +} + +/// `session.queue.*` RPCs. +#[derive(Clone, Copy)] +pub struct SessionRpcQueue<'a> { + pub(crate) session: &'a Session, +} + +impl<'a> SessionRpcQueue<'a> { + /// Returns the local session's pending user-facing queued items and steering messages. + /// + /// Wire method: `session.queue.pendingItems`. + /// + /// # Returns + /// + /// Snapshot of the session's pending queued items and immediate-steering messages. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn pending_items(&self) -> Result { + let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_QUEUE_PENDINGITEMS, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Returns the internal native queue snapshot for in-process session orchestration. + /// + /// Wire method: `session.queue.snapshot`. + /// + /// # Returns + /// + /// Internal snapshot of native queue state for local session orchestration. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub(crate) async fn snapshot(&self) -> Result { + let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_QUEUE_SNAPSHOT, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Moves an addressable queued item to a public visible position. + /// + /// Wire method: `session.queue.moveItem`. + /// + /// # Parameters + /// + /// * `params` - Parameters for moving a queued item by stable id. + /// + /// # Returns + /// + /// Result of moving a queued item. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn move_item( + &self, + params: QueueMoveItemRequest, + ) -> Result { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_QUEUE_MOVEITEM, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Inserts a new queued message at a public visible position. + /// + /// Wire method: `session.queue.insertAt`. + /// + /// # Parameters + /// + /// * `params` - Parameters for inserting a queued message at a public visible position. + /// + /// # Returns + /// + /// Result of inserting a queued message. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn insert_at( + &self, + params: QueueInsertAtRequest, + ) -> Result { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_QUEUE_INSERTAT, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Removes an addressable queued item by its stable id. + /// + /// Wire method: `session.queue.removeAt`. + /// + /// # Parameters + /// + /// * `params` - Parameters for removing a queued item by stable id. + /// + /// # Returns + /// + /// Result of removing a queued item. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn remove_at( + &self, + params: QueueRemoveAtRequest, + ) -> Result { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_QUEUE_REMOVEAT, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Updates the text of an addressable single-message queue item. + /// + /// Wire method: `session.queue.updateText`. + /// + /// # Parameters + /// + /// * `params` - Parameters for editing a single queued message. + /// + /// # Returns + /// + /// Result of editing a queued message. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn update_text( + &self, + params: QueueUpdateTextRequest, + ) -> Result { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_QUEUE_UPDATETEXT, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Duplicates an addressable queued item immediately after its source. + /// + /// Wire method: `session.queue.duplicateAt`. + /// + /// # Parameters + /// + /// * `params` - Parameters for duplicating a queued item. + /// + /// # Returns + /// + /// Result of duplicating a queued item. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn duplicate_at( + &self, + params: QueueDuplicateAtRequest, + ) -> Result { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_QUEUE_DUPLICATEAT, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Acquires or releases the queued-lane drain pause. + /// + /// Wire method: `session.queue.setDrainPaused`. + /// + /// # Parameters + /// + /// * `params` - Parameters for acquiring or releasing the queued-lane drain pause. Acquisition is exclusive and non-idempotent: `paused: true` against an already-paused session fails with `queue_already_paused`. The pause is never released automatically — it is not tied to the caller's lifetime, so a client that exits without sending `paused: false` leaves the lane frozen. Release is unowned: `paused: false` clears the pause for any caller, including one that never acquired it. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn set_drain_paused(&self, params: QueueSetDrainPausedRequest) -> Result<(), Error> { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_QUEUE_SETDRAINPAUSED, Some(wire_params)) + .await?; + Ok(()) + } + + /// Moves an addressable queued message into the live turn's steering lane. + /// + /// Wire method: `session.queue.sendNow`. + /// + /// # Parameters + /// + /// * `params` - Parameters for steering a queued message into a live turn. + /// + /// # Returns + /// + /// Result of trying to steer a queued message into a live turn. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn send_now(&self, params: QueueSendNowRequest) -> Result { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_QUEUE_SENDNOW, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Reports whether the local session has native queued work pending. + /// + /// Wire method: `session.queue.hasPending`. + /// + /// # Returns + /// + /// Whether the native queue has pending work. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub(crate) async fn has_pending(&self) -> Result { + let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_QUEUE_HASPENDING, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Begins a native deferred-idle drain when background work has quiesced. /// - /// Wire method: `session.permissions.urls.setUnrestrictedMode`. + /// Wire method: `session.queue.beginDeferredIdleDrain`. /// /// # Parameters /// - /// * `params` - Whether the URL-permission policy should run in unrestricted mode. + /// * `params` - Inputs for starting a deferred-idle drain. /// /// # Returns /// - /// Indicates whether the operation succeeded. + /// Whether a deferred-idle drain should run. /// ///
/// @@ -6914,38 +8438,34 @@ impl<'a> SessionRpcPermissionsUrls<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn set_unrestricted_mode( + pub(crate) async fn begin_deferred_idle_drain( &self, - params: PermissionUrlsSetUnrestrictedModeParams, - ) -> Result { + params: QueueBeginDeferredIdleDrainRequest, + ) -> Result { let mut wire_params = serde_json::to_value(params)?; wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); let _value = self .session .client() .call( - rpc_methods::SESSION_PERMISSIONS_URLS_SETUNRESTRICTEDMODE, + rpc_methods::SESSION_QUEUE_BEGINDEFERREDIDLEDRAIN, Some(wire_params), ) .await?; Ok(serde_json::from_value(_value)?) } -} - -/// `session.plan.*` RPCs. -#[derive(Clone, Copy)] -pub struct SessionRpcPlan<'a> { - pub(crate) session: &'a Session, -} -impl<'a> SessionRpcPlan<'a> { - /// Reads the session plan file from the workspace. + /// Finishes a native deferred-idle drain and reports whether to drain queue work or emit idle. /// - /// Wire method: `session.plan.read`. + /// Wire method: `session.queue.finishDeferredIdleDrain`. + /// + /// # Parameters + /// + /// * `params` - Inputs for completing a deferred-idle drain. /// /// # Returns /// - /// Existence, contents, and resolved path of the session plan file. + /// Action selected by the native deferred-idle drain. /// ///
/// @@ -6954,23 +8474,30 @@ impl<'a> SessionRpcPlan<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn read(&self) -> Result { - let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + pub(crate) async fn finish_deferred_idle_drain( + &self, + params: QueueFinishDeferredIdleDrainRequest, + ) -> Result { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); let _value = self .session .client() - .call(rpc_methods::SESSION_PLAN_READ, Some(wire_params)) + .call( + rpc_methods::SESSION_QUEUE_FINISHDEFERREDIDLEDRAIN, + Some(wire_params), + ) .await?; Ok(serde_json::from_value(_value)?) } - /// Writes new content to the session plan file. + /// Marks session.idle as deferred by native background work state. /// - /// Wire method: `session.plan.update`. + /// Wire method: `session.queue.deferSessionIdle`. /// /// # Parameters /// - /// * `params` - Replacement contents to write to the session plan file. + /// * `params` - Inputs for marking session.idle deferred in native state. /// ///
/// @@ -6979,20 +8506,30 @@ impl<'a> SessionRpcPlan<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn update(&self, params: PlanUpdateRequest) -> Result<(), Error> { + pub(crate) async fn defer_session_idle( + &self, + params: QueueDeferSessionIdleRequest, + ) -> Result<(), Error> { let mut wire_params = serde_json::to_value(params)?; wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); let _value = self .session .client() - .call(rpc_methods::SESSION_PLAN_UPDATE, Some(wire_params)) + .call( + rpc_methods::SESSION_QUEUE_DEFERSESSIONIDLE, + Some(wire_params), + ) .await?; Ok(()) } - /// Deletes the session plan file from the workspace. + /// Removes the most recently queued user-facing item (LIFO). /// - /// Wire method: `session.plan.delete`. + /// Wire method: `session.queue.removeMostRecent`. + /// + /// # Returns + /// + /// Indicates whether a user-facing pending item was removed. /// ///
/// @@ -7001,23 +8538,22 @@ impl<'a> SessionRpcPlan<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn delete(&self) -> Result<(), Error> { + pub async fn remove_most_recent(&self) -> Result { let wire_params = serde_json::json!({ "sessionId": self.session.id() }); let _value = self .session .client() - .call(rpc_methods::SESSION_PLAN_DELETE, Some(wire_params)) + .call( + rpc_methods::SESSION_QUEUE_REMOVEMOSTRECENT, + Some(wire_params), + ) .await?; - Ok(()) + Ok(serde_json::from_value(_value)?) } - /// Reads todo rows from the session SQL database for plan rendering. - /// - /// Wire method: `session.plan.readSqlTodos`. - /// - /// # Returns + /// Clears all pending queued items on the local session. /// - /// Todo rows read from the session SQL database. Empty when no session database is available. + /// Wire method: `session.queue.clear`. /// ///
/// @@ -7026,23 +8562,27 @@ impl<'a> SessionRpcPlan<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn read_sql_todos(&self) -> Result { + pub async fn clear(&self) -> Result<(), Error> { let wire_params = serde_json::json!({ "sessionId": self.session.id() }); let _value = self .session .client() - .call(rpc_methods::SESSION_PLAN_READSQLTODOS, Some(wire_params)) + .call(rpc_methods::SESSION_QUEUE_CLEAR, Some(wire_params)) .await?; - Ok(serde_json::from_value(_value)?) + Ok(()) } - /// Reads todo rows AND dependency edges from the session SQL database for structured progress UI. Same defensive behavior as readSqlTodos — returns empty arrays when the database, tables, or columns aren't available. Clients should call this on session start and after every `session.todos_changed` event to refresh structured-UI rendering. + /// Consumes queued native system notifications matching an internal filter. /// - /// Wire method: `session.plan.readSqlTodosWithDependencies`. + /// Wire method: `session.queue.consumeSystemNotifications`. + /// + /// # Parameters + /// + /// * `params` - Internal filter for consuming queued system notifications. /// /// # Returns /// - /// Todo rows + dependency edges read from the session SQL database. + /// Indicates whether a user-facing pending item was removed. /// ///
/// @@ -7051,36 +8591,30 @@ impl<'a> SessionRpcPlan<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn read_sql_todos_with_dependencies( + pub(crate) async fn consume_system_notifications( &self, - ) -> Result { - let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + params: QueueConsumeSystemNotificationsRequest, + ) -> Result { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); let _value = self .session .client() .call( - rpc_methods::SESSION_PLAN_READSQLTODOSWITHDEPENDENCIES, + rpc_methods::SESSION_QUEUE_CONSUMESYSTEMNOTIFICATIONS, Some(wire_params), ) .await?; Ok(serde_json::from_value(_value)?) } -} - -/// `session.plugins.*` RPCs. -#[derive(Clone, Copy)] -pub struct SessionRpcPlugins<'a> { - pub(crate) session: &'a Session, -} -impl<'a> SessionRpcPlugins<'a> { - /// Lists plugins installed for the session. + /// Enqueues the internal resume-pending wake item when orphan handling needs a follow-up turn. /// - /// Wire method: `session.plugins.list`. + /// Wire method: `session.queue.enqueueResumePending`. /// /// # Returns /// - /// Plugins installed for the session, with their enabled state and version metadata. + /// Result of enqueueing the resume-pending wake item. /// ///
/// @@ -7089,19 +8623,24 @@ impl<'a> SessionRpcPlugins<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn list(&self) -> Result { + pub(crate) async fn enqueue_resume_pending( + &self, + ) -> Result { let wire_params = serde_json::json!({ "sessionId": self.session.id() }); let _value = self .session .client() - .call(rpc_methods::SESSION_PLUGINS_LIST, Some(wire_params)) + .call( + rpc_methods::SESSION_QUEUE_ENQUEUERESUMEPENDING, + Some(wire_params), + ) .await?; Ok(serde_json::from_value(_value)?) } - /// Reloads the session's plugin set, refreshing MCP servers, custom agents, hooks, and skills cache so SDK-driven changes via `server.plugins.*` take effect immediately. + /// Drains the native local-session work queue for in-process session orchestration. /// - /// Wire method: `session.plugins.reload`. + /// Wire method: `session.queue.process`. /// ///
/// @@ -7110,23 +8649,35 @@ impl<'a> SessionRpcPlugins<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn reload(&self) -> Result<(), Error> { + pub(crate) async fn process(&self) -> Result<(), Error> { let wire_params = serde_json::json!({ "sessionId": self.session.id() }); let _value = self .session .client() - .call(rpc_methods::SESSION_PLUGINS_RELOAD, Some(wire_params)) + .call(rpc_methods::SESSION_QUEUE_PROCESS, Some(wire_params)) .await?; Ok(()) } +} - /// Reloads the session's plugin set, refreshing MCP servers, custom agents, hooks, and skills cache so SDK-driven changes via `server.plugins.*` take effect immediately. +/// `session.remote.*` RPCs. +#[derive(Clone, Copy)] +pub struct SessionRpcRemote<'a> { + pub(crate) session: &'a Session, +} + +impl<'a> SessionRpcRemote<'a> { + /// Enables remote session export or steering. /// - /// Wire method: `session.plugins.reload`. + /// Wire method: `session.remote.enable`. /// /// # Parameters /// - /// * `params` - Optional flags controlling which side effects the reload performs. + /// * `params` - Optional remote session mode ("off", "export", or "on"); defaults to enabling both export and remote steering. + /// + /// # Returns + /// + /// GitHub URL for the session and a flag indicating whether remote steering is enabled. /// ///
/// @@ -7135,32 +8686,20 @@ impl<'a> SessionRpcPlugins<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn reload_with_params(&self, params: PluginsReloadRequest) -> Result<(), Error> { + pub async fn enable(&self, params: RemoteEnableRequest) -> Result { let mut wire_params = serde_json::to_value(params)?; wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); let _value = self .session .client() - .call(rpc_methods::SESSION_PLUGINS_RELOAD, Some(wire_params)) + .call(rpc_methods::SESSION_REMOTE_ENABLE, Some(wire_params)) .await?; - Ok(()) + Ok(serde_json::from_value(_value)?) } -} - -/// `session.provider.*` RPCs. -#[derive(Clone, Copy)] -pub struct SessionRpcProvider<'a> { - pub(crate) session: &'a Session, -} -impl<'a> SessionRpcProvider<'a> { - /// Returns the provider endpoint and credentials the session is currently configured to talk to, so the caller can make inference calls directly against the same backend the session uses. - /// - /// Wire method: `session.provider.getEndpoint`. - /// - /// # Returns + /// Disables remote session export and steering. /// - /// A snapshot of the provider endpoint the session is currently configured to talk to. + /// Wire method: `session.remote.disable`. /// ///
/// @@ -7169,27 +8708,27 @@ impl<'a> SessionRpcProvider<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn get_endpoint(&self) -> Result { + pub async fn disable(&self) -> Result<(), Error> { let wire_params = serde_json::json!({ "sessionId": self.session.id() }); let _value = self .session .client() - .call(rpc_methods::SESSION_PROVIDER_GETENDPOINT, Some(wire_params)) + .call(rpc_methods::SESSION_REMOTE_DISABLE, Some(wire_params)) .await?; - Ok(serde_json::from_value(_value)?) + Ok(()) } - /// Returns the provider endpoint and credentials the session is currently configured to talk to, so the caller can make inference calls directly against the same backend the session uses. + /// Persists a remote-steerability change emitted by the host as a session event. /// - /// Wire method: `session.provider.getEndpoint`. + /// Wire method: `session.remote.notifySteerableChanged`. /// /// # Parameters /// - /// * `params` - Optional model identifier to scope the endpoint snapshot to. + /// * `params` - New remote-steerability state to persist as a `session.remote_steerable_changed` event. /// /// # Returns /// - /// A snapshot of the provider endpoint the session is currently configured to talk to. + /// Persist a steerability change as a `session.remote_steerable_changed` event. Used by the host (CLI / SDK consumer) when it has just finished enabling or disabling steering on a remote exporter that the runtime does not directly own. /// ///
/// @@ -7198,31 +8737,38 @@ impl<'a> SessionRpcProvider<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn get_endpoint_with_params( + pub async fn notify_steerable_changed( &self, - params: ProviderGetEndpointRequest, - ) -> Result { + params: RemoteNotifySteerableChangedRequest, + ) -> Result { let mut wire_params = serde_json::to_value(params)?; wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); let _value = self .session .client() - .call(rpc_methods::SESSION_PROVIDER_GETENDPOINT, Some(wire_params)) + .call( + rpc_methods::SESSION_REMOTE_NOTIFYSTEERABLECHANGED, + Some(wire_params), + ) .await?; Ok(serde_json::from_value(_value)?) } +} - /// Adds BYOK providers and/or models to the session's registry at runtime, extending the additive registry built from the session's `providers`/`models` options. Both fields are optional, so a call may add providers only, models only, or both. Within a single call providers are registered before models, so a model may reference a provider added in the same call; across calls a model may reference any provider already registered (from session creation or a prior add). A model whose referenced provider is not registered by the end of the call is rejected. Newly added models become selectable via `model.list` / `model.switchTo` and are inherited by sub-agents spawned afterwards. - /// - /// Wire method: `session.provider.add`. - /// - /// # Parameters +/// `session.schedule.*` RPCs. +#[derive(Clone, Copy)] +pub struct SessionRpcSchedule<'a> { + pub(crate) session: &'a Session, +} + +impl<'a> SessionRpcSchedule<'a> { + /// Lists the session's currently active scheduled prompts. /// - /// * `params` - BYOK providers and/or models to add to the session's registry at runtime. Both fields are optional; provide providers, models, or both. + /// Wire method: `session.schedule.list`. /// /// # Returns /// - /// The selectable model entries synthesized for the models added by this call. + /// Snapshot of the currently active recurring prompts for this session. /// ///
/// @@ -7231,32 +8777,19 @@ impl<'a> SessionRpcProvider<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn add(&self, params: ProviderAddRequest) -> Result { - let mut wire_params = serde_json::to_value(params)?; - wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + pub async fn list(&self) -> Result { + let wire_params = serde_json::json!({ "sessionId": self.session.id() }); let _value = self .session .client() - .call(rpc_methods::SESSION_PROVIDER_ADD, Some(wire_params)) + .call(rpc_methods::SESSION_SCHEDULE_LIST, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } -} - -/// `session.queue.*` RPCs. -#[derive(Clone, Copy)] -pub struct SessionRpcQueue<'a> { - pub(crate) session: &'a Session, -} -impl<'a> SessionRpcQueue<'a> { - /// Returns the local session's pending user-facing queued items and steering messages. - /// - /// Wire method: `session.queue.pendingItems`. - /// - /// # Returns + /// Hydrates the native schedule registry from persisted session events. /// - /// Snapshot of the session's pending queued items and immediate-steering messages. + /// Wire method: `session.schedule.hydrate`. /// ///
/// @@ -7265,23 +8798,23 @@ impl<'a> SessionRpcQueue<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn pending_items(&self) -> Result { + pub(crate) async fn hydrate(&self) -> Result<(), Error> { let wire_params = serde_json::json!({ "sessionId": self.session.id() }); let _value = self .session .client() - .call(rpc_methods::SESSION_QUEUE_PENDINGITEMS, Some(wire_params)) + .call(rpc_methods::SESSION_SCHEDULE_HYDRATE, Some(wire_params)) .await?; - Ok(serde_json::from_value(_value)?) + Ok(()) } - /// Removes the most recently queued user-facing item (LIFO). + /// Reports whether the session has an active self-paced scheduled prompt. /// - /// Wire method: `session.queue.removeMostRecent`. + /// Wire method: `session.schedule.hasSelfPaced`. /// /// # Returns /// - /// Indicates whether a user-facing pending item was removed. + /// Whether the session currently has an active self-paced schedule. /// ///
/// @@ -7290,22 +8823,30 @@ impl<'a> SessionRpcQueue<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn remove_most_recent(&self) -> Result { + pub(crate) async fn has_self_paced(&self) -> Result { let wire_params = serde_json::json!({ "sessionId": self.session.id() }); let _value = self .session .client() .call( - rpc_methods::SESSION_QUEUE_REMOVEMOSTRECENT, + rpc_methods::SESSION_SCHEDULE_HASSELFPACED, Some(wire_params), ) .await?; Ok(serde_json::from_value(_value)?) } - /// Clears all pending queued items on the local session. + /// Registers a relative-interval scheduled prompt. /// - /// Wire method: `session.queue.clear`. + /// Wire method: `session.schedule.add`. + /// + /// # Parameters + /// + /// * `params` - Register a relative-interval scheduled prompt. + /// + /// # Returns + /// + /// Result of registering or re-arming a scheduled prompt. /// ///
/// @@ -7314,35 +8855,28 @@ impl<'a> SessionRpcQueue<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn clear(&self) -> Result<(), Error> { - let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + pub(crate) async fn add(&self, params: ScheduleAddRequest) -> Result { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); let _value = self .session .client() - .call(rpc_methods::SESSION_QUEUE_CLEAR, Some(wire_params)) + .call(rpc_methods::SESSION_SCHEDULE_ADD, Some(wire_params)) .await?; - Ok(()) + Ok(serde_json::from_value(_value)?) } -} - -/// `session.remote.*` RPCs. -#[derive(Clone, Copy)] -pub struct SessionRpcRemote<'a> { - pub(crate) session: &'a Session, -} -impl<'a> SessionRpcRemote<'a> { - /// Enables remote session export or steering. + /// Registers a recurring cron scheduled prompt. /// - /// Wire method: `session.remote.enable`. + /// Wire method: `session.schedule.addCron`. /// /// # Parameters /// - /// * `params` - Optional remote session mode ("off", "export", or "on"); defaults to enabling both export and remote steering. + /// * `params` - Register a cron scheduled prompt. /// /// # Returns /// - /// GitHub URL for the session and a flag indicating whether remote steering is enabled. + /// Result of registering or re-arming a scheduled prompt. /// ///
/// @@ -7351,20 +8885,31 @@ impl<'a> SessionRpcRemote<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn enable(&self, params: RemoteEnableRequest) -> Result { + pub(crate) async fn add_cron( + &self, + params: ScheduleAddCronRequest, + ) -> Result { let mut wire_params = serde_json::to_value(params)?; wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); let _value = self .session .client() - .call(rpc_methods::SESSION_REMOTE_ENABLE, Some(wire_params)) + .call(rpc_methods::SESSION_SCHEDULE_ADDCRON, Some(wire_params)) .await?; Ok(serde_json::from_value(_value)?) } - /// Disables remote session export and steering. + /// Registers an absolute-time scheduled prompt. + /// + /// Wire method: `session.schedule.addAt`. + /// + /// # Parameters /// - /// Wire method: `session.remote.disable`. + /// * `params` - Register an absolute-time scheduled prompt. + /// + /// # Returns + /// + /// Result of registering or re-arming a scheduled prompt. /// ///
/// @@ -7373,27 +8918,31 @@ impl<'a> SessionRpcRemote<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn disable(&self) -> Result<(), Error> { - let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + pub(crate) async fn add_at( + &self, + params: ScheduleAddAtRequest, + ) -> Result { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); let _value = self .session .client() - .call(rpc_methods::SESSION_REMOTE_DISABLE, Some(wire_params)) + .call(rpc_methods::SESSION_SCHEDULE_ADDAT, Some(wire_params)) .await?; - Ok(()) + Ok(serde_json::from_value(_value)?) } - /// Persists a remote-steerability change emitted by the host as a session event. + /// Registers a self-paced scheduled prompt. /// - /// Wire method: `session.remote.notifySteerableChanged`. + /// Wire method: `session.schedule.addSelfPaced`. /// /// # Parameters /// - /// * `params` - New remote-steerability state to persist as a `session.remote_steerable_changed` event. + /// * `params` - Register a self-paced scheduled prompt. /// /// # Returns /// - /// Persist a steerability change as a `session.remote_steerable_changed` event. Used by the host (CLI / SDK consumer) when it has just finished enabling or disabling steering on a remote exporter that the runtime does not directly own. + /// Result of registering or re-arming a scheduled prompt. /// ///
/// @@ -7402,38 +8951,34 @@ impl<'a> SessionRpcRemote<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn notify_steerable_changed( + pub(crate) async fn add_self_paced( &self, - params: RemoteNotifySteerableChangedRequest, - ) -> Result { + params: ScheduleAddSelfPacedRequest, + ) -> Result { let mut wire_params = serde_json::to_value(params)?; wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); let _value = self .session .client() .call( - rpc_methods::SESSION_REMOTE_NOTIFYSTEERABLECHANGED, + rpc_methods::SESSION_SCHEDULE_ADDSELFPACED, Some(wire_params), ) .await?; Ok(serde_json::from_value(_value)?) } -} - -/// `session.schedule.*` RPCs. -#[derive(Clone, Copy)] -pub struct SessionRpcSchedule<'a> { - pub(crate) session: &'a Session, -} -impl<'a> SessionRpcSchedule<'a> { - /// Lists the session's currently active scheduled prompts. + /// Re-arms an active self-paced scheduled prompt. /// - /// Wire method: `session.schedule.list`. + /// Wire method: `session.schedule.rearmSelfPaced`. + /// + /// # Parameters + /// + /// * `params` - Re-arm a self-paced scheduled prompt. /// /// # Returns /// - /// Snapshot of the currently active recurring prompts for this session. + /// Result of registering or re-arming a scheduled prompt. /// ///
/// @@ -7442,12 +8987,19 @@ impl<'a> SessionRpcSchedule<'a> { /// SDK and CLI versions if your code depends on it. /// ///
- pub async fn list(&self) -> Result { - let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + pub(crate) async fn rearm_self_paced( + &self, + params: ScheduleRearmSelfPacedRequest, + ) -> Result { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); let _value = self .session .client() - .call(rpc_methods::SESSION_SCHEDULE_LIST, Some(wire_params)) + .call( + rpc_methods::SESSION_SCHEDULE_REARMSELFPACED, + Some(wire_params), + ) .await?; Ok(serde_json::from_value(_value)?) } @@ -7559,7 +9111,7 @@ pub struct SessionRpcShell<'a> { } impl<'a> SessionRpcShell<'a> { - /// Starts a shell command and streams output through session notifications. + /// Starts a shell command and streams output through session notifications. The command runs as the leader of its own process group (POSIX) or in a dedicated job object (Windows), so a forced termination — via "shell.kill", the request timeout, or session disposal — signals that whole group/job rather than only the direct child. Two gaps are worth planning for: a command that exits on its own does not trigger that teardown, and on POSIX a descendant that moves itself into a new session or process group (for example via "setsid") leaves the signalled group, so either can leave a background process running. /// /// Wire method: `session.shell.exec`. /// @@ -7589,7 +9141,7 @@ impl<'a> SessionRpcShell<'a> { Ok(serde_json::from_value(_value)?) } - /// Sends a signal to a shell process previously started via "shell.exec". + /// Sends a signal to a shell process previously started via "shell.exec". The signal targets the command's whole process group (POSIX) or job object (Windows), so descendants still in that group are signalled too, not just the direct child. On POSIX a descendant that moved itself into a new session or process group (for example via "setsid") is no longer in the signalled group and survives. /// /// Wire method: `session.shell.kill`. /// @@ -8875,6 +10427,75 @@ impl<'a> SessionRpcWorkspaces<'a> { Ok(serde_json::from_value(_value)?) } + /// Updates workspace metadata for a local session and returns the refreshed workspace. + /// + /// Wire method: `session.workspaces.updateMetadata`. + /// + /// # Parameters + /// + /// * `params` - Workspace metadata fields to update. + /// + /// # Returns + /// + /// Current workspace metadata for the session, including its absolute filesystem path when available. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn update_metadata( + &self, + params: WorkspacesUpdateMetadataRequest, + ) -> Result { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + let _value = self + .session + .client() + .call( + rpc_methods::SESSION_WORKSPACES_UPDATEMETADATA, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Ensures a local session workspace exists and returns it. + /// + /// Wire method: `session.workspaces.ensure`. + /// + /// # Parameters + /// + /// * `params` - Optional session context used when creating a local workspace. + /// + /// # Returns + /// + /// Current workspace metadata for the session, including its absolute filesystem path when available. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn ensure( + &self, + params: WorkspacesEnsureRequest, + ) -> Result { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + let _value = self + .session + .client() + .call(rpc_methods::SESSION_WORKSPACES_ENSURE, Some(wire_params)) + .await?; + Ok(serde_json::from_value(_value)?) + } + /// Lists files stored in the session workspace files directory. /// /// Wire method: `session.workspaces.listFiles`. @@ -9026,6 +10647,204 @@ impl<'a> SessionRpcWorkspaces<'a> { Ok(serde_json::from_value(_value)?) } + /// Adds a compaction summary checkpoint to the local session workspace. + /// + /// Wire method: `session.workspaces.addSummary`. + /// + /// # Parameters + /// + /// * `params` - Compaction summary checkpoint to persist. + /// + /// # Returns + /// + /// Persisted summary metadata and refreshed workspace metadata. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn add_summary( + &self, + params: WorkspacesAddSummaryRequest, + ) -> Result { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + let _value = self + .session + .client() + .call( + rpc_methods::SESSION_WORKSPACES_ADDSUMMARY, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Truncates local workspace compaction summaries after a rollback. + /// + /// Wire method: `session.workspaces.truncateSummaries`. + /// + /// # Parameters + /// + /// * `params` - Rollback point for local workspace summaries. + /// + /// # Returns + /// + /// Current workspace metadata for the session, including its absolute filesystem path when available. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn truncate_summaries( + &self, + params: WorkspacesTruncateSummariesRequest, + ) -> Result { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + let _value = self + .session + .client() + .call( + rpc_methods::SESSION_WORKSPACES_TRUNCATESUMMARIES, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Reads the autopilot objective state file from the local session workspace. + /// + /// Wire method: `session.workspaces.readAutopilotObjective`. + /// + /// # Returns + /// + /// Autopilot objective file content, or null when missing. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn read_autopilot_objective( + &self, + ) -> Result { + let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + let _value = self + .session + .client() + .call( + rpc_methods::SESSION_WORKSPACES_READAUTOPILOTOBJECTIVE, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Writes the autopilot objective state file in the local session workspace. + /// + /// Wire method: `session.workspaces.writeAutopilotObjective`. + /// + /// # Parameters + /// + /// * `params` - Autopilot objective file content to persist. + /// + /// # Returns + /// + /// Result of writing the autopilot objective file. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn write_autopilot_objective( + &self, + params: WorkspacesWriteAutopilotObjectiveRequest, + ) -> Result { + let mut wire_params = serde_json::to_value(params)?; + wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string()); + let _value = self + .session + .client() + .call( + rpc_methods::SESSION_WORKSPACES_WRITEAUTOPILOTOBJECTIVE, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Deletes the autopilot objective state file from the local session workspace. + /// + /// Wire method: `session.workspaces.deleteAutopilotObjective`. + /// + /// # Returns + /// + /// Result of deleting the autopilot objective file. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn delete_autopilot_objective( + &self, + ) -> Result { + let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + let _value = self + .session + .client() + .call( + rpc_methods::SESSION_WORKSPACES_DELETEAUTOPILOTOBJECTIVE, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + + /// Checks whether the local session workspace has an autopilot objective state file. + /// + /// Wire method: `session.workspaces.autopilotObjectiveExists`. + /// + /// # Returns + /// + /// Whether the autopilot objective file exists. + /// + ///
+ /// + /// **Experimental.** This API is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. Pin both the + /// SDK and CLI versions if your code depends on it. + /// + ///
+ pub async fn autopilot_objective_exists( + &self, + ) -> Result { + let wire_params = serde_json::json!({ "sessionId": self.session.id() }); + let _value = self + .session + .client() + .call( + rpc_methods::SESSION_WORKSPACES_AUTOPILOTOBJECTIVEEXISTS, + Some(wire_params), + ) + .await?; + Ok(serde_json::from_value(_value)?) + } + /// Saves pasted content as a UTF-8 file in the session workspace. /// /// Wire method: `session.workspaces.saveLargePaste`. @@ -9062,7 +10881,7 @@ impl<'a> SessionRpcWorkspaces<'a> { Ok(serde_json::from_value(_value)?) } - /// Computes a diff for the session workspace. + /// Computes a diff for the session workspace. Never rejects for a busy session: a `session`-mode diff that cannot read the session's file-change captures falls back to an unstaged git diff with `isFallback: true` and reports why in `unavailableReason`. /// /// Wire method: `session.workspaces.diff`. /// diff --git a/rust/src/generated/session_events.rs b/rust/src/generated/session_events.rs index abc7dca62..913499a6f 100644 --- a/rust/src/generated/session_events.rs +++ b/rust/src/generated/session_events.rs @@ -63,6 +63,8 @@ pub enum SessionEventType { SessionContextChanged, #[serde(rename = "session.usage_info")] SessionUsageInfo, + #[serde(rename = "session.context_cleared")] + SessionContextCleared, #[serde(rename = "session.compaction_start")] SessionCompactionStart, #[serde(rename = "session.compaction_complete")] @@ -233,6 +235,15 @@ pub enum SessionEventType { SessionToolsUpdated, #[serde(rename = "session.background_tasks_changed")] SessionBackgroundTasksChanged, + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(rename = "factory.run_updated")] + FactoryRunUpdated, #[serde(rename = "session.skills_loaded")] SessionSkillsLoaded, #[serde(rename = "session.custom_agents_updated")] @@ -371,6 +382,8 @@ pub enum SessionEventData { SessionContextChanged(SessionContextChangedData), #[serde(rename = "session.usage_info")] SessionUsageInfo(SessionUsageInfoData), + #[serde(rename = "session.context_cleared")] + SessionContextCleared(SessionContextClearedData), #[serde(rename = "session.compaction_start")] SessionCompactionStart(SessionCompactionStartData), #[serde(rename = "session.compaction_complete")] @@ -534,6 +547,15 @@ pub enum SessionEventData { SessionToolsUpdated(SessionToolsUpdatedData), #[serde(rename = "session.background_tasks_changed")] SessionBackgroundTasksChanged(SessionBackgroundTasksChangedData), + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(rename = "factory.run_updated")] + FactoryRunUpdated(FactoryRunUpdatedData), #[serde(rename = "session.skills_loaded")] SessionSkillsLoaded(SessionSkillsLoadedData), #[serde(rename = "session.custom_agents_updated")] @@ -658,6 +680,9 @@ pub struct WorkingDirectoryContext { /// Hosting platform type of the repository (github or ado) #[serde(skip_serializing_if = "Option::is_none")] pub host_type: Option, + /// Set on the immediate preliminary event of a working-directory change, before the git context is resolved. A settled follow-up event (enriched with git context, or cwd-only for a non-repository) is always emitted afterward, so observers may defer to it. Absent on standalone/final events (e.g. relay context changes). + #[serde(skip_serializing_if = "Option::is_none")] + pub pending_git_context: Option, /// Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) #[serde(skip_serializing_if = "Option::is_none")] pub repository: Option, @@ -666,6 +691,24 @@ pub struct WorkingDirectoryContext { pub repository_host: Option, } +/// Per-session configuration for the built-in GitHub MCP server +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GitHubMcpToolConfig { + /// Additional GitHub MCP tools requested by the session + #[serde(skip_serializing_if = "Option::is_none")] + pub additional_tools: Option>, + /// Additional GitHub MCP toolsets requested by the session + #[serde(skip_serializing_if = "Option::is_none")] + pub additional_toolsets: Option>, + /// Whether to use the read-write endpoint and request all toolsets + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_all_tools: Option, + /// Whether to request the GitHub MCP insiders build + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_insiders_mode: Option, +} + /// Optional session limits. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -693,6 +736,9 @@ pub struct SessionStartData { /// When set, identifies a parent session whose context this session continues — e.g., a detached headless rem-agent run launched on the parent's interactive shutdown. Telemetry from this session is reported under the parent's session_id. #[serde(skip_serializing_if = "Option::is_none")] pub detached_from_spawning_parent_session_id: Option, + /// Per-session GitHub MCP override persisted for cold resume + #[serde(skip_serializing_if = "Option::is_none")] + pub github_mcp_tool_config: Option, /// Identifier of the software producing the events (e.g., "copilot-agent") pub producer: String, /// Reasoning effort level used for model calls, if applicable (e.g. "none", "low", "medium", "high", "xhigh", "max") @@ -734,7 +780,7 @@ pub struct SessionResumeData { /// Context tier currently selected at resume time; null when no tier is active #[serde(skip_serializing_if = "Option::is_none")] pub context_tier: Option, - /// When true, tool calls and permission requests left in flight by the previous session lifetime remain pending after resume and the agentic loop awaits their results. User sends are queued behind the pending work until all such requests reach a terminal state. When false (the default), any such tool calls and permission requests are immediately marked as interrupted on resume. + /// When true, tool calls and permission requests left in flight by the previous session lifetime remain pending after resume and the agentic loop awaits their results. User sends are queued behind the pending work until all such requests reach a terminal state. When false or omitted, pending work is normally marked as interrupted unless the resume passively joined live work owned by another client; sessionWasActive distinguishes that case. #[serde(skip_serializing_if = "Option::is_none")] pub continue_pending_work: Option, /// Total number of persisted events in the session at the time of resume @@ -759,7 +805,7 @@ pub struct SessionResumeData { /// Session limits currently configured at resume time; null when no limits are active #[serde(skip_serializing_if = "Option::is_none")] pub session_limits: Option, - /// True when this resume attached to a session that the runtime already had running in-memory (for example, an extension joining a session another client was actively driving). False (or omitted) for cold resumes — the runtime had to reconstitute the session from its persisted event log. + /// True when this resume passively joined a session that already had live work running in the runtime - an agent turn, a native queue run, a queued resume continuation, or an in-flight send (for example, an extension joining a session another client was actively driving). False (or omitted) when the session had no live work or when the resume explicitly abandoned pending work, including cold resumes and suspended sessions that remain resident in memory. #[serde(skip_serializing_if = "Option::is_none")] pub session_was_active: Option, /// Output verbosity level used for model calls, if applicable (e.g. "low", "medium", "high") @@ -841,6 +887,9 @@ pub struct SessionScheduleCreatedData { /// Interval between ticks in milliseconds (relative-interval schedules) #[serde(skip_serializing_if = "Option::is_none")] pub interval_ms: Option, + /// Who created the schedule (`user` or `model`). Persisted so a resumed session keeps gating non-user schedules from firing skills that opted out of model invocation. Absent on entries created before this field existed; a missing origin fails closed (treated the same as a non-user origin), so such a schedule may not resolve a `disable-model-invocation` skill. + #[serde(skip_serializing_if = "Option::is_none")] + pub origin: Option, /// Prompt text that gets enqueued on every tick pub prompt: String, /// Whether the schedule re-arms after each tick (`/every`) or fires once (`/after`) @@ -919,7 +968,7 @@ pub struct SessionWarningData { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionModelChangeData { - /// Reason the change happened, when not user-initiated. Currently `"rate_limit_auto_switch"` for changes triggered by the auto-mode-switch rate-limit recovery path. UI clients can use this to render contextual copy. + /// Reason the change happened, when not user-initiated. `"rate_limit_auto_switch"` for changes triggered by the auto-mode-switch rate-limit recovery path, or `"refusal_fallback"` when the active model declined a request (content refusal) and the runtime switched to the configured refusal-fallback model. UI clients can use this to render contextual copy. #[serde(skip_serializing_if = "Option::is_none")] pub cause: Option, /// Context tier after the model change; null explicitly clears a previously selected tier @@ -1289,6 +1338,9 @@ pub struct SessionContextChangedData { /// Hosting platform type of the repository (github or ado) #[serde(skip_serializing_if = "Option::is_none")] pub host_type: Option, + /// Set on the immediate preliminary event of a working-directory change, before the git context is resolved. A settled follow-up event (enriched with git context, or cwd-only for a non-repository) is always emitted afterward, so observers may defer to it. Absent on standalone/final events (e.g. relay context changes). + #[serde(skip_serializing_if = "Option::is_none")] + pub pending_git_context: Option, /// Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) #[serde(skip_serializing_if = "Option::is_none")] pub repository: Option, @@ -1321,6 +1373,17 @@ pub struct SessionUsageInfoData { pub tool_definitions_tokens: Option, } +/// Session event "session.context_cleared". Context-cleared details emitted when the host clears the conversation (the session.history.clearContext RPC / Session.clearContextMessages) +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionContextClearedData { + /// Optional initial message set after clearing + #[serde(skip_serializing_if = "Option::is_none")] + pub initial_message: Option, + /// Number of conversation messages that were cleared + pub messages_cleared: i64, +} + /// Session event "session.compaction_start". Context window breakdown at the start of LLM-powered conversation compaction #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -1328,15 +1391,24 @@ pub struct SessionCompactionStartData { /// Token count from non-system messages (user, assistant, tool) at compaction start #[serde(skip_serializing_if = "Option::is_none")] pub conversation_tokens: Option, + /// Total context tokens (system + conversation + tool definitions) at compaction start, when known + #[serde(skip_serializing_if = "Option::is_none")] + pub current_tokens: Option, /// Model identifier used for compaction, when known #[serde(skip_serializing_if = "Option::is_none")] pub model: Option, /// Token count from system message(s) at compaction start #[serde(skip_serializing_if = "Option::is_none")] pub system_tokens: Option, + /// Model context window token limit the compaction is targeting, when known + #[serde(skip_serializing_if = "Option::is_none")] + pub token_limit: Option, /// Token count from tool definitions at compaction start #[serde(skip_serializing_if = "Option::is_none")] pub tool_definitions_tokens: Option, + /// What initiated this compaction, when known + #[serde(skip_serializing_if = "Option::is_none")] + pub trigger: Option, } /// Token usage detail for a single billing category @@ -1445,19 +1517,34 @@ pub struct SessionCompactionCompleteData { /// Token count from system message(s) after compaction #[serde(skip_serializing_if = "Option::is_none")] pub system_tokens: Option, + /// Model context window token limit the compaction was targeting, when known + #[serde(skip_serializing_if = "Option::is_none")] + pub token_limit: Option, /// Number of tokens removed during compaction #[serde(skip_serializing_if = "Option::is_none")] pub tokens_removed: Option, /// Token count from tool definitions after compaction #[serde(skip_serializing_if = "Option::is_none")] pub tool_definitions_tokens: Option, + /// What initiated this compaction, when known + #[serde(skip_serializing_if = "Option::is_none")] + pub trigger: Option, } /// Session event "session.task_complete". Task completion notification with summary from the agent #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionTaskCompleteData { - /// Whether the tool call succeeded. False when validation failed (e.g., invalid arguments) + /// Active autopilot objective ID evaluated by the completion reviewer + #[serde(skip_serializing_if = "Option::is_none")] + pub objective_id: Option, + /// Semantic completion decision. Absent on legacy events and invalid tool calls + #[serde(skip_serializing_if = "Option::is_none")] + pub outcome: Option, + /// Label-safe runtime rationale for the completion decision (e.g. a cancellation or pause/resume downgrade), when one applies. Reviewer-authored rationale is intentionally omitted here because this event has no IFC label channel; the reviewer's findings remain available through its own labeled sub-agent events + #[serde(skip_serializing_if = "Option::is_none")] + pub reason: Option, + /// Whether the task was accepted as complete. False when validation failed or completion was rejected or blocked by the reviewer #[serde(skip_serializing_if = "Option::is_none")] pub success: Option, /// Summary of the completed task, provided by the agent @@ -1492,7 +1579,7 @@ pub struct UserMessageData { /// Parent agent task ID for background telemetry correlated to this user turn #[serde(skip_serializing_if = "Option::is_none")] pub parent_agent_task_id: Option, - /// Origin of this message, used for timeline filtering (e.g., "skill-pdf" for skill-injected messages that should be hidden from the user) + /// Origin of this message, used for timeline filtering and attribution (e.g., `skill-pdf` for hidden skill injection or `agent-` for an inter-agent prompt) #[serde(skip_serializing_if = "Option::is_none")] pub source: Option, /// Normalized document MIME types that were sent natively instead of through tagged_files XML @@ -1564,6 +1651,8 @@ pub struct AssistantReasoningData { pub content: String, /// Unique identifier for this reasoning block pub reasoning_id: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub rte: Option, } /// Session event "assistant.reasoning_delta". Streaming reasoning delta for incremental extended thinking updates @@ -1743,6 +1832,12 @@ pub struct AssistantMessageData { /// Provider's completion / response identifier; shared across all chunks of a single API call. Used to group multi-chunk assistant utterances. #[serde(skip_serializing_if = "Option::is_none")] pub api_call_id: Option, + /// Total messages the model call's response was split into, one per reasoning boundary. Absent for a single-message response; the last chunk is the one where chunkIndex is chunkCount - 1. + #[serde(skip_serializing_if = "Option::is_none")] + pub chunk_count: Option, + /// Zero-based position of this message within its model call's response. Absent when the response was not split into chunks. + #[serde(skip_serializing_if = "Option::is_none")] + pub chunk_index: Option, /// Provider-agnostic citations linking spans of this message's content to the sources that support them. Experimental; only populated when citation emission is enabled. /// ///
@@ -1792,6 +1887,8 @@ pub struct AssistantMessageData { /// GitHub request tracing ID (x-github-request-id header) for correlating with server-side logs #[serde(skip_serializing_if = "Option::is_none")] pub request_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub rte: Option, /// Neutral provider-tagged server-side tool-use payload (tool search, advisor) for verbatim round-tripping #[serde(skip_serializing_if = "Option::is_none")] pub server_tools: Option, @@ -1931,6 +2028,10 @@ pub struct AssistantUsageData { /// API endpoint used for this model call, matching CAPI supported_endpoints vocabulary #[serde(skip_serializing_if = "Option::is_none")] pub api_endpoint: Option, + /// Number of tools available to the model for this call + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) available_tool_count: Option, /// Updated prompt-cache expiration for this model call. Present only when the call establishes or refreshes known cache state. #[serde(skip_serializing_if = "Option::is_none")] pub cache_expires_at: Option, @@ -1968,11 +2069,18 @@ pub struct AssistantUsageData { /// Number of input tokens consumed #[serde(skip_serializing_if = "Option::is_none")] pub input_tokens: Option, + /// Coarse classification of the interaction that produced this call, mirroring the session's per-request agent context (e.g. `conversation-agent`, `conversation-subagent`, `conversation-sampling`, `conversation-background`, `conversation-compaction`, `conversation-user`). Non-billing; lets consumers attribute a model call to a call class (e.g. sub-agent/sidekick) independently of the billing initiator. Absent when the runtime did not classify the request. + #[serde(skip_serializing_if = "Option::is_none")] + pub interaction_type: Option, /// Average inter-token latency in milliseconds. Only available for streaming requests #[serde(skip_serializing_if = "Option::is_none")] pub inter_token_latency_ms: Option, /// Model identifier used for this API call pub model: String, + /// Number of tool calls returned by the model + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) num_tool_calls: Option, /// Number of output tokens produced #[serde(skip_serializing_if = "Option::is_none")] pub output_tokens: Option, @@ -1994,12 +2102,22 @@ pub struct AssistantUsageData { /// Number of output tokens used for reasoning (e.g., chain-of-thought) #[serde(skip_serializing_if = "Option::is_none")] pub reasoning_tokens: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub rte: Option, /// Copilot service request ID (x-copilot-service-request-id header) for CAPI log correlation #[serde(skip_serializing_if = "Option::is_none")] pub service_request_id: Option, /// Time to first token in milliseconds. Only available for streaming requests #[serde(skip_serializing_if = "Option::is_none")] pub time_to_first_token_ms: Option, + /// Tool-call counts keyed by tool name + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) tool_counts: Option>, + /// Number of tokens used by tool definitions for this call + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) tool_token_count: Option, } /// Content-free structural summary of the failing request for diagnosing malformed 4xx calls @@ -2082,6 +2200,8 @@ pub struct ModelCallFailureData { /// Content-free structural summary of the failing request. Contains only counts and shape flags (no prompt content), so it is safe for unrestricted telemetry. Populated only for client-error (4xx) failures. #[serde(skip_serializing_if = "Option::is_none")] pub request_fingerprint: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub rte: Option, /// Copilot service request ID (x-copilot-service-request-id header) for CAPI log correlation #[serde(skip_serializing_if = "Option::is_none")] pub service_request_id: Option, @@ -2102,6 +2222,10 @@ pub struct ModelCallStartData { /// Model identifier used for this API call, when known #[serde(skip_serializing_if = "Option::is_none")] pub model: Option, + /// Previous response or interaction identifier included in the model request, when present + #[doc(hidden)] + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) previous_response_id: Option, /// Identifier of the assistant turn that initiated the model call pub turn_id: String, } @@ -2131,6 +2255,16 @@ pub struct ToolUserRequestedData { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ToolExecutionStartShellToolInfo { + /// The command with a redundant leading `cd` into the working directory removed, present only when there was one to remove. Computed with the same routine the shell driver applies before spawning, so a surface that renders this shows the text that actually runs. Consumers that display it should keep the original tool arguments available on demand. + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(skip_serializing_if = "Option::is_none")] + pub display_command: Option, /// Whether the command includes a file write redirection (e.g., > or >>). pub has_write_file_redirection: bool, /// File paths the command may read or write, derived from the command at start time. Produced by the same shell-aware extractor as PermissionRequestShell.possiblePaths, so it is present even when the command is auto-approved and no permission request fires. @@ -2196,6 +2330,8 @@ pub struct ToolExecutionStartData { #[deprecated] #[serde(skip_serializing_if = "Option::is_none")] pub parent_tool_call_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub rte: Option, /// Shell-tool path hints derived from the command at start time for shell tools (bash/powershell/local_shell). Produced by the same shell-aware extractor as PermissionRequestShell.possiblePaths, so it is present even when the command is auto-approved and no permission request fires. Absent for non-shell tools. #[serde(skip_serializing_if = "Option::is_none")] pub shell_tool_info: Option, @@ -2636,6 +2772,8 @@ pub struct ToolExecutionCompleteData { /// Tool execution result on success #[serde(skip_serializing_if = "Option::is_none")] pub result: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub rte: Option, /// Whether this tool execution ran inside a sandbox container #[serde(skip_serializing_if = "Option::is_none")] pub sandboxed: Option, @@ -2722,6 +2860,9 @@ pub struct SubagentCompletedData { pub agent_display_name: String, /// Internal name of the sub-agent pub agent_name: String, + /// Whether the sub-agent was torn down by cancellation - its own abort, or an ancestor being killed - instead of finishing its work. Cancellation is not a failure, so the run still reports completion; this distinguishes a torn-down sub-agent from one that ran to the end. + #[serde(skip_serializing_if = "Option::is_none")] + pub cancelled: Option, /// Wall-clock duration of the sub-agent execution in milliseconds #[serde(skip_serializing_if = "Option::is_none")] pub duration_ms: Option, @@ -2877,6 +3018,9 @@ pub struct SystemMessageMetadata { pub struct SystemMessageData { /// The system or developer prompt text sent as model input pub content: String, + /// Logical interaction identifier for the model run receiving this prompt + #[serde(skip_serializing_if = "Option::is_none")] + pub interaction_id: Option, /// Metadata about the prompt template and its construction #[serde(skip_serializing_if = "Option::is_none")] pub metadata: Option, @@ -2907,6 +3051,16 @@ pub struct PermissionRequestShellCommand { pub read_only: bool, } +/// A parsed shell command segment used for argument-aware managed policy matching. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PermissionRequestShellCommandSegment { + /// Full text of this command segment, including arguments + pub full_command_text: String, + /// Command identifier (e.g., executable name) + pub identifier: String, +} + /// A URL that may be accessed by a command in a shell permission request. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -2923,6 +3077,9 @@ pub struct PermissionRequestShell { pub can_offer_session_approval: bool, /// Parsed command identifiers found in the command text pub commands: Vec, + /// Parsed command segments, including arguments, used for managed policy matching + #[serde(skip_serializing_if = "Option::is_none")] + pub command_segments: Option>, /// The complete shell command text to be executed pub full_command_text: String, /// Whether the command includes a file write redirection (e.g., > or >>) @@ -2931,6 +3088,9 @@ pub struct PermissionRequestShell { pub intention: String, /// Permission kind discriminator pub kind: PermissionRequestShellKind, + /// Whether managed policy requires a human response and forbids host auto-approval + #[serde(skip_serializing_if = "Option::is_none")] + pub managed_approval_required: Option, /// File paths that may be read or written by the command pub possible_paths: Vec, /// URLs that may be accessed by the command @@ -2963,6 +3123,9 @@ pub struct PermissionRequestWrite { pub intention: String, /// Permission kind discriminator pub kind: PermissionRequestWriteKind, + /// Whether managed policy requires a human response and forbids host auto-approval + #[serde(skip_serializing_if = "Option::is_none")] + pub managed_approval_required: Option, /// Complete new file contents for newly created files #[serde(skip_serializing_if = "Option::is_none")] pub new_file_contents: Option, @@ -2985,6 +3148,9 @@ pub struct PermissionRequestRead { pub intention: String, /// Permission kind discriminator pub kind: PermissionRequestReadKind, + /// Whether managed policy requires a human response and forbids host auto-approval + #[serde(skip_serializing_if = "Option::is_none")] + pub managed_approval_required: Option, /// Path of the file or directory being read pub path: String, /// True when the model has requested to run this search outside the sandbox (it set requestSandboxBypass: true and the host opted in via sandbox.allowBypass). This is a request, not a grant: the search runs unsandboxed only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI. @@ -3007,6 +3173,9 @@ pub struct PermissionRequestMcp { pub args: Option, /// Permission kind discriminator pub kind: PermissionRequestMcpKind, + /// When true, managed policy requires an explicit user decision and automatic approval must be bypassed. + #[serde(skip_serializing_if = "Option::is_none")] + pub managed_approval_required: Option, /// Whether this MCP tool is read-only (no side effects) pub read_only: bool, /// Name of the MCP server providing the tool @@ -3028,6 +3197,12 @@ pub struct PermissionRequestUrl { pub intention: String, /// Permission kind discriminator pub kind: PermissionRequestUrlKind, + /// Whether managed policy requires a human response and forbids host auto-approval + #[serde(skip_serializing_if = "Option::is_none")] + pub managed_approval_required: Option, + /// Immediately preceding URL when this request is for a redirect target + #[serde(skip_serializing_if = "Option::is_none")] + pub redirected_from: Option, /// True when this URL fetch is requesting to bypass the sandbox network policy: either the model set requestSandboxBypass: true, or the tool re-issued the request as an interactive bypass after the network policy denied the approved URL (host opted in via sandbox.allowBypass). This is a request, not a grant: the fetch runs only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI. #[serde(skip_serializing_if = "Option::is_none")] pub request_sandbox_bypass: Option, @@ -3058,6 +3233,9 @@ pub struct PermissionRequestMemory { pub fact: String, /// Permission kind discriminator pub kind: PermissionRequestMemoryKind, + /// When true, managed policy requires an explicit user decision and automatic approval must be bypassed. + #[serde(skip_serializing_if = "Option::is_none")] + pub managed_approval_required: Option, /// Reason for the vote (vote only) #[serde(skip_serializing_if = "Option::is_none")] pub reason: Option, @@ -3078,6 +3256,9 @@ pub struct PermissionRequestCustomTool { pub args: Option, /// Permission kind discriminator pub kind: PermissionRequestCustomToolKind, + /// When true, managed policy requires an explicit user decision and automatic approval must be bypassed. + #[serde(skip_serializing_if = "Option::is_none")] + pub managed_approval_required: Option, /// Tool call ID that triggered this permission request #[serde(skip_serializing_if = "Option::is_none")] pub tool_call_id: Option, @@ -3096,6 +3277,9 @@ pub struct PermissionRequestHook { pub hook_message: Option, /// Permission kind discriminator pub kind: PermissionRequestHookKind, + /// When true, managed policy requires an explicit user decision and automatic approval must be bypassed. + #[serde(skip_serializing_if = "Option::is_none")] + pub managed_approval_required: Option, /// Arguments of the tool call being gated #[serde(skip_serializing_if = "Option::is_none")] pub tool_args: Option, @@ -3115,6 +3299,9 @@ pub struct PermissionRequestExtensionManagement { pub extension_name: Option, /// Permission kind discriminator pub kind: PermissionRequestExtensionManagementKind, + /// When true, managed policy requires an explicit user decision and automatic approval must be bypassed. + #[serde(skip_serializing_if = "Option::is_none")] + pub managed_approval_required: Option, /// The extension management operation (scaffold, reload) pub operation: String, /// Tool call ID that triggered this permission request @@ -3122,6 +3309,63 @@ pub struct PermissionRequestExtensionManagement { pub tool_call_id: Option, } +/// A declared phase shown in a factory permission prompt. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct FactoryPermissionPhase { + /// Optional phase detail + #[serde(skip_serializing_if = "Option::is_none")] + pub detail: Option, + /// Phase title + pub title: String, +} + +/// Factory run or authoring permission request +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PermissionRequestFactory { + /// Canonical key used for scoped factory approvals + pub approval_key: String, + /// Whether this factory is eligible for persistent approval + pub can_persist_approval: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub declared_max_ai_credits: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub declared_max_concurrent_subagents: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub declared_max_total_subagents: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub declared_timeout_seconds: Option, + /// Factory description + pub description: String, + /// Permission kind discriminator + pub kind: PermissionRequestFactoryKind, + /// When true, managed policy requires an explicit user decision and automatic approval must be bypassed. + #[serde(skip_serializing_if = "Option::is_none")] + pub managed_approval_required: Option, + /// Effective AI-credit limit; omitted means unlimited + #[serde(skip_serializing_if = "Option::is_none")] + pub max_ai_credits: Option, + /// Effective concurrent-subagent limit; omitted means unlimited + #[serde(skip_serializing_if = "Option::is_none")] + pub max_concurrent_subagents: Option, + /// Effective total-subagent limit; omitted means unlimited + #[serde(skip_serializing_if = "Option::is_none")] + pub max_total_subagents: Option, + /// Factory name + pub name: String, + /// Factory operation, either run or author + pub operation: FactoryPermissionOperation, + /// Declared factory phases + pub phases: Vec, + /// Effective active-time limit in seconds; omitted means unlimited + #[serde(skip_serializing_if = "Option::is_none")] + pub timeout_seconds: Option, + /// Tool call ID that triggered this permission request + #[serde(skip_serializing_if = "Option::is_none")] + pub tool_call_id: Option, +} + /// Extension permission access request #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -3132,6 +3376,9 @@ pub struct PermissionRequestExtensionPermissionAccess { pub extension_name: String, /// Permission kind discriminator pub kind: PermissionRequestExtensionPermissionAccessKind, + /// When true, managed policy requires an explicit user decision and automatic approval must be bypassed. + #[serde(skip_serializing_if = "Option::is_none")] + pub managed_approval_required: Option, /// Tool call ID that triggered this permission request #[serde(skip_serializing_if = "Option::is_none")] pub tool_call_id: Option, @@ -3148,6 +3395,12 @@ pub struct PermissionRequestExtensionPermissionAccess { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PermissionAutoApproval { + /// Classified cause of an `error` recommendation. Absent for every other recommendation. + #[serde(skip_serializing_if = "Option::is_none")] + pub failure_reason: Option, + /// Model id that produced the recommendation, when the judge was consulted and reported one. Absent for `excluded` (the judge was not consulted) and for failures that occurred before a model was selected. + #[serde(skip_serializing_if = "Option::is_none")] + pub model: Option, /// Human-readable reason for the judge's recommendation, when available. #[serde(skip_serializing_if = "Option::is_none")] pub reason: Option, @@ -3179,6 +3432,9 @@ pub struct PermissionPromptRequestCommands { pub intention: String, /// Prompt kind discriminator pub kind: PermissionPromptRequestCommandsKind, + /// Whether managed policy requires a human response and forbids host auto-approval + #[serde(skip_serializing_if = "Option::is_none")] + pub managed_approval_required: Option, /// Tool call ID that triggered this permission request #[serde(skip_serializing_if = "Option::is_none")] pub tool_call_id: Option, @@ -3211,6 +3467,9 @@ pub struct PermissionPromptRequestWrite { pub intention: String, /// Prompt kind discriminator pub kind: PermissionPromptRequestWriteKind, + /// Whether managed policy requires a human response and forbids host auto-approval + #[serde(skip_serializing_if = "Option::is_none")] + pub managed_approval_required: Option, /// Complete new file contents for newly created files #[serde(skip_serializing_if = "Option::is_none")] pub new_file_contents: Option, @@ -3237,6 +3496,9 @@ pub struct PermissionPromptRequestRead { pub intention: String, /// Prompt kind discriminator pub kind: PermissionPromptRequestReadKind, + /// Whether managed policy requires a human response and forbids host auto-approval + #[serde(skip_serializing_if = "Option::is_none")] + pub managed_approval_required: Option, /// Path of the file or directory being read pub path: String, /// Tool call ID that triggered this permission request @@ -3292,6 +3554,12 @@ pub struct PermissionPromptRequestUrl { pub intention: String, /// Prompt kind discriminator pub kind: PermissionPromptRequestUrlKind, + /// Whether managed policy requires a human response and forbids host auto-approval + #[serde(skip_serializing_if = "Option::is_none")] + pub managed_approval_required: Option, + /// Immediately preceding URL when this prompt is for a redirect target + #[serde(skip_serializing_if = "Option::is_none")] + pub redirected_from: Option, /// True when this URL fetch is requesting to bypass the sandbox network policy: either the model set requestSandboxBypass: true, or the tool re-issued the request as an interactive bypass after the network policy denied the approved URL (host opted in via sandbox.allowBypass). This is a request, not a grant: the fetch runs only if the user approves this permission request. Hosts should highlight the elevated risk in the approval UI. #[serde(skip_serializing_if = "Option::is_none")] pub request_sandbox_bypass: Option, @@ -3451,6 +3719,62 @@ pub struct PermissionPromptRequestExtensionManagement { pub tool_call_id: Option, } +/// Factory run or authoring permission prompt +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PermissionPromptRequestFactory { + /// Canonical key used for scoped factory approvals + pub approval_key: String, + /// Auto-approval judge information for this request; present only when auto mode is enabled. + /// + ///
+ /// + /// **Experimental.** This type is part of an experimental wire-protocol surface + /// and may change or be removed in future SDK or CLI releases. + /// + ///
+ #[serde(skip_serializing_if = "Option::is_none")] + pub auto_approval: Option, + /// Whether this factory is eligible for persistent approval + pub can_persist_approval: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub declared_max_ai_credits: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub declared_max_concurrent_subagents: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub declared_max_total_subagents: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub declared_timeout_seconds: Option, + /// Factory description + pub description: String, + /// Prompt kind discriminator + pub kind: PermissionPromptRequestFactoryKind, + /// Whether managed policy requires a human response and forbids host auto-approval + #[serde(skip_serializing_if = "Option::is_none")] + pub managed_approval_required: Option, + /// Effective AI-credit limit; omitted means unlimited + #[serde(skip_serializing_if = "Option::is_none")] + pub max_ai_credits: Option, + /// Effective concurrent-subagent limit; omitted means unlimited + #[serde(skip_serializing_if = "Option::is_none")] + pub max_concurrent_subagents: Option, + /// Effective total-subagent limit; omitted means unlimited + #[serde(skip_serializing_if = "Option::is_none")] + pub max_total_subagents: Option, + /// Factory name + pub name: String, + /// Factory operation, either run or author + pub operation: FactoryPermissionOperation, + /// Declared factory phases + pub phases: Vec, + /// Effective active-time limit in seconds; omitted means unlimited + #[serde(skip_serializing_if = "Option::is_none")] + pub timeout_seconds: Option, + /// Tool call ID that triggered this permission request + #[serde(skip_serializing_if = "Option::is_none")] + pub tool_call_id: Option, +} + /// Extension permission access prompt #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -3490,6 +3814,9 @@ pub struct PermissionRequestedData { /// When true, this permission was already resolved by a permissionRequest hook and requires no client action #[serde(skip_serializing_if = "Option::is_none")] pub resolved_by_hook: Option, + /// Neutral risk metadata supplied by the tool host. Consumers may display this value but must not use it to bypass the permission decision. + #[serde(skip_serializing_if = "Option::is_none")] + pub risk_assessment: Option, } /// Permission response variant indicating the request was approved without persisting an approval rule. @@ -3567,6 +3894,17 @@ pub struct UserToolSessionApprovalExtensionManagement { pub operation: Option, } +/// Session-scoped factory approval, optionally narrowed by approval key. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UserToolSessionApprovalFactory { + /// Optional factory operation name or canonical approval key + #[serde(skip_serializing_if = "Option::is_none")] + pub approval_key: Option, + /// Factory approval kind + pub kind: UserToolSessionApprovalFactoryKind, +} + /// Session-scoped tool-approval rule for an extension's permission-gated capability access, keyed by extension name. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -4066,6 +4404,9 @@ pub struct SessionLimitsExhaustedCompletedData { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionAutoModeResolvedData { + /// Models offered to the router for this resolution + #[serde(skip_serializing_if = "Option::is_none")] + pub available_models: Option>, /// Ordered candidate model list the router returned, when not a fallback #[serde(skip_serializing_if = "Option::is_none")] pub candidate_models: Option>, @@ -4074,18 +4415,42 @@ pub struct SessionAutoModeResolvedData { pub category_scores: Option>, /// The concrete model the session will use after any intent refinement pub chosen_model: String, + /// The chosen model's score shortfall relative to the top candidate + #[serde(skip_serializing_if = "Option::is_none")] + pub chosen_shortfall: Option, /// Classifier confidence for the predicted label, when available #[serde(skip_serializing_if = "Option::is_none")] pub confidence: Option, + /// End-to-end client wait time for the router request in milliseconds + #[serde(skip_serializing_if = "Option::is_none")] + pub end_to_end_latency_ms: Option, + /// Whether the router fell back to the standard Auto selection + #[serde(skip_serializing_if = "Option::is_none")] + pub fallback: Option, + /// Server-provided reason for falling back, when available + #[serde(skip_serializing_if = "Option::is_none")] + pub fallback_reason: Option, + /// Whether the routed prompt contained an image + #[serde(skip_serializing_if = "Option::is_none")] + pub has_image: Option, /// The predicted classifier label (e.g. `needs_reasoning`), when available #[serde(skip_serializing_if = "Option::is_none")] pub predicted_label: Option, /// Coarse request-difficulty bucket, for explaining why a model was chosen ("picked X because this looks like high-reasoning work") #[serde(skip_serializing_if = "Option::is_none")] pub reasoning_bucket: Option, + /// Server-reported router processing time in milliseconds + #[serde(skip_serializing_if = "Option::is_none")] + pub router_latency_ms: Option, + /// The routing method the server applied, when Auto Intent ran + #[serde(skip_serializing_if = "Option::is_none")] + pub routing_method: Option, + /// Whether a sticky model choice overrode the router result + #[serde(skip_serializing_if = "Option::is_none")] + pub sticky_override: Option, } -/// Session event "session.managed_settings_resolved". Enterprise managed-settings resolution: the effective managed settings the session applied and where they came from, so SDK clients can show users what is enterprise-managed and by which authority. Fires whenever managed policy is (re)applied — at session start, on resume, and on account switch. This is an ephemeral live snapshot (delivered to subscribers but not persisted to the session event log), because at session start it resolves before `session.start` is emitted; for a session-independent pull, use the SDK `getManagedSettings()` API, which returns the identical payload. Managed settings have a single authoritative source, so the highest-authority present layer (server > device) wins wholesale; `bypassPermissionsDisabled` is deny-wins across layers. Marked experimental while the managed-settings surface stabilizes. +/// Session event "session.managed_settings_resolved". Enterprise managed-settings resolution: the effective managed settings the session applied and which channels contributed, so SDK clients can show users what is enterprise-managed. Fires whenever managed policy is (re)applied — at session start, on resume, and on account switch. This is an ephemeral live snapshot (delivered to subscribers but not persisted to the session event log), because at session start it resolves before `session.start` is emitted. Device values take precedence over server values per ordinary key, while permissions compose restrictively across device, server, and SDK-client layers. The account-scoped `getManagedSettings()` API does not include session-local client injection. Marked experimental while the managed-settings surface stabilizes. /// ///
/// @@ -4098,18 +4463,24 @@ pub struct SessionAutoModeResolvedData { pub struct SessionManagedSettingsResolvedData { /// Whether enterprise policy disables bypass-permissions ("yolo") mode for this session. Deny-wins across layers, and forced on when `failClosed` is true. pub bypass_permissions_disabled: bool, - /// Whether the device (MDM/plist/registry/file) managed-settings layer was present + /// Whether a session-local permissions layer injected by the SDK host was present + #[serde(skip_serializing_if = "Option::is_none")] + pub client_managed: Option, + /// Whether an actual device MDM/plist/registry/file managed-settings layer was present pub device_managed: bool, /// Whether managed policy could not be determined (e.g. a failed server fetch) and the session fell back to the fail-closed restriction. When true, restrictions such as disabling bypass-permissions are enforced even though `settings` may be absent. pub fail_closed: bool, /// The setting keys under enterprise management in the effective managed settings (e.g. `model`, `enabledPlugins`, `permissions`). Empty when no managed settings are in force. pub managed_keys: Vec, + /// Whether at least two managed sources supplied permission allowlists, so enforcement intersects them and the flattened settings payload omits `permissions.allow`. + #[serde(skip_serializing_if = "Option::is_none")] + pub permissions_allow_intersected: Option, /// Whether the server (account/org) managed-settings layer was present pub server_managed: bool, /// The effective (resolved) managed settings values, so clients can render exactly what is enforced. Absent when no managed policy is in force. #[serde(skip_serializing_if = "Option::is_none")] pub settings: Option, - /// Which channel supplied the effective managed settings (the winning layer), or `none` when no policy is in force + /// Channel summary: `server`, `device`, or `client` when exactly one channel contributed; `mixed` when multiple channels contributed; otherwise `none`. Consult the per-channel booleans for exact provenance. pub source: ManagedSettingsResolvedSource, } @@ -4229,6 +4600,22 @@ pub struct SessionToolsUpdatedData { #[serde(rename_all = "camelCase")] pub struct SessionBackgroundTasksChangedData {} +/// Session event "factory.run_updated". Ephemeral invalidation signal for a changed factory run. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct FactoryRunUpdatedData { + /// Monotonic revision now available for the run. + pub revision: i64, + pub run_id: String, +} + /// A single resolved skill in `session.skills_loaded`, including source, invocability, enabled state, path, and argument hint. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -4236,6 +4623,9 @@ pub struct SkillsLoadedSkill { /// Optional freeform hint describing the skill's expected arguments, from the `argument-hint` frontmatter field #[serde(skip_serializing_if = "Option::is_none")] pub argument_hint: Option, + /// Canonical slash command name used to invoke the skill, without the leading '/' + #[serde(skip_serializing_if = "Option::is_none")] + pub command_name: Option, /// Description of what the skill does pub description: String, /// Whether the skill is currently enabled @@ -4312,7 +4702,7 @@ pub struct McpServersLoadedServer { /// Configuration source: user, workspace, plugin, or builtin #[serde(skip_serializing_if = "Option::is_none")] pub source: Option, - /// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured + /// Connection status: connected, failed, needs-auth, pending, disabled, stopped, or not_configured pub status: McpServerStatus, /// Transport mechanism: stdio, http, sse (deprecated), or memory (in-process MCP server) #[serde(skip_serializing_if = "Option::is_none")] @@ -4336,7 +4726,7 @@ pub struct SessionMcpServerStatusChangedData { pub error: Option, /// Name of the MCP server whose status changed pub server_name: String, - /// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured + /// Connection status: connected, failed, needs-auth, pending, disabled, stopped, or not_configured pub status: McpServerStatus, } @@ -4703,6 +5093,21 @@ pub enum Verbosity { Unknown, } +/// Who created the schedule: `user` (an explicit user action such as `/every` or `/after`) or `model` (the agent via the `manage_schedule` tool). Gates whether a scheduled skill that opted out of model invocation may fire: only user-created schedules may. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum ScheduleOrigin { + /// The schedule was created by an explicit user action, such as `/every` or `/after`. + #[serde(rename = "user")] + User, + /// The schedule was created by the agent via the `manage_schedule` tool. + #[serde(rename = "model")] + Model, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// The type of operation performed on the autopilot objective state file #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum AutopilotObjectiveChangedOperation { @@ -4848,6 +5253,48 @@ pub enum ShutdownType { Unknown, } +/// What initiated a conversation compaction +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CompactionTrigger { + /// Background compaction started automatically because context utilization crossed the background threshold. + #[serde(rename = "threshold")] + Threshold, + /// Compaction forced by a context-limit model response (e.g. HTTP 413) before retrying the request. + #[serde(rename = "context_limit_retry")] + ContextLimitRetry, + /// User-requested compaction, e.g. the /compact command or the history.compact API. + #[serde(rename = "manual")] + Manual, + /// Emergency compaction triggered by high process memory usage. + #[serde(rename = "memory_pressure")] + MemoryPressure, + /// Compaction requested while switching to a model with a smaller context window. + #[serde(rename = "model_switch")] + ModelSwitch, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Semantic result of evaluating a task completion request +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum TaskCompletionOutcome { + /// The completion request was accepted and the objective is complete. + #[serde(rename = "completed")] + Completed, + /// The completion request was rejected because more work or validation remains. + #[serde(rename = "continue")] + Continue, + /// Completion cannot proceed without intervention; the active objective is paused when one is identified. + #[serde(rename = "blocked")] + Blocked, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// The agent mode that was active when this message was sent #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum UserMessageAgentMode { @@ -5023,6 +5470,9 @@ pub enum AbortReason { /// An MCP server delivered a user.abort notification. #[serde(rename = "user_abort")] UserAbort, + /// Autopilot stopped the run because the active objective reached its user-set --max-ai-credits limit. + #[serde(rename = "autopilot_credit_limit")] + AutopilotCreditLimit, /// Unknown variant for forward compatibility. #[default] #[serde(other)] @@ -5301,6 +5751,29 @@ pub enum PermissionRequestExtensionManagementKind { ExtensionManagement, } +/// Permission kind discriminator +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum PermissionRequestFactoryKind { + #[serde(rename = "factory")] + #[default] + Factory, +} + +/// Operation gated by a factory permission request. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum FactoryPermissionOperation { + /// Running a registered factory, which spends subagents, active time, and AI credits under the approved limits. + #[serde(rename = "run")] + Run, + /// Authoring a factory, which writes JavaScript into a session-scoped extension and loads it. + #[serde(rename = "author")] + Author, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// Permission kind discriminator #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum PermissionRequestExtensionPermissionAccessKind { @@ -5322,9 +5795,41 @@ pub enum PermissionRequest { CustomTool(PermissionRequestCustomTool), Hook(PermissionRequestHook), ExtensionManagement(PermissionRequestExtensionManagement), + Factory(PermissionRequestFactory), ExtensionPermissionAccess(PermissionRequestExtensionPermissionAccess), } +/// Why the auto-approval judge produced no usable recommendation. Present only alongside an `error` recommendation, where the human-readable reason is a fixed string and therefore cannot distinguish these cases. Intended to make a judge failure reportable by a consumer that has no access to the host's logs. +/// +///
+/// +/// **Experimental.** This type is part of an experimental wire-protocol surface +/// and may change or be removed in future SDK or CLI releases. +/// +///
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum AutoApprovalJudgeFailureReason { + /// The judge model call exceeded its deadline. + #[serde(rename = "timeout")] + Timeout, + /// The judge model call was cancelled before it returned. + #[serde(rename = "abort")] + Abort, + /// The judge model call completed but returned no content. + #[serde(rename = "empty_response")] + EmptyResponse, + /// The judge model call failed (for example a transport, authentication, or rate-limit error). + #[serde(rename = "model_error")] + ModelError, + /// The judge model replied, but the reply carried no ALLOW/DENY verdict. + #[serde(rename = "parse_error")] + ParseError, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// Outcome of the auto-approval safety judge for a permission request. Present only when auto mode is enabled; its absence means the judge did not evaluate the request (auto mode was off). /// ///
@@ -5451,6 +5956,14 @@ pub enum PermissionPromptRequestExtensionManagementKind { ExtensionManagement, } +/// Prompt kind discriminator +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum PermissionPromptRequestFactoryKind { + #[serde(rename = "factory")] + #[default] + Factory, +} + /// Prompt kind discriminator #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum PermissionPromptRequestExtensionPermissionAccessKind { @@ -5473,6 +5986,7 @@ pub enum PermissionPromptRequest { Path(PermissionPromptRequestPath), Hook(PermissionPromptRequestHook), ExtensionManagement(PermissionPromptRequestExtensionManagement), + Factory(PermissionPromptRequestFactory), ExtensionPermissionAccess(PermissionPromptRequestExtensionPermissionAccess), } @@ -5540,6 +6054,14 @@ pub enum UserToolSessionApprovalExtensionManagementKind { ExtensionManagement, } +/// Factory approval kind +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum UserToolSessionApprovalFactoryKind { + #[serde(rename = "factory")] + #[default] + Factory, +} + /// Extension permission access approval kind #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum UserToolSessionApprovalExtensionPermissionAccessKind { @@ -5559,6 +6081,7 @@ pub enum UserToolSessionApproval { Memory(UserToolSessionApprovalMemory), CustomTool(UserToolSessionApprovalCustomTool), ExtensionManagement(UserToolSessionApprovalExtensionManagement), + Factory(UserToolSessionApprovalFactory), ExtensionPermissionAccess(UserToolSessionApprovalExtensionPermissionAccess), } @@ -5821,16 +6344,22 @@ pub enum AutoModeResolvedReasoningBucket { Unknown, } -/// Which channel supplied the effective enterprise managed settings (highest-authority present layer wins wholesale) +/// Summary of which managed-settings channels contributed to the effective session policy. Use the per-channel booleans for exact provenance. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum ManagedSettingsResolvedSource { - /// Account/org policy self-fetched from the GitHub managed-settings endpoint (higher authority). + /// Only the server/account channel contributed. #[serde(rename = "server")] Server, - /// Device-level MDM policy discovered from plist/registry/file (lower authority). + /// Only the device MDM/plist/registry/file channel contributed. #[serde(rename = "device")] Device, - /// No managed policy is in force (no layer contributed). + /// Only session-local SDK-host injection contributed. + #[serde(rename = "client")] + Client, + /// More than one channel contributed. Ordinary keys resolve device over server per key, while permissions compose restrictively across all present layers. + #[serde(rename = "mixed")] + Mixed, + /// No managed policy is in force (no channel contributed). #[serde(rename = "none")] None, /// Unknown variant for forward compatibility. @@ -5947,7 +6476,7 @@ pub enum McpServerSource { Unknown, } -/// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured +/// Connection status: connected, failed, needs-auth, pending, disabled, stopped, or not_configured #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum McpServerStatus { /// The server is connected and available. @@ -5965,6 +6494,9 @@ pub enum McpServerStatus { /// The server is configured but disabled. #[serde(rename = "disabled")] Disabled, + /// The server was intentionally stopped and can be restarted on demand when policy permits; a server quarantined by restrictive managed policy stays stopped and cannot be restarted until the policy allows it. + #[serde(rename = "stopped")] + Stopped, /// The server is not configured for this session. #[serde(rename = "not_configured")] NotConfigured, diff --git a/rust/src/handler.rs b/rust/src/handler.rs index 3287a4f09..f1f0d9566 100644 --- a/rust/src/handler.rs +++ b/rust/src/handler.rs @@ -22,7 +22,7 @@ use crate::generated::api_types::{ McpOauthPendingRequestResponse, McpOauthPendingRequestResponseCancelled, McpOauthPendingRequestResponseCancelledKind, McpOauthPendingRequestResponseToken, McpOauthPendingRequestResponseTokenKind, PermissionDecision, PermissionDecisionApproveOnce, - PermissionDecisionReject, PermissionDecisionUserNotAvailable, + PermissionDecisionContext, PermissionDecisionReject, PermissionDecisionUserNotAvailable, }; use crate::session_events::{ McpOauthRequestReason, McpOauthRequiredStaticClientConfig, McpOauthWWWAuthenticateParams, @@ -35,13 +35,30 @@ use crate::types::{ /// Decision returned by a [`PermissionHandler`]. /// /// Either a concrete wire-level [`PermissionDecision`] (approve, reject, -/// approve-for-session, approve-permanently, user-not-available, …) or -/// [`PermissionResult::NoResult`], which tells the SDK to suppress its -/// response so another connected client can answer instead. +/// approve-for-session, approve-permanently, user-not-available, …) with +/// optional telemetry context, or [`PermissionResult::NoResult`], which tells +/// the SDK to suppress its response so another connected client can answer +/// instead. +/// +/// ``` +/// use github_copilot_sdk::handler::PermissionResult; +/// +/// fn is_decision(result: PermissionResult) -> bool { +/// match result { +/// PermissionResult::Decision { .. } => true, +/// PermissionResult::NoResult => false, +/// } +/// } +/// ``` #[derive(Debug, Clone)] pub enum PermissionResult { /// Send a permission decision on the wire. - Decision(PermissionDecision), + Decision { + /// The decision to send. + decision: PermissionDecision, + /// Optional context describing how and where the decision was reached. + context: Option, + }, /// Decline to respond to this request, allowing another connected /// client to answer instead. The SDK suppresses the response. NoResult, @@ -50,24 +67,31 @@ pub enum PermissionResult { impl PermissionResult { /// Approve this single request. pub fn approve_once() -> Self { - Self::Decision(PermissionDecision::ApproveOnce( - PermissionDecisionApproveOnce::default(), - )) + Self::Decision { + decision: PermissionDecision::ApproveOnce(PermissionDecisionApproveOnce::default()), + context: None, + } } /// Reject the request, optionally forwarding feedback to the LLM. pub fn reject(feedback: impl Into>) -> Self { - Self::Decision(PermissionDecision::Reject(PermissionDecisionReject { - feedback: feedback.into(), - ..Default::default() - })) + Self::Decision { + decision: PermissionDecision::Reject(PermissionDecisionReject { + feedback: feedback.into(), + ..Default::default() + }), + context: None, + } } /// Deny because no user is available to confirm. pub fn user_not_available() -> Self { - Self::Decision(PermissionDecision::UserNotAvailable( - PermissionDecisionUserNotAvailable::default(), - )) + Self::Decision { + decision: PermissionDecision::UserNotAvailable( + PermissionDecisionUserNotAvailable::default(), + ), + context: None, + } } /// Decline to respond, allowing another connected client to answer @@ -75,14 +99,50 @@ impl PermissionResult { pub fn no_result() -> Self { Self::NoResult } + + /// Attach provenance describing how and where this decision was made, + /// so the runtime can attribute auto-approval telemetry. + /// + /// It is a no-op on [`PermissionResult::NoResult`]. + /// + /// ```rust,no_run + /// # use github_copilot_sdk::handler::PermissionResult; + /// # use github_copilot_sdk::{ + /// # PermissionDecisionContext, PermissionDecisionOutcome, PermissionDecisionSource, + /// # PermissionDecisionSurface, + /// # }; + /// + /// let result = PermissionResult::approve_once().with_context(PermissionDecisionContext { + /// outcome: PermissionDecisionOutcome::AutoApproved, + /// source: PermissionDecisionSource::HostPolicy, + /// surface: PermissionDecisionSurface::Sdk, + /// }); + /// ``` + pub fn with_context(self, context: PermissionDecisionContext) -> Self { + match self { + Self::Decision { decision, .. } => Self::Decision { + decision, + context: Some(context), + }, + Self::NoResult => Self::NoResult, + } + } } impl From for PermissionResult { fn from(value: PermissionDecision) -> Self { - Self::Decision(value) + Self::Decision { + decision: value, + context: None, + } } } +pub(crate) fn permission_handler_failure(message: &str) -> PermissionResult { + tracing::error!(error = message, "permission handler failed"); + PermissionResult::user_not_available() +} + /// Response to a user input request. #[derive(Debug, Clone)] pub struct UserInputResponse { @@ -273,9 +333,12 @@ pub trait AutoModeSwitchHandler: Send + Sync + 'static { ) -> AutoModeSwitchResponse; } -/// A [`PermissionHandler`] that approves every request. Useful for CLI -/// tools, scripts, and tests that don't need interactive permission -/// prompts. +/// A [`PermissionHandler`] that approves ordinary requests when managed settings are disabled. +/// +/// When managed settings are enabled, the handler logs an error and returns a +/// user-not-available decision. As a defense-in-depth fallback, a request marked +/// as requiring managed approval is left unanswered even if the session flag is +/// absent. #[derive(Debug, Clone)] pub struct ApproveAllHandler; @@ -285,9 +348,17 @@ impl PermissionHandler for ApproveAllHandler { &self, _session_id: SessionId, _request_id: RequestId, - _data: PermissionRequestData, + data: PermissionRequestData, ) -> PermissionResult { - PermissionResult::approve_once() + if data.managed_settings_enabled { + permission_handler_failure( + "ApproveAllHandler cannot be used when managed settings are enabled", + ) + } else if data.managed_approval_required == Some(true) { + PermissionResult::no_result() + } else { + PermissionResult::approve_once() + } } } @@ -322,10 +393,49 @@ mod tests { .await; assert!(matches!( result, - PermissionResult::Decision(PermissionDecision::ApproveOnce(_)) + PermissionResult::Decision { + decision: PermissionDecision::ApproveOnce(_), + .. + } )); } + #[tokio::test] + async fn approve_all_handler_fails_when_managed_settings_enabled() { + let result = ApproveAllHandler + .handle( + SessionId::from("s1"), + RequestId::new("1"), + PermissionRequestData { + managed_settings_enabled: true, + ..Default::default() + }, + ) + .await; + assert!(matches!( + result, + PermissionResult::Decision { + decision: PermissionDecision::UserNotAvailable(_), + .. + } + )); + } + + #[tokio::test] + async fn approve_all_handler_leaves_managed_approval_pending() { + let result = ApproveAllHandler + .handle( + SessionId::from("s1"), + RequestId::new("1"), + PermissionRequestData { + managed_approval_required: Some(true), + ..Default::default() + }, + ) + .await; + assert!(matches!(result, PermissionResult::NoResult)); + } + #[tokio::test] async fn deny_all_handler_returns_denied() { let result = DenyAllHandler @@ -337,7 +447,10 @@ mod tests { .await; assert!(matches!( result, - PermissionResult::Decision(PermissionDecision::Reject(_)) + PermissionResult::Decision { + decision: PermissionDecision::Reject(_), + .. + } )); } diff --git a/rust/src/hooks.rs b/rust/src/hooks.rs index 0c3d64076..4986d6cb1 100644 --- a/rust/src/hooks.rs +++ b/rust/src/hooks.rs @@ -199,6 +199,32 @@ pub struct UserPromptSubmittedOutput { pub suppress_output: Option, } +/// Input for the `userPromptTransformed` hook. +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UserPromptTransformedInput { + /// The runtime session ID of the session that triggered the hook. + pub session_id: String, + /// Unix timestamp in ms. + pub timestamp: f64, + /// Working directory. + #[serde(rename = "cwd")] + pub working_directory: PathBuf, + /// The prompt after any `userPromptSubmitted` hooks have run. + pub prompt: String, + /// The model-facing prompt after runtime transformations. + pub transformed_prompt: String, +} + +/// Output for the `userPromptTransformed` hook. +#[derive(Debug, Clone, Default, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct UserPromptTransformedOutput { + /// Replacement model-facing prompt to persist and send to the model. + #[serde(skip_serializing_if = "Option::is_none")] + pub modified_transformed_prompt: Option, +} + /// Input for the `sessionStart` hook. #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] @@ -302,6 +328,40 @@ pub struct ErrorOccurredOutput { pub user_notification: Option, } +/// Input for the `agentStop` hook, received when the top-level agent reaches a natural stop. +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AgentStopInput { + /// The runtime session ID of the session that triggered the hook. + pub session_id: String, + /// Unix timestamp in ms (the runtime serializes this as a JSON float). + pub timestamp: f64, + /// Working directory. + #[serde(rename = "cwd")] + pub working_directory: PathBuf, + /// Reason the agent stopped. + #[serde(default)] + pub stop_reason: Option, + /// Path to the on-disk session transcript. + #[serde(default)] + pub transcript_path: Option, + /// Whether this stop follows a previous block decision from the hook. + #[serde(default, rename = "stop_hook_active")] + pub stop_hook_active: Option, +} + +/// Output for the `agentStop` hook. +#[derive(Debug, Clone, Default, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AgentStopOutput { + /// Set to `"block"` to keep the agent running. + #[serde(skip_serializing_if = "Option::is_none")] + pub decision: Option, + /// Follow-up instruction supplied when the stop is blocked. + #[serde(skip_serializing_if = "Option::is_none")] + pub reason: Option, +} + /// Events dispatched to [`SessionHooks::on_hook`] at CLI lifecycle points. /// /// Each variant carries the typed input for that hook plus the shared @@ -347,6 +407,13 @@ pub enum HookEvent { /// Session context. ctx: HookContext, }, + /// Fired after the runtime transforms a submitted prompt. + UserPromptTransformed { + /// Typed input data. + input: UserPromptTransformedInput, + /// Session context. + ctx: HookContext, + }, /// Fired at session creation or resume. SessionStart { /// Typed input data. @@ -368,6 +435,13 @@ pub enum HookEvent { /// Session context. ctx: HookContext, }, + /// Fired when the top-level agent reaches a natural stop. + AgentStop { + /// Typed input data. + input: AgentStopInput, + /// Session context. + ctx: HookContext, + }, } /// Response from [`SessionHooks::on_hook`] back to the SDK. @@ -389,12 +463,16 @@ pub enum HookOutput { PostToolUseFailure(PostToolUseFailureOutput), /// Response for a user-prompt-submitted hook. UserPromptSubmitted(UserPromptSubmittedOutput), + /// Response for a user-prompt-transformed hook. + UserPromptTransformed(UserPromptTransformedOutput), /// Response for a session-start hook. SessionStart(SessionStartOutput), /// Response for a session-end hook. SessionEnd(SessionEndOutput), /// Response for an error-occurred hook. ErrorOccurred(ErrorOccurredOutput), + /// Response for an agent-stop hook. + AgentStop(AgentStopOutput), } impl HookOutput { @@ -406,9 +484,11 @@ impl HookOutput { Self::PostToolUse(_) => "PostToolUse", Self::PostToolUseFailure(_) => "PostToolUseFailure", Self::UserPromptSubmitted(_) => "UserPromptSubmitted", + Self::UserPromptTransformed(_) => "UserPromptTransformed", Self::SessionStart(_) => "SessionStart", Self::SessionEnd(_) => "SessionEnd", Self::ErrorOccurred(_) => "ErrorOccurred", + Self::AgentStop(_) => "AgentStop", } } } @@ -462,6 +542,11 @@ pub trait SessionHooks: Send + Sync + 'static { .await .map(HookOutput::UserPromptSubmitted) .unwrap_or(HookOutput::None), + HookEvent::UserPromptTransformed { input, ctx } => self + .on_user_prompt_transformed(input, ctx) + .await + .map(HookOutput::UserPromptTransformed) + .unwrap_or(HookOutput::None), HookEvent::SessionStart { input, ctx } => self .on_session_start(input, ctx) .await @@ -477,6 +562,11 @@ pub trait SessionHooks: Send + Sync + 'static { .await .map(HookOutput::ErrorOccurred) .unwrap_or(HookOutput::None), + HookEvent::AgentStop { input, ctx } => self + .on_agent_stop(input, ctx) + .await + .map(HookOutput::AgentStop) + .unwrap_or(HookOutput::None), } } @@ -534,6 +624,16 @@ pub trait SessionHooks: Send + Sync + 'static { None } + /// Called after the runtime transforms a submitted prompt. Return + /// `Some(output)` to replace the model-facing content before it is stored. + async fn on_user_prompt_transformed( + &self, + _input: UserPromptTransformedInput, + _ctx: HookContext, + ) -> Option { + None + } + /// Called at session creation or resume. Return `Some(output)` to /// inject startup context. async fn on_session_start( @@ -563,6 +663,16 @@ pub trait SessionHooks: Send + Sync + 'static { ) -> Option { None } + + /// Called when the top-level agent reaches a natural stop. Return a block + /// decision to keep the agent running with a follow-up instruction. + async fn on_agent_stop( + &self, + _input: AgentStopInput, + _ctx: HookContext, + ) -> Option { + None + } } /// Dispatches a `hooks.invoke` request to [`SessionHooks::on_hook`]. @@ -601,6 +711,10 @@ pub(crate) async fn dispatch_hook( let input: UserPromptSubmittedInput = serde_json::from_value(raw_input)?; HookEvent::UserPromptSubmitted { input, ctx } } + "userPromptTransformed" => { + let input: UserPromptTransformedInput = serde_json::from_value(raw_input)?; + HookEvent::UserPromptTransformed { input, ctx } + } "sessionStart" => { let input: SessionStartInput = serde_json::from_value(raw_input)?; HookEvent::SessionStart { input, ctx } @@ -613,6 +727,10 @@ pub(crate) async fn dispatch_hook( let input: ErrorOccurredInput = serde_json::from_value(raw_input)?; HookEvent::ErrorOccurred { input, ctx } } + "agentStop" => { + let input: AgentStopInput = serde_json::from_value(raw_input)?; + HookEvent::AgentStop { input, ctx } + } _ => { tracing::warn!( hook_type = hook_type, @@ -645,9 +763,13 @@ pub(crate) async fn dispatch_hook( ("userPromptSubmitted", HookOutput::UserPromptSubmitted(o)) => { Some(serde_json::to_value(o)?) } + ("userPromptTransformed", HookOutput::UserPromptTransformed(o)) => { + Some(serde_json::to_value(o)?) + } ("sessionStart", HookOutput::SessionStart(o)) => Some(serde_json::to_value(o)?), ("sessionEnd", HookOutput::SessionEnd(o)) => Some(serde_json::to_value(o)?), ("errorOccurred", HookOutput::ErrorOccurred(o)) => Some(serde_json::to_value(o)?), + ("agentStop", HookOutput::AgentStop(o)) => Some(serde_json::to_value(o)?), _ => { tracing::warn!( hook_type = hook_type, @@ -689,6 +811,14 @@ mod tests { ..Default::default() }) } + HookEvent::UserPromptTransformed { input, .. } => { + HookOutput::UserPromptTransformed(UserPromptTransformedOutput { + modified_transformed_prompt: Some(format!( + "[transformed] {}", + input.transformed_prompt + )), + }) + } _ => HookOutput::None, } } @@ -749,6 +879,30 @@ mod tests { assert_eq!(result["output"]["modifiedPrompt"], "[prefixed] hello world"); } + #[tokio::test] + async fn dispatch_user_prompt_transformed() { + let hooks = TestHooks; + let input = serde_json::json!({ + "sessionId": "sess-1", + "timestamp": 1234567890, + "cwd": "/tmp", + "prompt": "hello world", + "transformedPrompt": "now\nhello world" + }); + let result = dispatch_hook( + &hooks, + &SessionId::new("sess-1"), + "userPromptTransformed", + input, + ) + .await + .unwrap(); + assert_eq!( + result["output"]["modifiedTransformedPrompt"], + "[transformed] now\nhello world" + ); + } + #[tokio::test] async fn dispatch_unregistered_hook_returns_empty() { let hooks = TestHooks; @@ -981,4 +1135,50 @@ mod tests { assert_eq!(result["output"]["errorHandling"], "retry"); assert_eq!(result["output"]["retryCount"], 3); } + + #[tokio::test] + async fn dispatch_agent_stop_block() { + struct AgentStopHooks; + #[async_trait] + impl SessionHooks for AgentStopHooks { + async fn on_agent_stop( + &self, + input: AgentStopInput, + ctx: HookContext, + ) -> Option { + assert_eq!(ctx.session_id, SessionId::new("sess-1")); + assert_eq!(input.session_id, "sess-1"); + assert_eq!(input.stop_reason.as_deref(), Some("end_turn")); + assert_eq!( + input.transcript_path, + Some(PathBuf::from("/tmp/transcript.jsonl")) + ); + assert_eq!(input.stop_hook_active, Some(true)); + Some(AgentStopOutput { + decision: Some("block".to_string()), + reason: Some("finish the remaining work".to_string()), + }) + } + } + + let input = serde_json::json!({ + "sessionId": "sess-1", + "timestamp": 1234567890, + "cwd": "/tmp", + "stopReason": "end_turn", + "transcriptPath": "/tmp/transcript.jsonl", + "stop_hook_active": true + }); + let result = dispatch_hook( + &AgentStopHooks, + &SessionId::new("sess-1"), + "agentStop", + input, + ) + .await + .unwrap(); + + assert_eq!(result["output"]["decision"], "block"); + assert_eq!(result["output"]["reason"], "finish the remaining work"); + } } diff --git a/rust/src/jsonrpc.rs b/rust/src/jsonrpc.rs index fbdc96505..25a405080 100644 --- a/rust/src/jsonrpc.rs +++ b/rust/src/jsonrpc.rs @@ -169,6 +169,68 @@ impl JsonRpcResponse { const CONTENT_LENGTH_HEADER: &str = "Content-Length: "; +/// Rewrites unpaired UTF-16 surrogate escapes to `\uFFFD`. +/// +/// Returns `None` when the body contains no unpaired surrogate, so valid +/// frames do not incur a repair allocation. +fn repair_lone_surrogates(body: &[u8]) -> Option> { + fn hex_escape_at(body: &[u8], index: usize) -> Option { + let digits = body.get(index + 2..index + 6)?; + let text = std::str::from_utf8(digits).ok()?; + u16::from_str_radix(text, 16).ok() + } + + let mut repaired = None; + let mut in_string = false; + let mut index = 0; + + while index < body.len() { + let byte = body[index]; + + if !in_string { + in_string = byte == b'"'; + index += 1; + continue; + } + + match byte { + b'"' => { + in_string = false; + index += 1; + } + // Consume non-Unicode escapes whole so an escaped backslash cannot + // be mistaken for the start of a surrogate escape. + b'\\' if body.get(index + 1) != Some(&b'u') => index += 2, + b'\\' => { + let Some(unit) = hex_escape_at(body, index) else { + index += 2; + continue; + }; + + let is_pair = (0xD800..0xDC00).contains(&unit) + && body.get(index + 6) == Some(&b'\\') + && body.get(index + 7) == Some(&b'u') + && hex_escape_at(body, index + 6) + .is_some_and(|low| (0xDC00..0xE000).contains(&low)); + + if is_pair { + index += 12; + continue; + } + + if (0xD800..0xE000).contains(&unit) { + let output = repaired.get_or_insert_with(|| body.to_vec()); + output[index..index + 6].copy_from_slice(br"\ufffd"); + } + index += 6; + } + _ => index += 1, + } + } + + repaired +} + /// One framed JSON-RPC message handed to the writer actor. /// /// `frame` is the fully serialized bytes (header + body); the caller pays @@ -428,8 +490,26 @@ impl JsonRpcClient { let mut body = vec![0u8; length]; reader.read_exact(&mut body).await?; - let message: JsonRpcMessage = serde_json::from_slice(&body)?; - Ok(Some(message)) + match serde_json::from_slice::(&body) { + Ok(message) => Ok(Some(message)), + Err(error) => { + // Dropping an undecodable frame could leave its pending + // request waiting forever because this layer has no timeout. + match repair_lone_surrogates(&body) + .and_then(|repaired| serde_json::from_slice::(&repaired).ok()) + { + Some(message) => { + warn!( + error = %error, + length, + "recovered JSON-RPC frame containing unpaired UTF-16 surrogates" + ); + Ok(Some(message)) + } + None => Err(error.into()), + } + } + } } /// Send a JSON-RPC request and wait for the matching response. diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 65d9dab21..9e3041fec 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -40,6 +40,8 @@ pub mod session; /// Custom session filesystem provider (virtualizable filesystem layer). pub mod session_fs; mod session_fs_dispatch; +/// Per-phase timing breakdown for [`Client::start`]. +pub mod startup_timings; /// Event subscription handles returned by `subscribe()` methods. pub mod subscription; /// Typed tool definition framework and dispatch router. @@ -106,12 +108,24 @@ pub use types::*; mod sdk_protocol_version; pub use sdk_protocol_version::{SDK_PROTOCOL_VERSION, get_sdk_protocol_version}; +pub use startup_timings::StartupTimings; pub use subscription::{EventSubscription, LifecycleSubscription}; /// Minimum protocol version this SDK can communicate with. const MIN_PROTOCOL_VERSION: u32 = 3; const RUNTIME_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(10); +fn record_optional_millis(span: &tracing::Span, field: &'static str, value: Option) { + match value { + Some(value) => { + span.record(field, value); + } + None => { + span.record(field, "None"); + } + } +} + /// How the SDK communicates with the CLI server. #[derive(Debug, Default)] #[non_exhaustive] @@ -240,6 +254,11 @@ pub struct ClientOptions { pub env_remove: Vec, /// Extra flags for child-process transports. pub extra_args: Vec, + /// Absolute paths to trusted plugin directories bundled by the host. + /// + /// When non-empty, [`Client::start`] replaces the runtime's complete + /// trusted built-in plugin directory set before sessions can be created. + pub builtin_plugin_directories: Vec, /// Transport mode used to communicate with the CLI server. pub transport: Transport, /// GitHub token for authentication. When set, the SDK passes the token @@ -354,6 +373,10 @@ impl std::fmt::Debug for ClientOptions { .field("env", &self.env) .field("env_remove", &self.env_remove) .field("extra_args", &self.extra_args) + .field( + "builtin_plugin_directories", + &self.builtin_plugin_directories, + ) .field("transport", &self.transport) .field( "github_token", @@ -618,6 +641,7 @@ impl Default for ClientOptions { env: Vec::new(), env_remove: Vec::new(), extra_args: Vec::new(), + builtin_plugin_directories: Vec::new(), transport: Transport::default(), github_token: None, use_logged_in_user: None, @@ -710,6 +734,19 @@ impl ClientOptions { self } + /// Set trusted plugin directories bundled by the host. + /// + /// Every path must be absolute; invalid paths are rejected by + /// [`Client::start`]. + pub fn with_builtin_plugin_directories(mut self, paths: I) -> Self + where + I: IntoIterator, + P: Into, + { + self.builtin_plugin_directories = paths.into_iter().map(Into::into).collect(); + self + } + /// Transport mode used to communicate with the CLI server. See [`Transport`]. pub fn with_transport(mut self, transport: Transport) -> Self { self.transport = transport; @@ -1007,6 +1044,10 @@ struct ClientInner { /// SDK [`ClientMode`] captured at start time. Drives empty-mode safe /// defaults inside `create_session` / `resume_session`. pub(crate) mode: ClientMode, + /// Per-phase startup timing breakdown, populated once at the end of + /// [`Client::start`]. Empty for clients built via [`Client::from_streams`] + /// or [`Client::from_transport`] directly. + startup_timings: OnceLock, } impl Client { @@ -1024,6 +1065,7 @@ impl Client { /// backend. pub async fn start(options: ClientOptions) -> Result { let start_time = Instant::now(); + let mut timings = StartupTimings::default(); let mut options = options; if matches!(options.transport, Transport::Default) { options.transport = resolve_default_transport(&options)?; @@ -1052,6 +1094,30 @@ impl Client { if let Some(cfg) = &options.session_fs { validate_session_fs_config(cfg)?; } + let builtin_plugin_directories = options + .builtin_plugin_directories + .iter() + .map(|path| { + if !path.is_absolute() { + return Err(Error::with_message( + ErrorKind::InvalidConfig, + format!( + "builtin_plugin_directories must contain only absolute paths: {}", + path.display() + ), + )); + } + path.to_str().map(str::to_owned).ok_or_else(|| { + Error::with_message( + ErrorKind::InvalidConfig, + format!( + "builtin_plugin_directories must contain valid UTF-8 paths: {}", + path.display() + ), + ) + }) + }) + .collect::>>()?; // Auth options only make sense when the SDK spawns the CLI; with an // external server, the server manages its own auth. if matches!(options.transport, Transport::External { .. }) { @@ -1119,9 +1185,16 @@ impl Client { path.clone() } CliProgram::Resolve => { + let resolve_start = Instant::now(); let resolved = resolve::copilot_binary_with_extract_dir( options.bundled_cli_extract_dir.as_deref(), )?; + let resolve_elapsed = resolve_start.elapsed(); + timings.program_resolve_ms = Some(StartupTimings::millis(resolve_elapsed)); + debug!( + elapsed_ms = resolve_elapsed.as_millis(), + "Client::start CLI program resolution complete" + ); info!(path = %resolved.display(), "resolved copilot CLI"); #[cfg(windows)] { @@ -1148,6 +1221,7 @@ impl Client { } }; + let transport_setup_start = Instant::now(); let client = match options.transport { Transport::Default => unreachable!("default transport resolved above"), Transport::External { @@ -1183,8 +1257,10 @@ impl Client { port, connection_token: _, } => { - let (mut child, actual_port) = + let (mut child, actual_port, spawn_elapsed, port_wait_elapsed) = Self::spawn_tcp(&program, &options, &working_directory, port).await?; + timings.process_spawn_ms = Some(StartupTimings::millis(spawn_elapsed)); + timings.port_wait_ms = Some(StartupTimings::millis(port_wait_elapsed)); let connect_start = Instant::now(); let stream = TcpStream::connect(("127.0.0.1", actual_port)).await?; debug!( @@ -1209,7 +1285,9 @@ impl Client { )? } Transport::Stdio => { - let mut child = Self::spawn_stdio(&program, &options, &working_directory)?; + let (mut child, spawn_elapsed) = + Self::spawn_stdio(&program, &options, &working_directory)?; + timings.process_spawn_ms = Some(StartupTimings::millis(spawn_elapsed)); let stdin = child.stdin.take().expect("stdin is piped"); let stdout = child.stdout.take().expect("stdout is piped"); Self::drain_stderr(&mut child); @@ -1290,15 +1368,26 @@ impl Client { unreachable!("in-process feature validation returned above") } }; + timings.transport_setup_ms = StartupTimings::millis(transport_setup_start.elapsed()); debug!( elapsed_ms = start_time.elapsed().as_millis(), "Client::start transport setup complete" ); + let handshake_start = Instant::now(); client.verify_protocol_version().await?; + timings.handshake_ms = StartupTimings::millis(handshake_start.elapsed()); debug!( elapsed_ms = start_time.elapsed().as_millis(), "Client::start protocol verification complete" ); + if !builtin_plugin_directories.is_empty() { + client + .call( + "plugins.builtin.set", + Some(serde_json::json!({ "paths": builtin_plugin_directories })), + ) + .await?; + } if let Some(cfg) = session_fs_config { let session_fs_start = Instant::now(); let capabilities = cfg.capabilities.as_ref().map(|c| { @@ -1313,8 +1402,10 @@ impl Client { session_state_path: cfg.session_state_path, }; client.rpc().session_fs().set_provider(request).await?; + let session_fs_elapsed = session_fs_start.elapsed(); + timings.session_fs_ms = Some(StartupTimings::millis(session_fs_elapsed)); debug!( - elapsed_ms = session_fs_start.elapsed().as_millis(), + elapsed_ms = session_fs_elapsed.as_millis(), "Client::start session filesystem setup complete" ); } @@ -1334,11 +1425,38 @@ impl Client { client.inner.on_github_telemetry.clone(), ); client.rpc().llm_inference().set_provider().await?; + let llm_inference_elapsed = llm_inference_start.elapsed(); + timings.llm_handler_ms = Some(StartupTimings::millis(llm_inference_elapsed)); debug!( - elapsed_ms = llm_inference_start.elapsed().as_millis(), + elapsed_ms = llm_inference_elapsed.as_millis(), "Client::start Copilot request handler registration complete" ); } + timings.total_ms = StartupTimings::millis(start_time.elapsed()); + // A span allows optional fields to retain their numeric type when + // present while recording an explicit "None" when a phase did not run. + let timings_span = tracing::debug_span!( + "Client::start timings", + program_resolve_ms = tracing::field::Empty, + process_spawn_ms = tracing::field::Empty, + port_wait_ms = tracing::field::Empty, + transport_setup_ms = timings.transport_setup_ms, + handshake_ms = timings.handshake_ms, + session_fs_ms = tracing::field::Empty, + llm_handler_ms = tracing::field::Empty, + total_ms = timings.total_ms, + ); + record_optional_millis( + &timings_span, + "program_resolve_ms", + timings.program_resolve_ms, + ); + record_optional_millis(&timings_span, "process_spawn_ms", timings.process_spawn_ms); + record_optional_millis(&timings_span, "port_wait_ms", timings.port_wait_ms); + record_optional_millis(&timings_span, "session_fs_ms", timings.session_fs_ms); + record_optional_millis(&timings_span, "llm_handler_ms", timings.llm_handler_ms); + timings_span.in_scope(|| debug!("Client::start timings")); + let _ = client.inner.startup_timings.set(timings); debug!( elapsed_ms = start_time.elapsed().as_millis(), "Client::start complete" @@ -1507,6 +1625,7 @@ impl Client { on_get_trace_context, effective_connection_token, mode, + startup_timings: OnceLock::new(), }), }; client.spawn_lifecycle_dispatcher(); @@ -1683,7 +1802,7 @@ impl Client { program: &Path, options: &ClientOptions, working_directory: &Path, - ) -> Result { + ) -> Result<(Child, Duration)> { info!(cwd = ?working_directory, program = %program.display(), "spawning copilot CLI (stdio)"); let mut command = Self::build_command(program, options, working_directory); command @@ -1696,11 +1815,12 @@ impl Client { .stdin(Stdio::piped()); let spawn_start = Instant::now(); let child = command.spawn()?; + let spawn_elapsed = spawn_start.elapsed(); debug!( - elapsed_ms = spawn_start.elapsed().as_millis(), + elapsed_ms = spawn_elapsed.as_millis(), "Client::spawn_stdio subprocess spawned" ); - Ok(child) + Ok((child, spawn_elapsed)) } async fn spawn_tcp( @@ -1708,7 +1828,7 @@ impl Client { options: &ClientOptions, working_directory: &Path, port: u16, - ) -> Result<(Child, u16)> { + ) -> Result<(Child, u16, Duration, Duration)> { info!(cwd = ?working_directory, program = %program.display(), port = %port, "spawning copilot CLI (tcp)"); let mut command = Self::build_command(program, options, working_directory); command @@ -1721,8 +1841,9 @@ impl Client { .stdin(Stdio::null()); let spawn_start = Instant::now(); let mut child = command.spawn()?; + let spawn_elapsed = spawn_start.elapsed(); debug!( - elapsed_ms = spawn_start.elapsed().as_millis(), + elapsed_ms = spawn_elapsed.as_millis(), "Client::spawn_tcp subprocess spawned" ); let stdout = child.stdout.take().expect("stdout is piped"); @@ -1759,13 +1880,14 @@ impl Client { .map_err(|_| Error::from(ErrorKind::Protocol(ProtocolErrorKind::CliStartupTimeout)))? .map_err(|_| Error::from(ErrorKind::Protocol(ProtocolErrorKind::CliStartupFailed)))?; + let port_wait_elapsed = port_wait_start.elapsed(); debug!( - elapsed_ms = port_wait_start.elapsed().as_millis(), + elapsed_ms = port_wait_elapsed.as_millis(), port = actual_port, "Client::spawn_tcp TCP port wait complete" ); info!(port = %actual_port, "CLI server listening"); - Ok((child, actual_port)) + Ok((child, actual_port, spawn_elapsed, port_wait_elapsed)) } fn drain_stderr(child: &mut Child) { @@ -1942,6 +2064,16 @@ impl Client { self.inner.negotiated_protocol_version.get().copied() } + /// Returns the per-phase [`StartupTimings`] breakdown captured during + /// [`start`](Self::start), if available. + /// + /// Returns `None` for clients created via + /// [`from_streams`](Self::from_streams), which bypasses the timed startup + /// sequence. + pub fn startup_timings(&self) -> Option { + self.inner.startup_timings.get().cloned() + } + /// Verify the CLI server's protocol version is within the supported range. /// /// Called automatically by [`start`](Self::start). Call manually after @@ -2119,6 +2251,60 @@ impl Client { Ok(()) } + /// Start this client's notification and request router on the current runtime. + /// This is test-harness plumbing, not part of the supported SDK API. + #[cfg(feature = "test-support")] + #[doc(hidden)] + pub fn start_router_for_test(&self) { + self.inner.router.ensure_started( + &self.inner.notification_tx, + &self.inner.request_rx, + self.inner.llm_inference.get().cloned(), + self.inner.on_github_telemetry.clone(), + ); + } + + #[cfg(feature = "test-support")] + #[doc(hidden)] + /// Disconnect and delete every session owned by this test client's isolated + /// runtime. This is test-harness plumbing, not part of the supported SDK API. + pub async fn cleanup_sessions_for_test(&self) -> Result<()> { + let mut first_error = None; + + for session_id in self.inner.router.session_ids() { + if let Err(error) = self + .call( + "session.destroy", + Some(serde_json::json!({ "sessionId": session_id })), + ) + .await + && first_error.is_none() + { + first_error = Some(error); + } + self.inner.router.unregister(&session_id); + } + + match self.list_sessions(None).await { + Ok(sessions) => { + for session in sessions { + if let Err(error) = self.delete_session(&session.session_id).await + && first_error.is_none() + { + first_error = Some(error); + } + } + } + Err(error) if first_error.is_none() => first_error = Some(error), + Err(_) => {} + } + + match first_error { + Some(error) => Err(error), + None => Ok(()), + } + } + /// Return the ID of the most recently updated session, if any. /// /// Useful for resuming the last conversation when the session ID was @@ -3065,6 +3251,7 @@ mod tests { let (client_write, _server_read) = tokio::io::duplex(8192); let (_server_write, client_read) = tokio::io::duplex(8192); let client = Client::from_streams(client_read, client_write, std::env::temp_dir()).unwrap(); + assert!(client.startup_timings().is_none()); let session_id = SessionId::new("resume-cancel-test"); let handle = tokio::spawn({ let client = client.clone(); @@ -3112,6 +3299,7 @@ mod tests { on_get_trace_context: None, effective_connection_token: None, mode: ClientMode::default(), + startup_timings: OnceLock::new(), }), } } diff --git a/rust/src/mode.rs b/rust/src/mode.rs index 580a57bbd..2b1ab897c 100644 --- a/rust/src/mode.rs +++ b/rust/src/mode.rs @@ -33,6 +33,14 @@ pub enum ClientMode { Empty, } +/// Resolve the effective custom-agents locality setting for a client mode. +pub(crate) fn resolve_custom_agents_local_only( + mode: ClientMode, + custom_agents_local_only: Option, +) -> Option { + custom_agents_local_only.or_else(|| (mode == ClientMode::Empty).then_some(true)) +} + /// Tool name character set enforced by the runtime at every registration /// boundary. Mirrors the runtime's `VALID_TOOL_NAME_REGEX`. fn is_valid_tool_name(name: &str) -> bool { @@ -283,10 +291,35 @@ pub(crate) fn memory_for_mode( } } +/// Returns the `enable_experimental_mode` value to send for the given mode. +pub(crate) fn experimental_mode_for_mode(mode: ClientMode, supplied: Option) -> Option { + if mode == ClientMode::Empty { + Some(supplied.unwrap_or(false)) + } else { + supplied + } +} + #[cfg(test)] mod tests { use super::*; + #[test] + fn custom_agents_local_only_respects_mode_and_caller_value() { + assert_eq!( + resolve_custom_agents_local_only(ClientMode::Empty, None), + Some(true) + ); + assert_eq!( + resolve_custom_agents_local_only(ClientMode::Empty, Some(false)), + Some(false) + ); + assert_eq!( + resolve_custom_agents_local_only(ClientMode::CopilotCli, None), + None + ); + } + #[test] fn tool_set_emits_source_qualified_patterns() { let v = ToolSet::new() @@ -510,4 +543,28 @@ mod tests { Some(MemoryConfiguration::enabled()) ); } + + #[test] + fn experimental_mode_defaults_false_in_empty_mode() { + assert_eq!( + experimental_mode_for_mode(ClientMode::Empty, None), + Some(false) + ); + assert_eq!( + experimental_mode_for_mode(ClientMode::Empty, Some(true)), + Some(true) + ); + assert_eq!( + experimental_mode_for_mode(ClientMode::Empty, Some(false)), + Some(false) + ); + } + + #[test] + fn experimental_mode_remains_runtime_controlled_in_copilot_cli_mode() { + assert_eq!( + experimental_mode_for_mode(ClientMode::CopilotCli, None), + None + ); + } } diff --git a/rust/src/permission.rs b/rust/src/permission.rs index 2ddd773a3..57c078570 100644 --- a/rust/src/permission.rs +++ b/rust/src/permission.rs @@ -16,10 +16,14 @@ use std::sync::Arc; use async_trait::async_trait; -use crate::handler::{PermissionHandler, PermissionResult}; +use crate::handler::{PermissionHandler, PermissionResult, permission_handler_failure}; use crate::types::{PermissionRequestData, RequestId, SessionId}; -/// Return a [`PermissionHandler`] that approves every request. +/// Return a [`PermissionHandler`] that approves requests when managed settings +/// are disabled. +/// +/// When managed settings are enabled, the handler logs an error and returns a +/// user-not-available decision. pub fn approve_all() -> Arc { Arc::new(PolicyHandler { policy: Policy::ApproveAll, @@ -93,8 +97,7 @@ pub(crate) fn resolve_handler( ) -> Option> { match (handler, policy) { (_, Some(policy)) => Some(Arc::new(PolicyHandler { policy })), - (Some(h), None) => Some(h), - (None, None) => None, + (handler, None) => handler, } } @@ -116,7 +119,15 @@ impl PermissionHandler for PolicyHandler { Policy::Predicate(f) => f(&data), }; if approved { - PermissionResult::approve_once() + if matches!(self.policy, Policy::ApproveAll) && data.managed_settings_enabled { + permission_handler_failure( + "approve-all policy cannot be used when managed settings are enabled", + ) + } else if data.managed_approval_required == Some(true) { + PermissionResult::no_result() + } else { + PermissionResult::approve_once() + } } else { PermissionResult::reject(None) } @@ -140,7 +151,25 @@ mod tests { assert!(matches!( h.handle(SessionId::from("s"), RequestId::new("1"), data()) .await, - PermissionResult::Decision(crate::types::PermissionDecision::ApproveOnce(_)) + PermissionResult::Decision { + decision: crate::types::PermissionDecision::ApproveOnce(_), + .. + } + )); + } + + #[tokio::test] + async fn approve_all_fails_when_managed_settings_enabled() { + let h = approve_all(); + let mut request = data(); + request.managed_settings_enabled = true; + assert!(matches!( + h.handle(SessionId::from("s"), RequestId::new("1"), request) + .await, + PermissionResult::Decision { + decision: crate::types::PermissionDecision::UserNotAvailable(_), + .. + } )); } @@ -150,7 +179,10 @@ mod tests { assert!(matches!( h.handle(SessionId::from("s"), RequestId::new("1"), data()) .await, - PermissionResult::Decision(crate::types::PermissionDecision::Reject(_)) + PermissionResult::Decision { + decision: crate::types::PermissionDecision::Reject(_), + .. + } )); } @@ -160,7 +192,37 @@ mod tests { assert!(matches!( h.handle(SessionId::from("s"), RequestId::new("1"), data()) .await, - PermissionResult::Decision(crate::types::PermissionDecision::Reject(_)) + PermissionResult::Decision { + decision: crate::types::PermissionDecision::Reject(_), + .. + } + )); + } + + #[tokio::test] + async fn approve_if_leaves_managed_approval_pending_when_predicate_approves() { + let h = approve_if(|_| true); + let mut request = data(); + request.managed_approval_required = Some(true); + assert!(matches!( + h.handle(SessionId::from("s"), RequestId::new("1"), request) + .await, + PermissionResult::NoResult + )); + } + + #[tokio::test] + async fn approve_if_still_rejects_managed_request_when_predicate_denies() { + let h = approve_if(|_| false); + let mut request = data(); + request.managed_approval_required = Some(true); + assert!(matches!( + h.handle(SessionId::from("s"), RequestId::new("1"), request) + .await, + PermissionResult::Decision { + decision: crate::types::PermissionDecision::Reject(_), + .. + } )); } @@ -185,7 +247,10 @@ mod tests { resolved .handle(SessionId::from("s"), RequestId::new("1"), data()) .await, - PermissionResult::Decision(crate::types::PermissionDecision::Reject(_)) + PermissionResult::Decision { + decision: crate::types::PermissionDecision::Reject(_), + .. + } )); } @@ -208,7 +273,10 @@ mod tests { resolved .handle(SessionId::from("s"), RequestId::new("1"), data()) .await, - PermissionResult::Decision(crate::types::PermissionDecision::ApproveOnce(_)) + PermissionResult::Decision { + decision: crate::types::PermissionDecision::ApproveOnce(_), + .. + } )); } diff --git a/rust/src/session.rs b/rust/src/session.rs index 89162346e..99e793015 100644 --- a/rust/src/session.rs +++ b/rust/src/session.rs @@ -12,8 +12,8 @@ use tracing::{Instrument, warn}; use crate::canvas::CanvasHandler; use crate::generated::api_types::{ - LogRequest, ModelSwitchToRequest, OpenCanvasInstance, RegisterEventInterestParams, - ToolsGetCurrentMetadataResult, rpc_methods, + LogRequest, ModelSwitchToRequest, OpenCanvasInstance, PermissionDecisionRequest, + RegisterEventInterestParams, ToolsGetCurrentMetadataResult, rpc_methods, }; use crate::generated::session_events::{ CommandExecuteData, ElicitationRequestedData, ExternalToolRequestedData, McpOauthRequiredData, @@ -57,6 +57,7 @@ const TOOL_SEARCH_TOOL_NAME: &str = "tool_search_tool"; #[derive(Clone)] pub(crate) struct SessionHandlers { pub permission: Option>, + pub managed_settings_enabled: bool, pub elicitation: Option>, pub mcp_auth: Option>, pub user_input: Option>, @@ -65,6 +66,13 @@ pub(crate) struct SessionHandlers { pub tools: Arc>>, } +fn has_managed_settings( + enable_managed_settings: Option, + managed_settings: Option<&crate::types::ManagedSettings>, +) -> bool { + enable_managed_settings == Some(true) || managed_settings.is_some() +} + /// Shared state between a [`Session`] and its event loop, used by [`Session::send_and_wait`]. struct IdleWaiter { tx: oneshot::Sender, Error>>, @@ -538,6 +546,7 @@ impl Session { verbosity: None, context_tier: opts.context_tier, model_capabilities: opts.model_capabilities, + defer_if_model_change_queued: None, }; self.rpc().model().switch_to(request).await?; Ok(()) @@ -850,6 +859,8 @@ impl Client { config.system_message = crate::mode::system_message_for_mode(mode, config.system_message.take()); config.memory = crate::mode::memory_for_mode(mode, config.memory.take()); + config.enable_experimental_mode = + crate::mode::experimental_mode_for_mode(mode, config.enable_experimental_mode); if mode == crate::ClientMode::Empty { if config.enable_session_telemetry.is_none() { config.enable_session_telemetry = Some(false); @@ -879,6 +890,8 @@ impl Client { if mode == crate::ClientMode::Empty && config.embedding_cache_storage.is_none() { config.embedding_cache_storage = Some("in-memory".into()); } + config.custom_agents_local_only = + crate::mode::resolve_custom_agents_local_only(mode, config.custom_agents_local_only); let opt_skip_custom_instructions = config.skip_custom_instructions; let opt_custom_agents_local_only = config.custom_agents_local_only; let opt_coauthor_enabled = config.coauthor_enabled; @@ -893,6 +906,10 @@ impl Client { ); let handlers = SessionHandlers { permission: permission_handler, + managed_settings_enabled: has_managed_settings( + wire.enable_managed_settings, + wire.managed_settings.as_ref(), + ), elicitation: runtime.elicitation_handler.take(), mcp_auth: runtime.mcp_auth_handler.take(), user_input: runtime.user_input_handler.take(), @@ -1115,6 +1132,8 @@ impl Client { config.system_message = crate::mode::system_message_for_mode(mode, config.system_message.take()); config.memory = crate::mode::memory_for_mode(mode, config.memory.take()); + config.enable_experimental_mode = + crate::mode::experimental_mode_for_mode(mode, config.enable_experimental_mode); if mode == crate::ClientMode::Empty { if config.enable_session_telemetry.is_none() { config.enable_session_telemetry = Some(false); @@ -1144,6 +1163,8 @@ impl Client { if mode == crate::ClientMode::Empty && config.embedding_cache_storage.is_none() { config.embedding_cache_storage = Some("in-memory".into()); } + config.custom_agents_local_only = + crate::mode::resolve_custom_agents_local_only(mode, config.custom_agents_local_only); let opt_skip_custom_instructions = config.skip_custom_instructions; let opt_custom_agents_local_only = config.custom_agents_local_only; let opt_coauthor_enabled = config.coauthor_enabled; @@ -1158,6 +1179,10 @@ impl Client { ); let handlers = SessionHandlers { permission: permission_handler, + managed_settings_enabled: has_managed_settings( + wire.enable_managed_settings, + wire.managed_settings.as_ref(), + ), elicitation: runtime.elicitation_handler.take(), mcp_auth: runtime.mcp_auth_handler.take(), user_input: runtime.user_input_handler.take(), @@ -1519,17 +1544,60 @@ fn extract_request_id(data: &Value) -> Option { .map(RequestId::new) } -/// Map a [`PermissionResult`] to the `result` payload sent back to the -/// server via `session.permissions.handlePendingPermissionRequest`. +fn permission_request_data( + event_data: &Value, + managed_settings_enabled: bool, +) -> PermissionRequestData { + let request_data = event_data + .get("permissionRequest") + .cloned() + .unwrap_or_else(|| event_data.clone()); + let managed_approval_required = match request_data.get("managedApprovalRequired") { + None => None, + Some(Value::Bool(value)) => Some(*value), + Some(_) => Some(true), + }; + match serde_json::from_value::(request_data) { + Ok(mut data) => { + data.extra = event_data.clone(); + data.managed_settings_enabled = managed_settings_enabled; + data + } + Err(_) => PermissionRequestData { + kind: None, + tool_call_id: None, + managed_approval_required, + managed_settings_enabled, + extra: event_data.clone(), + }, + } +} + +/// Build the full `session.permissions.handlePendingPermissionRequest` +/// params for a permission result. +/// +/// `decisionContext` is a sibling of `result` and is only present when the +/// handler attributed the decision — omitting it preserves legacy behavior. /// /// Returns `None` when the SDK must not send a response. -fn notification_permission_payload(result: &PermissionResult) -> Option { - match result { - PermissionResult::NoResult => None, - PermissionResult::Decision(decision) => Some( - serde_json::to_value(decision).expect("serializing permission decision should succeed"), - ), - } +fn permission_response_params( + session_id: &SessionId, + request_id: &RequestId, + result: &PermissionResult, +) -> Option { + let (decision, decision_context) = match result { + PermissionResult::Decision { decision, context } => (decision, context.clone()), + PermissionResult::NoResult => return None, + }; + let mut params = serde_json::to_value(PermissionDecisionRequest { + decision_context, + request_id: request_id.clone(), + result: decision.clone(), + }) + .expect("serializing permission response should succeed"); + params["sessionId"] = + serde_json::to_value(session_id).expect("serializing session ID should succeed"); + Some(params) } async fn register_mcp_auth_interest(client: &Client, session_id: &SessionId) -> Result<(), Error> { @@ -1704,14 +1772,10 @@ async fn handle_notification( }; let client = client.clone(); let sid = session_id.clone(); - let data: PermissionRequestData = - serde_json::from_value(notification.event.data.clone()).unwrap_or_else(|_| { - PermissionRequestData { - kind: None, - tool_call_id: None, - extra: notification.event.data.clone(), - } - }); + let data = permission_request_data( + ¬ification.event.data, + handlers.managed_settings_enabled, + ); let span = tracing::error_span!( "permission_request_handler", session_id = %sid, @@ -1729,7 +1793,8 @@ async fn handle_notification( request_id = %request_id, "PermissionHandler::handle dispatch" ); - let Some(result_value) = notification_permission_payload(&result) else { + let Some(params) = permission_response_params(&sid, &request_id, &result) + else { // Handler returned Deferred / NoResult — it will // call handlePendingPermissionRequest itself (or // leave the request unanswered). @@ -1738,12 +1803,8 @@ async fn handle_notification( let rpc_start = Instant::now(); let _ = client .call( - "session.permissions.handlePendingPermissionRequest", - Some(serde_json::json!({ - "sessionId": sid, - "requestId": request_id, - "result": result_value, - })), + rpc_methods::SESSION_PERMISSIONS_HANDLEPENDINGPERMISSIONREQUEST, + Some(params), ) .await; tracing::debug!( @@ -2513,31 +2574,200 @@ fn inject_transform_sections_resume( mod tests { use serde_json::json; - use super::notification_permission_payload; + use super::{has_managed_settings, permission_request_data, permission_response_params}; use crate::handler::PermissionResult; + use crate::types::{ + PermissionDecisionContext, PermissionDecisionOutcome, PermissionDecisionSource, + PermissionDecisionSurface, RequestId, SessionId, + }; #[test] - fn notification_payload_suppresses_no_result() { - assert!(notification_permission_payload(&PermissionResult::NoResult).is_none()); + fn direct_injection_enables_managed_safeguards() { + let settings = crate::types::ManagedSettings::default(); + assert!(has_managed_settings(None, Some(&settings))); + assert!(!has_managed_settings(None, None)); + } + + fn attribution_context() -> PermissionDecisionContext { + PermissionDecisionContext { + outcome: PermissionDecisionOutcome::AutoApproved, + source: PermissionDecisionSource::JudgeRecommendation, + surface: PermissionDecisionSurface::CopilotApp, + } } #[test] - fn notification_payload_serializes_decisions() { + fn response_params_omit_decision_context_without_attribution() { + for (result, expected) in [ + ( + PermissionResult::approve_once(), + json!({ "kind": "approve-once" }), + ), + (PermissionResult::reject(None), json!({ "kind": "reject" })), + ( + PermissionResult::reject(Some("bad".to_string())), + json!({ "kind": "reject", "feedback": "bad" }), + ), + ( + PermissionResult::user_not_available(), + json!({ "kind": "user-not-available" }), + ), + ] { + let params = permission_response_params( + &SessionId::from("session-1"), + &RequestId::from("permission-1"), + &result, + ) + .unwrap(); + assert_eq!( + params, + json!({ + "sessionId": "session-1", + "requestId": "permission-1", + "result": expected, + }) + ); + } + } + + #[test] + fn response_params_forward_decision_context_alongside_result() { + let params = permission_response_params( + &SessionId::from("session-1"), + &RequestId::from("permission-1"), + &PermissionResult::approve_once().with_context(attribution_context()), + ) + .unwrap(); assert_eq!( - notification_permission_payload(&PermissionResult::approve_once()), - Some(json!({ "kind": "approve-once" })) + params, + json!({ + "sessionId": "session-1", + "requestId": "permission-1", + "result": { "kind": "approve-once" }, + "decisionContext": { + "outcome": "auto_approved", + "source": "judge_recommendation", + "surface": "copilot_app", + }, + }) ); - assert_eq!( - notification_permission_payload(&PermissionResult::reject(None)), - Some(json!({ "kind": "reject" })) + // The context is a sibling of `result`, never nested inside it. + assert!(params["result"].get("decisionContext").is_none()); + } + + #[test] + fn response_params_suppressed_for_no_result() { + assert!( + permission_response_params( + &SessionId::from("session-1"), + &RequestId::from("permission-1"), + &PermissionResult::NoResult, + ) + .is_none() ); + } + + #[test] + fn with_context_is_a_no_op_on_no_result() { + let result = PermissionResult::no_result().with_context(attribution_context()); + assert!(matches!(result, PermissionResult::NoResult)); + } + + #[test] + fn with_context_replaces_rather_than_nests() { + let result = PermissionResult::approve_once() + .with_context(attribution_context()) + .with_context(PermissionDecisionContext { + outcome: PermissionDecisionOutcome::PromptedUser, + source: PermissionDecisionSource::HumanResponse, + surface: PermissionDecisionSurface::Sdk, + }); + let params = permission_response_params( + &SessionId::from("session-1"), + &RequestId::from("permission-1"), + &result, + ) + .unwrap(); assert_eq!( - notification_permission_payload(&PermissionResult::reject(Some("bad".to_string()))), - Some(json!({ "kind": "reject", "feedback": "bad" })) + params["decisionContext"], + json!({ + "outcome": "prompted_user", + "source": "human_response", + "surface": "sdk", + }) ); + } + + #[test] + fn permission_request_data_reads_nested_managed_approval_metadata() { + let data = permission_request_data( + &json!({ + "requestId": "permission-1", + "permissionRequest": { + "kind": "read", + "managedApprovalRequired": true, + "path": "/workspace/file.txt" + } + }), + false, + ); + + assert_eq!(data.managed_approval_required, Some(true)); assert_eq!( - notification_permission_payload(&PermissionResult::user_not_available()), - Some(json!({ "kind": "user-not-available" })) + data.extra["permissionRequest"]["path"], + "/workspace/file.txt" ); } + + #[test] + fn permission_request_data_preserves_managed_flag_when_other_fields_are_malformed() { + let data = permission_request_data( + &json!({ + "requestId": "permission-1", + "permissionRequest": { + "kind": "read", + "managedApprovalRequired": true, + "toolCallId": 42 + } + }), + false, + ); + + assert_eq!(data.managed_approval_required, Some(true)); + assert_eq!(data.extra["requestId"], "permission-1"); + } + + #[test] + fn permission_request_data_fails_closed_for_malformed_managed_flag() { + let data = permission_request_data( + &json!({ + "requestId": "permission-1", + "permissionRequest": { + "kind": "read", + "managedApprovalRequired": "yes", + "path": "/workspace/file.txt" + } + }), + false, + ); + + assert_eq!(data.managed_approval_required, Some(true)); + } + + #[test] + fn permission_request_data_preserves_valid_false_managed_flag() { + let data = permission_request_data( + &json!({ + "requestId": "permission-1", + "permissionRequest": { + "kind": "read", + "managedApprovalRequired": false, + "path": "/workspace/file.txt" + } + }), + false, + ); + + assert_eq!(data.managed_approval_required, Some(false)); + } } diff --git a/rust/src/session_fs.rs b/rust/src/session_fs.rs index da4d3e3c9..87868101f 100644 --- a/rust/src/session_fs.rs +++ b/rust/src/session_fs.rs @@ -47,11 +47,14 @@ use std::fmt; use async_trait::async_trait; -pub use crate::generated::api_types::SessionFsSqliteQueryType; use crate::generated::api_types::{ SessionFsError, SessionFsErrorCode, SessionFsReaddirWithTypesEntry, SessionFsReaddirWithTypesEntryType, SessionFsSetProviderConventions, SessionFsStatResult, }; +pub use crate::generated::api_types::{ + SessionFsSqliteQueryType, SessionFsSqliteTransactionErrorClass, + SessionFsSqliteTransactionStatement, +}; use crate::{Custom, Repr}; /// Optional capabilities declared by a session filesystem provider. @@ -528,10 +531,76 @@ pub trait SessionFsSqliteProvider: Send + Sync { params: Option<&HashMap>, ) -> Result, FsError>; + /// Execute `statements` atomically against the provider's per-session + /// database, returning one result per statement, in order. + /// + /// Return `Err` with a [`SessionFsSqliteTransactionError`] describing how + /// the failure should be classified. `BusyOrLocked` guarantees the + /// transaction rolled back and is safe to retry; `PostCommitAmbiguous` + /// must never be retried. + async fn sqlite_transaction( + &self, + _statements: &[SessionFsSqliteTransactionStatement], + ) -> Result, SessionFsSqliteTransactionError> { + Err(SessionFsSqliteTransactionError::fatal( + "SQLite transactions are not supported by this SessionFs provider", + )) + } + /// Check whether the provider has a SQLite database for this session. async fn sqlite_exists(&self) -> Result; } +/// Classified SQLite transaction failure returned by +/// [`SessionFsSqliteProvider::sqlite_transaction`]. +#[derive(Debug, Clone)] +pub struct SessionFsSqliteTransactionError { + /// How the runtime should classify the failure. + pub error_class: SessionFsSqliteTransactionErrorClass, + /// Human-readable failure description. + pub message: String, +} + +impl SessionFsSqliteTransactionError { + /// Create a `Fatal` transaction error with the given message. + pub fn fatal(message: impl Into) -> Self { + Self { + error_class: SessionFsSqliteTransactionErrorClass::Fatal, + message: message.into(), + } + } + + /// Create a `BusyOrLocked` transaction error with the given message. + pub fn busy_or_locked(message: impl Into) -> Self { + Self { + error_class: SessionFsSqliteTransactionErrorClass::BusyOrLocked, + message: message.into(), + } + } + + /// Create a `PostCommitAmbiguous` transaction error with the given message. + pub fn post_commit_ambiguous(message: impl Into) -> Self { + Self { + error_class: SessionFsSqliteTransactionErrorClass::PostCommitAmbiguous, + message: message.into(), + } + } +} + +impl std::fmt::Display for SessionFsSqliteTransactionError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.message) + } +} + +impl std::error::Error for SessionFsSqliteTransactionError {} + +impl From for SessionFsSqliteTransactionError { + fn from(error: FsError) -> Self { + Self::fatal(error.to_string()) + } +} + /// Result of a SQLite query execution via [`SessionFsSqliteProvider::sqlite_query`]. /// /// Same shape as the generated RPC type but without the `error` field, diff --git a/rust/src/session_fs_dispatch.rs b/rust/src/session_fs_dispatch.rs index 9c5780d37..c84981ac0 100644 --- a/rust/src/session_fs_dispatch.rs +++ b/rust/src/session_fs_dispatch.rs @@ -18,7 +18,10 @@ use crate::generated::api_types::{ SessionFsReaddirWithTypesRequest, SessionFsReaddirWithTypesResult, SessionFsRenameRequest, SessionFsRmRequest, SessionFsSqliteExistsParams, SessionFsSqliteExistsResult, SessionFsSqliteQueryRequest, SessionFsSqliteQueryResult as GeneratedSqliteQueryResult, - SessionFsStatRequest, SessionFsStatResult, SessionFsWriteFileRequest, + SessionFsSqliteTransactionError as GeneratedSqliteTransactionError, + SessionFsSqliteTransactionErrorClass, SessionFsSqliteTransactionRequest, + SessionFsSqliteTransactionResult as GeneratedSqliteTransactionResult, SessionFsStatRequest, + SessionFsStatResult, SessionFsWriteFileRequest, }; use crate::session_fs::SessionFsProvider; use crate::{Client, JsonRpcRequest, JsonRpcResponse, error_codes}; @@ -371,6 +374,69 @@ pub(crate) async fn sqlite_query( respond(client, id, result).await; } +pub(crate) async fn sqlite_transaction( + client: &Client, + provider: &Arc, + request: JsonRpcRequest, +) { + let params: SessionFsSqliteTransactionRequest = match parse_params(&request) { + Some(p) => p, + None => { + send_error( + client, + request.id, + "invalid sessionFs.sqliteTransaction params", + ) + .await; + return; + } + }; + let id = request.id; + let sqlite = match provider.sqlite() { + Some(s) => s, + None => { + // SQLite not supported — return a result-level error, not a + // transport error, so the CLI can surface it gracefully. + respond( + client, + id, + GeneratedSqliteTransactionResult { + results: Vec::new(), + error: Some(GeneratedSqliteTransactionError { + error_class: SessionFsSqliteTransactionErrorClass::Fatal, + message: "SQLite is not supported by this SessionFs provider".to_string(), + }), + }, + ) + .await; + return; + } + }; + let result = match sqlite.sqlite_transaction(¶ms.statements).await { + Ok(results) => GeneratedSqliteTransactionResult { + results: results + .into_iter() + .map(|result| GeneratedSqliteQueryResult { + columns: result.columns, + rows: result.rows, + rows_affected: result.rows_affected, + last_insert_rowid: result.last_insert_rowid, + error: None, + }) + .collect(), + error: None, + }, + Err(e) => GeneratedSqliteTransactionResult { + results: Vec::new(), + error: Some(GeneratedSqliteTransactionError { + error_class: e.error_class, + message: e.message, + }), + }, + }; + respond(client, id, result).await; +} + pub(crate) async fn sqlite_exists( client: &Client, provider: &Arc, @@ -431,6 +497,7 @@ pub(crate) async fn dispatch( "sessionFs.rm" => rm(client, &provider, request).await, "sessionFs.rename" => rename(client, &provider, request).await, "sessionFs.sqliteQuery" => sqlite_query(client, &provider, request).await, + "sessionFs.sqliteTransaction" => sqlite_transaction(client, &provider, request).await, "sessionFs.sqliteExists" => sqlite_exists(client, &provider, request).await, _ => { warn!(method = %method, "unknown sessionFs.* method"); diff --git a/rust/src/startup_timings.rs b/rust/src/startup_timings.rs new file mode 100644 index 000000000..7938a462b --- /dev/null +++ b/rust/src/startup_timings.rs @@ -0,0 +1,105 @@ +//! Per-phase timing breakdown for [`Client::start`](crate::Client::start). +//! +//! `Client::start` performs several sequential phases between "spawn the CLI" +//! and "client is ready to create sessions": resolving (and possibly +//! extracting) the CLI binary, spawning the subprocess, waiting for the TCP +//! port announcement, the `connect` protocol handshake, and the optional +//! `sessionFs.setProvider` / `llmInference.setProvider` registration RPCs. +//! +//! Each phase is already measured internally with an [`Instant`] and logged at +//! `debug`. [`StartupTimings`] aggregates those durations into a single value +//! so a host can attribute total startup latency ("time to first token" +//! groundwork) to a specific phase — e.g. separating "process exec cost" from +//! "handshake/negotiation cost" — instead of reconstructing it from scattered +//! log lines. +//! +//! Retrieve it after start via +//! [`Client::startup_timings`](crate::Client::startup_timings). +//! +//! [`Instant`]: std::time::Instant + +use std::time::Duration; + +/// Millisecond breakdown of the phases of [`Client::start`](crate::Client::start). +/// +/// Optional fields represent phases that do not run for every configuration: +/// `program_resolve_ms` is `None` when the caller supplies an explicit CLI path +/// (no resolution/extraction), `port_wait_ms` is `Some` only for the TCP +/// transport, and `session_fs_ms` / `llm_handler_ms` are `Some` only when the +/// corresponding option is configured. `process_spawn_ms` is `None` for +/// transports that do not spawn a subprocess (external server, in-process FFI +/// runtime). `transport_setup_ms`, `handshake_ms`, and `total_ms` are always +/// populated for a value returned by +/// [`Client::startup_timings`](crate::Client::startup_timings). +/// +/// Durations are whole milliseconds, matching the existing `elapsed_ms` +/// tracing fields. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +#[non_exhaustive] +pub struct StartupTimings { + /// Time spent in `resolve::copilot_binary_with_extract_dir` locating (and, + /// for a bundled CLI, extracting) the copilot binary. `None` when the + /// caller passes an explicit [`CliProgram::Path`](crate::CliProgram::Path). + pub program_resolve_ms: Option, + /// Time spent spawning the CLI subprocess (`command.spawn()`). `None` for + /// the external-server and in-process transports, which do not spawn a + /// child. + pub process_spawn_ms: Option, + /// Time spent waiting for the TCP server to announce its listening port on + /// stdout. `Some` only for the TCP transport. + pub port_wait_ms: Option, + /// Total transport setup time. This includes spawning and connecting to a + /// subprocess, connecting to an external server, or starting the in-process + /// FFI runtime. `process_spawn_ms` and `port_wait_ms` provide nested detail + /// for spawned transports. + pub transport_setup_ms: u64, + /// Time spent on the `connect` protocol handshake in + /// [`Client::verify_protocol_version`](crate::Client::verify_protocol_version), + /// including the fallback to the legacy `ping` RPC. + pub handshake_ms: u64, + /// Time spent registering the filesystem provider via + /// `sessionFs.setProvider`. `Some` only when + /// [`ClientOptions::session_fs`](crate::ClientOptions::session_fs) is set. + pub session_fs_ms: Option, + /// Time spent registering the LLM inference provider via + /// `llmInference.setProvider`. `Some` only when + /// [`ClientOptions::request_handler`](crate::ClientOptions::request_handler) + /// is set. + pub llm_handler_ms: Option, + /// Total wall-clock time for [`Client::start`](crate::Client::start), from + /// entry to the client being ready. Always present. + pub total_ms: u64, +} + +impl StartupTimings { + /// Whole milliseconds of `duration`, saturating at [`u64::MAX`]. + pub(crate) fn millis(duration: Duration) -> u64 { + u64::try_from(duration.as_millis()).unwrap_or(u64::MAX) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn millis_truncates_to_whole_milliseconds() { + assert_eq!(StartupTimings::millis(Duration::from_micros(1_999)), 1); + assert_eq!(StartupTimings::millis(Duration::from_millis(250)), 250); + assert_eq!(StartupTimings::millis(Duration::ZERO), 0); + } + + #[test] + fn default_leaves_every_phase_unset() { + let timings = StartupTimings::default(); + assert_eq!(timings, StartupTimings::default()); + assert!(timings.program_resolve_ms.is_none()); + assert!(timings.process_spawn_ms.is_none()); + assert!(timings.port_wait_ms.is_none()); + assert_eq!(timings.transport_setup_ms, 0); + assert_eq!(timings.handshake_ms, 0); + assert!(timings.session_fs_ms.is_none()); + assert!(timings.llm_handler_ms.is_none()); + assert_eq!(timings.total_ms, 0); + } +} diff --git a/rust/src/types.rs b/rust/src/types.rs index dcbb51e48..392e0f840 100644 --- a/rust/src/types.rs +++ b/rust/src/types.rs @@ -33,7 +33,8 @@ use crate::provider_token::BearerTokenProvider; pub use crate::session_fs::{ DirEntry, DirEntryKind, FileInfo, FsError, SessionFsCapabilities, SessionFsConfig, SessionFsConventions, SessionFsProvider, SessionFsSqliteProvider, SessionFsSqliteQueryResult, - SessionFsSqliteQueryType, + SessionFsSqliteQueryType, SessionFsSqliteTransactionError, + SessionFsSqliteTransactionErrorClass, SessionFsSqliteTransactionStatement, }; pub use crate::trace_context::{TraceContext, TraceContextProvider}; use crate::transforms::SystemMessageTransform; @@ -345,6 +346,12 @@ pub struct Tool { /// access control. #[serde(default, skip_serializing_if = "is_false")] pub skip_permission: bool, + /// When `true`, a successful call to this tool ends the agent turn: the + /// runtime's tool phase halts instead of feeding the result back to the + /// model for another round. A failed call leaves the loop running so the + /// model can read the error and retry. + #[serde(default, skip_serializing_if = "is_false")] + pub is_terminal: bool, /// Controls whether the tool may be deferred (loaded lazily via tool /// search) rather than always pre-loaded. When [`DeferMode::Auto`], the /// tool can be deferred and surfaced through tool search. When @@ -469,6 +476,18 @@ impl Tool { self } + /// Sets whether a successful call to this tool ends the agent turn. + /// + /// When `true`, the runtime's tool phase halts after a successful call + /// instead of feeding the result back to the model for another round. A + /// failed call leaves the loop running so the model can read the error and + /// retry. + #[must_use] + pub fn with_is_terminal(mut self, is_terminal: bool) -> Self { + self.is_terminal = is_terminal; + self + } + /// Set the deferral mode controlling whether the tool may be loaded /// lazily via tool search ([`DeferMode::Auto`]) or always pre-loaded /// ([`DeferMode::Never`]). @@ -511,6 +530,7 @@ impl std::fmt::Debug for Tool { .field("parameters", &self.parameters) .field("overrides_built_in_tool", &self.overrides_built_in_tool) .field("skip_permission", &self.skip_permission) + .field("is_terminal", &self.is_terminal) .field("defer", &self.defer) .field("metadata", &self.metadata) .field( @@ -644,8 +664,8 @@ pub struct CustomAgentConfig { pub model: Option, /// Reasoning effort level for this agent's model. /// - /// When unset, no per-agent override is sent and the backend chooses its - /// default. The parent session effort is not inherited. + /// When unset, the runtime resolves model configuration, then inherits the + /// parent effort only for the same model. #[serde(default, skip_serializing_if = "Option::is_none")] pub reasoning_effort: Option, } @@ -824,6 +844,80 @@ impl ToolSearchConfig { } } +/// Configuration for the built-in GitHub MCP server. +/// +/// `disable_form_deferral` only applies to the built-in GitHub MCP server and +/// only has an effect when MCP Apps and form-backed GitHub tools are enabled. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub struct GitHubMcpToolConfig { + /// Whether all GitHub MCP tools are enabled. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub enable_all_tools: Option, + /// Additional GitHub MCP toolsets to enable. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub additional_toolsets: Option>, + /// Additional GitHub MCP tools to enable. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub additional_tools: Option>, + /// Whether GitHub MCP insiders mode is enabled. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub enable_insiders_mode: Option, + /// Disables form deferral for GitHub MCP tools. This only applies to the + /// built-in GitHub MCP server and only has an effect when MCP Apps and + /// form-backed GitHub tools are enabled. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub disable_form_deferral: Option, +} + +impl GitHubMcpToolConfig { + /// Construct an empty GitHub MCP tool configuration. + pub fn new() -> Self { + Self::default() + } + + /// Set whether all GitHub MCP tools are enabled. + pub fn with_enable_all_tools(mut self, value: bool) -> Self { + self.enable_all_tools = Some(value); + self + } + + /// Set the additional GitHub MCP toolsets to enable. + pub fn with_additional_toolsets(mut self, values: I) -> Self + where + I: IntoIterator, + S: Into, + { + self.additional_toolsets = Some(values.into_iter().map(Into::into).collect()); + self + } + + /// Set the additional GitHub MCP tools to enable. + pub fn with_additional_tools(mut self, values: I) -> Self + where + I: IntoIterator, + S: Into, + { + self.additional_tools = Some(values.into_iter().map(Into::into).collect()); + self + } + + /// Set whether GitHub MCP insiders mode is enabled. + pub fn with_enable_insiders_mode(mut self, value: bool) -> Self { + self.enable_insiders_mode = Some(value); + self + } + + /// Disable form deferral for GitHub MCP tools. This only applies to the + /// built-in GitHub MCP server and only has an effect when MCP Apps and + /// form-backed GitHub tools are enabled. + pub fn with_disable_form_deferral(mut self, value: bool) -> Self { + self.disable_form_deferral = Some(value); + self + } +} + /// Configures infinite sessions: persistent workspaces with automatic /// context-window compaction. /// @@ -1351,7 +1445,7 @@ impl CapiSessionOptions { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AzureProviderOptions { - /// Azure API version. Defaults to `"2024-10-21"`. + /// Azure API version. When omitted, the runtime uses the GA versionless v1 route. #[serde(default, skip_serializing_if = "Option::is_none")] pub api_version: Option, } @@ -1669,6 +1763,99 @@ pub struct CopilotExpAssignmentResponse { pub assignment_context: String, } +/// Controls whether bypass-permissions mode is available in a managed session. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +#[non_exhaustive] +pub enum DisableBypassPermissionsMode { + /// Turn off bypass-permissions mode. + Disable, +} + +/// Permission rules injected as a managed-settings layer at session bootstrap. +/// +/// All fields are optional; an omitted field imposes no constraint from this +/// layer. This layer composes restrictively with any server- or device-level +/// managed settings: [`deny`](Self::deny) and [`ask`](Self::ask) rules are +/// unioned across layers, every present [`allow`](Self::allow) list must admit a +/// tool for it to be allowed, and +/// [`disable_bypass_permissions_mode`](Self::disable_bypass_permissions_mode) is +/// honored if any layer sets it (deny-wins). +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub struct ManagedSettingsPermissions { + /// When set to `"disable"`, bypass-permissions mode is turned off for the + /// session regardless of other layers. Serialized as + /// `disableBypassPermissionsMode`. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub disable_bypass_permissions_mode: Option, + /// Tool-permission patterns that are always denied. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub deny: Option>, + /// Tool-permission patterns that require an explicit ask. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub ask: Option>, + /// Tool-permission patterns that are allowed without prompting. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub allow: Option>, +} + +impl ManagedSettingsPermissions { + /// Sets the bypass-permissions policy for this managed layer. + pub fn with_disable_bypass_permissions_mode( + mut self, + value: DisableBypassPermissionsMode, + ) -> Self { + self.disable_bypass_permissions_mode = Some(value); + self + } + + /// Sets the rules that are always denied. + pub fn with_deny(mut self, rules: Vec) -> Self { + self.deny = Some(rules); + self + } + + /// Sets the rules that require explicit approval. + pub fn with_ask(mut self, rules: Vec) -> Self { + self.ask = Some(rules); + self + } + + /// Sets the rules that are allowed without prompting. + pub fn with_allow(mut self, rules: Vec) -> Self { + self.allow = Some(rules); + self + } +} + +/// Managed-settings layer injected at session startup. Currently carries only a +/// [`permissions`](Self::permissions) object. +/// +/// This layer is startup-only and is not persisted with the session. It must be +/// re-supplied on resume to remain in effect; omitting it on resume clears the +/// previously injected layer. It can be combined with +/// [`SessionConfig::enable_managed_settings`]. Older runtimes may ignore this +/// additive field, so hosts must not rely on injected policy until they ship a +/// compatible runtime. +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub struct ManagedSettings { + /// Permission rules for this managed-settings layer. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub permissions: Option, +} + +impl ManagedSettings { + /// Sets the permissions-only managed policy. + pub fn with_permissions(mut self, permissions: ManagedSettingsPermissions) -> Self { + self.permissions = Some(permissions); + self + } +} + /// Configuration for creating a new session via the `session.create` RPC. /// /// All fields are optional — the CLI applies sensible defaults. @@ -1785,7 +1972,8 @@ pub struct SessionConfig { /// applied automatically at session creation/resume time. `None` means no /// explicit value is set and the runtime default takes effect. pub mcp_oauth_token_storage: Option, - /// When true, the CLI runs config discovery (MCP config files, skills, plugins). + /// Enables runtime discovery of supported configuration. Explicitly supplied + /// configuration takes precedence over discovered values. pub enable_config_discovery: Option, /// When true, skips embedding retrieval for this session. pub skip_embedding_retrieval: Option, @@ -1831,6 +2019,11 @@ pub struct SessionConfig { /// /// Defaults to `None` (treated as `false`). pub enable_mcp_apps: Option, + /// Configuration for the built-in GitHub MCP server. + /// + /// `disable_form_deferral` only applies to that server and only has an + /// effect when MCP Apps and form-backed GitHub tools are enabled. + pub github_mcp_tool_config: Option, /// Skill directory paths passed through to the GitHub Copilot CLI. pub skill_directories: Option>, /// Additional directories to search for custom instruction files. @@ -1847,6 +2040,10 @@ pub struct SessionConfig { /// Skill names to disable. Skills in this set will not be available /// even if found in skill directories. pub disabled_skills: Option>, + /// Exact MCP server names to disable for this session. Disabled servers are + /// not started or authenticated on create or cold resume; a resident resume + /// cannot stop servers that are already running. + pub disabled_mcp_servers: Option>, /// Enable session hooks. When `true`, the CLI sends `hooks.invoke` /// RPC requests at key lifecycle points (pre/post tool use, prompt /// submission, session start/end, errors). @@ -1896,6 +2093,9 @@ pub struct SessionConfig { pub enable_session_telemetry: Option, /// **Experimental.** Enables native model citations for supported providers. pub enable_citations: Option, + /// Opts in to capturing file changes from the first turn for session rewind + /// and cumulative session diff. + pub enable_file_change_tracking: Option, /// **Experimental.** Limits applied to this session's current accounting window. pub session_limits: Option, /// Per-property overrides for model capabilities, deep-merged over @@ -1909,6 +2109,10 @@ pub struct SessionConfig { /// Working directory for the session. Tool operations resolve /// relative paths against this directory. pub working_directory: Option, + /// Additional directories the agent may access beyond the working directory. + /// Relative paths resolve against the session working directory. Re-supply + /// them when resuming a session. + pub additional_directories: Option>, /// Per-session GitHub token. Distinct from /// [`ClientOptions::github_token`](crate::ClientOptions::github_token), /// which authenticates the CLI process itself; this token determines @@ -1947,6 +2151,15 @@ pub struct SessionConfig { /// (fail-closed). When `None`, behaves exactly as before. Set via /// [`with_enable_managed_settings`](Self::with_enable_managed_settings). pub enable_managed_settings: Option, + /// Optional managed-settings layer injected at session bootstrap. Currently + /// carries a [`permissions`](ManagedSettingsPermissions) object that composes + /// restrictively with any server- or device-level managed settings. This + /// layer is startup-only and is not persisted: it must be re-supplied on + /// resume to remain in effect. Can be combined with + /// [`enable_managed_settings`](Self::enable_managed_settings). Serialized on + /// the wire as `managedSettings`. Set via + /// [`with_managed_settings`](Self::with_managed_settings). + pub managed_settings: Option, /// Custom session filesystem provider for this session. Required when /// the [`Client`](crate::Client) was started with /// [`ClientOptions::session_fs`](crate::ClientOptions::session_fs) set. @@ -1989,10 +2202,15 @@ pub struct SessionConfig { /// Applied via `session.options.update` after create/resume. Defaults to /// `true` in [`crate::ClientMode::Empty`] when unset. pub skip_custom_instructions: Option, - /// Whether to constrain custom agents to local-only execution. Applied - /// via `session.options.update` after create/resume. Defaults to `true` - /// in [`crate::ClientMode::Empty`] when unset. + /// Whether to constrain custom agents to local-only execution. Sent with + /// the initial create request and maintained via `session.options.update`. + /// Defaults to `true` in [`crate::ClientMode::Empty`] when unset. pub custom_agents_local_only: Option, + /// Controls whether the session enables experimental features. + /// + /// Defaults to `false` in [`crate::ClientMode::Empty`] when unset; + /// in `copilot-cli` mode, leaving this unset lets the runtime decide. + pub enable_experimental_mode: Option, /// Whether to include the `Co-authored-by` trailer in commit messages. /// Applied via `session.options.update` after create/resume. Defaults to /// `false` in [`crate::ClientMode::Empty`] when unset. @@ -2058,6 +2276,7 @@ impl std::fmt::Debug for SessionConfig { .field("large_output", &self.large_output) .field("tool_search", &self.tool_search) .field("disabled_skills", &self.disabled_skills) + .field("disabled_mcp_servers", &self.disabled_mcp_servers) .field("hooks", &self.hooks) .field("custom_agents", &self.custom_agents) .field("default_agent", &self.default_agent) @@ -2067,11 +2286,16 @@ impl std::fmt::Debug for SessionConfig { .field("capi", &self.capi) .field("enable_session_telemetry", &self.enable_session_telemetry) .field("enable_citations", &self.enable_citations) + .field( + "enable_file_change_tracking", + &self.enable_file_change_tracking, + ) .field("session_limits", &self.session_limits) .field("model_capabilities", &self.model_capabilities) .field("memory", &self.memory) .field("config_directory", &self.config_directory) .field("working_directory", &self.working_directory) + .field("additional_directories", &self.additional_directories) .field( "github_token", &self.github_token.as_ref().map(|_| ""), @@ -2085,6 +2309,8 @@ impl std::fmt::Debug for SessionConfig { .field("commands", &self.commands) .field("exp_assignments", &self.exp_assignments) .field("enable_managed_settings", &self.enable_managed_settings) + .field("enable_experimental_mode", &self.enable_experimental_mode) + .field("managed_settings", &self.managed_settings) .field( "session_fs_provider", &self.session_fs_provider.as_ref().map(|_| ""), @@ -2164,12 +2390,14 @@ impl Default for SessionConfig { enable_skills: None, embedding_cache_storage: None, enable_mcp_apps: None, + github_mcp_tool_config: None, skill_directories: None, instruction_directories: None, plugin_directories: None, large_output: None, tool_search: None, disabled_skills: None, + disabled_mcp_servers: None, hooks: None, custom_agents: None, default_agent: None, @@ -2181,11 +2409,13 @@ impl Default for SessionConfig { models: None, enable_session_telemetry: None, enable_citations: None, + enable_file_change_tracking: None, session_limits: None, model_capabilities: None, memory: None, config_directory: None, working_directory: None, + additional_directories: None, github_token: None, remote_session: None, cloud: None, @@ -2193,6 +2423,7 @@ impl Default for SessionConfig { commands: None, exp_assignments: None, enable_managed_settings: None, + managed_settings: None, session_fs_provider: None, permission_handler: None, elicitation_handler: None, @@ -2205,6 +2436,7 @@ impl Default for SessionConfig { system_message_transform: None, skip_custom_instructions: None, custom_agents_local_only: None, + enable_experimental_mode: None, coauthor_enabled: None, manage_schedule_enabled: None, } @@ -2322,6 +2554,7 @@ impl SessionConfig { request_auto_mode_switch, request_elicitation, request_mcp_apps: self.enable_mcp_apps.unwrap_or(false), + github_mcp_tool_config: self.github_mcp_tool_config, hooks: hooks_flag, skill_directories: self.skill_directories, instruction_directories: self.instruction_directories, @@ -2329,7 +2562,9 @@ impl SessionConfig { large_output: self.large_output, tool_search: self.tool_search, disabled_skills: self.disabled_skills, + disabled_mcp_servers: self.disabled_mcp_servers, custom_agents: self.custom_agents, + custom_agents_local_only: self.custom_agents_local_only, default_agent: self.default_agent, agent: self.agent, infinite_sessions: self.infinite_sessions, @@ -2339,11 +2574,13 @@ impl SessionConfig { models: self.models, enable_session_telemetry: self.enable_session_telemetry, enable_citations: self.enable_citations, + enable_file_change_tracking: self.enable_file_change_tracking, session_limits: self.session_limits, model_capabilities: self.model_capabilities, memory: self.memory, config_dir: self.config_directory, working_directory: self.working_directory, + additional_directories: self.additional_directories, github_token: self.github_token, remote_session: self.remote_session, cloud: self.cloud, @@ -2352,6 +2589,8 @@ impl SessionConfig { commands: wire_commands, exp_assignments: self.exp_assignments, enable_managed_settings: self.enable_managed_settings, + is_experimental_mode: self.enable_experimental_mode, + managed_settings: self.managed_settings, }; let runtime = SessionConfigRuntime { @@ -2640,7 +2879,8 @@ impl SessionConfig { self } - /// Enable or disable CLI config discovery (MCP config files, skills, plugins). + /// Enables runtime discovery of supported configuration. Explicitly supplied + /// configuration takes precedence over discovered values. pub fn with_enable_config_discovery(mut self, enable: bool) -> Self { self.enable_config_discovery = Some(enable); self @@ -2701,6 +2941,12 @@ impl SessionConfig { self } + /// Set the built-in GitHub MCP server configuration. + pub fn with_github_mcp_tool_config(mut self, config: GitHubMcpToolConfig) -> Self { + self.github_mcp_tool_config = Some(config); + self + } + /// Set skill directory paths passed through to the CLI. pub fn with_skill_directories(mut self, paths: I) -> Self where @@ -2756,6 +3002,16 @@ impl SessionConfig { self } + /// Set exact MCP server names to disable for this session. + pub fn with_disabled_mcp_servers(mut self, names: I) -> Self + where + I: IntoIterator, + S: Into, + { + self.disabled_mcp_servers = Some(names.into_iter().map(Into::into).collect()); + self + } + /// Set the custom agents (sub-agents) configured for this session. pub fn with_custom_agents>( mut self, @@ -2831,6 +3087,13 @@ impl SessionConfig { self } + /// Opt in to capturing file changes from the first turn for session rewind + /// and cumulative session diff. + pub fn with_enable_file_change_tracking(mut self, enable: bool) -> Self { + self.enable_file_change_tracking = Some(enable); + self + } + /// **Experimental.** Set limits for this session's current accounting window. pub fn with_session_limits(mut self, limits: SessionLimitsConfig) -> Self { self.session_limits = Some(limits); @@ -2865,6 +3128,16 @@ impl SessionConfig { self } + /// Set directories the agent may access beyond the working directory. + pub fn with_additional_directories(mut self, paths: I) -> Self + where + I: IntoIterator, + P: Into, + { + self.additional_directories = Some(paths.into_iter().map(Into::into).collect()); + self + } + /// Set the per-session GitHub token. Distinct from /// [`ClientOptions::github_token`](crate::ClientOptions::github_token); /// this token determines the GitHub identity used for content exclusion, @@ -2908,6 +3181,12 @@ impl SessionConfig { self } + /// Set [`enable_experimental_mode`](Self::enable_experimental_mode). + pub fn with_enable_experimental_mode(mut self, enable_experimental_mode: bool) -> Self { + self.enable_experimental_mode = Some(enable_experimental_mode); + self + } + /// Set [`Self::coauthor_enabled`]. pub fn with_coauthor_enabled(mut self, value: bool) -> Self { self.coauthor_enabled = Some(value); @@ -2942,6 +3221,15 @@ impl SessionConfig { self.enable_managed_settings = Some(enabled); self } + + /// Inject a managed-settings layer (currently permission rules) at session + /// bootstrap. This layer is startup-only and is not persisted, so it must be + /// re-supplied on resume to remain in effect. Can be combined with + /// [`with_enable_managed_settings`](Self::with_enable_managed_settings). + pub fn with_managed_settings(mut self, managed_settings: ManagedSettings) -> Self { + self.managed_settings = Some(managed_settings); + self + } } /// /// See [`SessionConfig`] for the construction patterns (chained `with_*` @@ -3010,7 +3298,8 @@ pub struct ResumeSessionConfig { /// Controls how MCP OAuth tokens are stored for this session. /// See [`SessionConfig::mcp_oauth_token_storage`] for details. pub mcp_oauth_token_storage: Option, - /// Enable config discovery on resume. + /// Enables runtime discovery of supported configuration. Explicitly supplied + /// configuration takes precedence over discovered values. pub enable_config_discovery: Option, /// When true, skips embedding retrieval on resume. pub skip_embedding_retrieval: Option, @@ -3034,6 +3323,11 @@ pub struct ResumeSessionConfig { /// Enable MCP Apps (SEP-1865) UI passthrough on resume. See /// [`SessionConfig::enable_mcp_apps`]. Defaults to `None` (treated as `false`). pub enable_mcp_apps: Option, + /// Configuration for the built-in GitHub MCP server. + /// + /// `disable_form_deferral` only applies to that server and only has an + /// effect when MCP Apps and form-backed GitHub tools are enabled. + pub github_mcp_tool_config: Option, /// Skill directory paths passed through to the GitHub Copilot CLI on resume. pub skill_directories: Option>, /// Additional directories to search for custom instruction files on @@ -3048,6 +3342,9 @@ pub struct ResumeSessionConfig { pub tool_search: Option, /// Skill names to disable on resume. pub disabled_skills: Option>, + /// Exact MCP server names to disable on resume. This prevents startup and + /// authentication during a cold resume, but cannot stop resident servers. + pub disabled_mcp_servers: Option>, /// Enable session hooks on resume. pub hooks: Option, /// Custom agents to re-supply on resume. @@ -3088,6 +3385,10 @@ pub struct ResumeSessionConfig { pub enable_session_telemetry: Option, /// **Experimental.** Enables native model citations for supported providers. pub enable_citations: Option, + /// Opts in to capturing file changes for session rewind and cumulative + /// session diff when the resumed session has a valid baseline. Earlier + /// untracked changes cannot be reconstructed. + pub enable_file_change_tracking: Option, /// **Experimental.** Limits applied to this session's current accounting window. pub session_limits: Option, /// Per-property model capability overrides on resume. @@ -3098,6 +3399,9 @@ pub struct ResumeSessionConfig { pub config_directory: Option, /// Per-session working directory on resume. pub working_directory: Option, + /// Additional directories the agent may access on resume. Relative paths + /// resolve against the session working directory. + pub additional_directories: Option>, /// Per-session GitHub token on resume. See /// [`SessionConfig::github_token`]. pub github_token: Option, @@ -3122,6 +3426,12 @@ pub struct ResumeSessionConfig { /// process restart. Set via /// [`with_enable_managed_settings`](Self::with_enable_managed_settings). pub enable_managed_settings: Option, + /// Optional managed-settings layer injected on resume. See + /// [`SessionConfig::managed_settings`]. This layer is not persisted, so it + /// must be re-supplied on resume to remain in effect; omitting it clears the + /// previously injected layer. Serialized on the wire as `managedSettings`. + /// Set via [`with_managed_settings`](Self::with_managed_settings). + pub managed_settings: Option, /// Custom session filesystem provider. Required on resume when the /// [`Client`](crate::Client) was started with /// [`ClientOptions::session_fs`](crate::ClientOptions::session_fs). @@ -3165,6 +3475,11 @@ pub struct ResumeSessionConfig { pub skip_custom_instructions: Option, /// See [`SessionConfig::custom_agents_local_only`]. pub custom_agents_local_only: Option, + /// Controls whether the session enables experimental features. + /// + /// Defaults to `false` in [`crate::ClientMode::Empty`] when unset; + /// in `copilot-cli` mode, leaving this unset lets the runtime decide. + pub enable_experimental_mode: Option, /// See [`SessionConfig::coauthor_enabled`]. pub coauthor_enabled: Option, /// See [`SessionConfig::manage_schedule_enabled`]. @@ -3227,6 +3542,7 @@ impl std::fmt::Debug for ResumeSessionConfig { .field("large_output", &self.large_output) .field("tool_search", &self.tool_search) .field("disabled_skills", &self.disabled_skills) + .field("disabled_mcp_servers", &self.disabled_mcp_servers) .field("hooks", &self.hooks) .field("custom_agents", &self.custom_agents) .field("default_agent", &self.default_agent) @@ -3236,11 +3552,16 @@ impl std::fmt::Debug for ResumeSessionConfig { .field("capi", &self.capi) .field("enable_session_telemetry", &self.enable_session_telemetry) .field("enable_citations", &self.enable_citations) + .field( + "enable_file_change_tracking", + &self.enable_file_change_tracking, + ) .field("session_limits", &self.session_limits) .field("model_capabilities", &self.model_capabilities) .field("memory", &self.memory) .field("config_directory", &self.config_directory) .field("working_directory", &self.working_directory) + .field("additional_directories", &self.additional_directories) .field( "github_token", &self.github_token.as_ref().map(|_| ""), @@ -3253,6 +3574,8 @@ impl std::fmt::Debug for ResumeSessionConfig { .field("commands", &self.commands) .field("exp_assignments", &self.exp_assignments) .field("enable_managed_settings", &self.enable_managed_settings) + .field("enable_experimental_mode", &self.enable_experimental_mode) + .field("managed_settings", &self.managed_settings) .field( "session_fs_provider", &self.session_fs_provider.as_ref().map(|_| ""), @@ -3376,6 +3699,7 @@ impl ResumeSessionConfig { request_auto_mode_switch, request_elicitation, request_mcp_apps: self.enable_mcp_apps.unwrap_or(false), + github_mcp_tool_config: self.github_mcp_tool_config, hooks: hooks_flag, skill_directories: self.skill_directories, instruction_directories: self.instruction_directories, @@ -3383,7 +3707,9 @@ impl ResumeSessionConfig { large_output: self.large_output, tool_search: self.tool_search, disabled_skills: self.disabled_skills, + disabled_mcp_servers: self.disabled_mcp_servers, custom_agents: self.custom_agents, + custom_agents_local_only: self.custom_agents_local_only, default_agent: self.default_agent, agent: self.agent, infinite_sessions: self.infinite_sessions, @@ -3393,11 +3719,13 @@ impl ResumeSessionConfig { models: self.models, enable_session_telemetry: self.enable_session_telemetry, enable_citations: self.enable_citations, + enable_file_change_tracking: self.enable_file_change_tracking, session_limits: self.session_limits, model_capabilities: self.model_capabilities, memory: self.memory, config_dir: self.config_directory, working_directory: self.working_directory, + additional_directories: self.additional_directories, github_token: self.github_token, remote_session: self.remote_session, include_sub_agent_streaming_events: self.include_sub_agent_streaming_events, @@ -3405,6 +3733,8 @@ impl ResumeSessionConfig { commands: wire_commands, exp_assignments: self.exp_assignments, enable_managed_settings: self.enable_managed_settings, + is_experimental_mode: self.enable_experimental_mode, + managed_settings: self.managed_settings, suppress_resume_event: self.suppress_resume_event, continue_pending_work: self.continue_pending_work, }; @@ -3467,12 +3797,14 @@ impl ResumeSessionConfig { enable_skills: None, embedding_cache_storage: None, enable_mcp_apps: None, + github_mcp_tool_config: None, skill_directories: None, instruction_directories: None, plugin_directories: None, large_output: None, tool_search: None, disabled_skills: None, + disabled_mcp_servers: None, hooks: None, custom_agents: None, default_agent: None, @@ -3484,17 +3816,20 @@ impl ResumeSessionConfig { models: None, enable_session_telemetry: None, enable_citations: None, + enable_file_change_tracking: None, session_limits: None, model_capabilities: None, memory: None, config_directory: None, working_directory: None, + additional_directories: None, github_token: None, remote_session: None, include_sub_agent_streaming_events: None, commands: None, exp_assignments: None, enable_managed_settings: None, + managed_settings: None, session_fs_provider: None, suppress_resume_event: None, continue_pending_work: None, @@ -3509,6 +3844,7 @@ impl ResumeSessionConfig { system_message_transform: None, skip_custom_instructions: None, custom_agents_local_only: None, + enable_experimental_mode: None, coauthor_enabled: None, manage_schedule_enabled: None, } @@ -3764,7 +4100,8 @@ impl ResumeSessionConfig { self } - /// Enable or disable CLI config discovery on resume. + /// Enables runtime discovery of supported configuration. Explicitly supplied + /// configuration takes precedence over discovered values. pub fn with_enable_config_discovery(mut self, enable: bool) -> Self { self.enable_config_discovery = Some(enable); self @@ -3825,6 +4162,12 @@ impl ResumeSessionConfig { self } + /// Set the built-in GitHub MCP server configuration. + pub fn with_github_mcp_tool_config(mut self, config: GitHubMcpToolConfig) -> Self { + self.github_mcp_tool_config = Some(config); + self + } + /// Set skill directory paths passed through to the CLI on resume. pub fn with_skill_directories(mut self, paths: I) -> Self where @@ -3880,6 +4223,16 @@ impl ResumeSessionConfig { self } + /// Set exact MCP server names to disable for this session. + pub fn with_disabled_mcp_servers(mut self, names: I) -> Self + where + I: IntoIterator, + S: Into, + { + self.disabled_mcp_servers = Some(names.into_iter().map(Into::into).collect()); + self + } + /// Re-supply custom agents on resume. pub fn with_custom_agents>( mut self, @@ -3953,6 +4306,13 @@ impl ResumeSessionConfig { self } + /// Opt in to capturing file changes for session rewind and cumulative + /// session diff when the resumed session has a valid baseline. + pub fn with_enable_file_change_tracking(mut self, enable: bool) -> Self { + self.enable_file_change_tracking = Some(enable); + self + } + /// **Experimental.** Set limits for this session's current accounting window. pub fn with_session_limits(mut self, limits: SessionLimitsConfig) -> Self { self.session_limits = Some(limits); @@ -3986,6 +4346,16 @@ impl ResumeSessionConfig { self } + /// Set directories the agent may access beyond the working directory on resume. + pub fn with_additional_directories(mut self, paths: I) -> Self + where + I: IntoIterator, + P: Into, + { + self.additional_directories = Some(paths.into_iter().map(Into::into).collect()); + self + } + /// Set the per-session GitHub token on resume. See /// [`SessionConfig::github_token`] for distinction from the /// client-level token. @@ -4038,6 +4408,12 @@ impl ResumeSessionConfig { self } + /// Set [`enable_experimental_mode`](Self::enable_experimental_mode). + pub fn with_enable_experimental_mode(mut self, enable_experimental_mode: bool) -> Self { + self.enable_experimental_mode = Some(enable_experimental_mode); + self + } + /// Set [`Self::coauthor_enabled`]. pub fn with_coauthor_enabled(mut self, value: bool) -> Self { self.coauthor_enabled = Some(value); @@ -4065,6 +4441,14 @@ impl ResumeSessionConfig { self.enable_managed_settings = Some(enabled); self } + + /// Inject a managed-settings layer (currently permission rules) on resume. + /// See [`SessionConfig::with_managed_settings`]. Must be re-supplied on + /// resume; omitting it clears the previously injected layer. + pub fn with_managed_settings(mut self, managed_settings: ManagedSettings) -> Self { + self.managed_settings = Some(managed_settings); + self + } } /// Controls how the system message is constructed. @@ -4223,7 +4607,7 @@ impl LogOptions { #[derive(Debug, Clone, Default)] pub struct SetModelOptions { /// Reasoning effort for the new model (e.g. `"low"`, `"medium"`, - /// `"high"`, `"xhigh"`). + /// `"high"`, `"xhigh"`, `"max"`). pub reasoning_effort: Option, /// Reasoning summary mode for the new model. Use /// [`ReasoningSummary::None`] to suppress summary output regardless of @@ -5389,7 +5773,9 @@ pub use crate::generated::api_types::{ Model, ModelBilling, ModelBillingTokenPrices, ModelBillingTokenPricesLongContext, ModelCapabilities, ModelCapabilitiesLimits, ModelCapabilitiesLimitsVision, ModelCapabilitiesSupports, ModelList, ModelPolicy, PermissionDecision, - PermissionDecisionApproveOnce, PermissionDecisionReject, PermissionDecisionUserNotAvailable, + PermissionDecisionApproveOnce, PermissionDecisionContext, PermissionDecisionOutcome, + PermissionDecisionReject, PermissionDecisionSource, PermissionDecisionSurface, + PermissionDecisionUserNotAvailable, }; /// Permission categories the CLI may request approval for. @@ -5440,8 +5826,15 @@ pub struct PermissionRequestData { /// to a specific tool invocation. #[serde(default, skip_serializing_if = "Option::is_none")] pub tool_call_id: Option, - /// The full permission request params from the CLI. The shape varies by - /// permission type and CLI version, so we preserve it as `Value`. + /// Whether managed policy requires an explicit human decision. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub managed_approval_required: Option, + /// Whether managed settings are enabled for this session. + #[serde(default, skip_serializing_if = "is_false")] + pub managed_settings_enabled: bool, + /// The full permission event params from the CLI, including the request ID + /// and nested permission request. The shape varies by permission type and + /// CLI version, so we preserve it as `Value`. #[serde(flatten)] pub extra: Value, } @@ -5490,12 +5883,12 @@ mod tests { AgentMode, Attachment, AttachmentLineRange, AttachmentSelectionPosition, AttachmentSelectionRange, AzureProviderOptions, CapiSessionOptions, ConnectionState, CopilotExpAssignmentResponse, CustomAgentConfig, DeliveryMode, ExpConfigEntry, - ExpFlagValue, ExtensionInfo, GitHubReferenceType, InfiniteSessionConfig, - LargeToolOutputConfig, McpServerConfig, McpStdioServerConfig, MemoryConfiguration, - NamedProviderConfig, ProviderConfig, ProviderModelConfig, ReasoningSummary, - ResumeSessionConfig, SessionConfig, SessionEvent, SessionId, SystemMessageConfig, Tool, - ToolBinaryResult, ToolResult, ToolResultExpanded, ToolResultResponse, - ensure_attachment_display_names, + ExpFlagValue, ExtensionInfo, GitHubMcpToolConfig, GitHubReferenceType, + InfiniteSessionConfig, LargeToolOutputConfig, McpServerConfig, McpStdioServerConfig, + MemoryConfiguration, NamedProviderConfig, ProviderConfig, ProviderModelConfig, + ReasoningSummary, ResumeSessionConfig, SessionConfig, SessionEvent, SessionId, + SystemMessageConfig, Tool, ToolBinaryResult, ToolResult, ToolResultExpanded, + ToolResultResponse, ensure_attachment_display_names, }; use crate::generated::session_events::TypedSessionEvent; @@ -5771,6 +6164,35 @@ mod tests { assert!(!wire.request_mcp_apps); } + #[test] + fn custom_agents_local_only_serializes_on_create_and_resume() { + let (create_wire, _) = SessionConfig::default() + .with_custom_agents_local_only(false) + .into_wire(Some(SessionId::from("create-locality"))) + .expect("create config has no duplicate handlers"); + let create_json = serde_json::to_value(&create_wire).unwrap(); + assert_eq!(create_json["customAgentsLocalOnly"], false); + + let (resume_wire, _) = ResumeSessionConfig::new(SessionId::from("resume-locality")) + .with_custom_agents_local_only(false) + .into_wire() + .expect("resume config has no duplicate handlers"); + let resume_json = serde_json::to_value(&resume_wire).unwrap(); + assert_eq!(resume_json["customAgentsLocalOnly"], false); + + let (unset_create_wire, _) = SessionConfig::default() + .into_wire(Some(SessionId::from("create-unset"))) + .expect("create config has no duplicate handlers"); + let unset_create_json = serde_json::to_value(&unset_create_wire).unwrap(); + assert!(unset_create_json.get("customAgentsLocalOnly").is_none()); + + let (unset_resume_wire, _) = ResumeSessionConfig::new(SessionId::from("resume-unset")) + .into_wire() + .expect("resume config has no duplicate handlers"); + let unset_resume_json = serde_json::to_value(&unset_resume_wire).unwrap(); + assert!(unset_resume_json.get("customAgentsLocalOnly").is_none()); + } + #[test] fn session_config_enable_mcp_apps_sets_wire_flag_and_serializes() { let cfg = SessionConfig::default().with_enable_mcp_apps(true); @@ -5800,6 +6222,47 @@ mod tests { assert_eq!(json["requestMcpApps"], serde_json::Value::Bool(true)); } + #[test] + fn github_mcp_tool_config_serializes_for_create_and_resume() { + let github_config = GitHubMcpToolConfig::new() + .with_enable_all_tools(true) + .with_additional_toolsets(["repos"]) + .with_additional_tools(["get_issue"]) + .with_enable_insiders_mode(true) + .with_disable_form_deferral(true); + + let (create_wire, _) = SessionConfig::default() + .with_github_mcp_tool_config(github_config.clone()) + .into_wire(Some(SessionId::from("github-mcp"))) + .expect("create config has no duplicate handlers"); + assert_eq!( + serde_json::to_value(&create_wire).unwrap()["githubMcpToolConfig"], + serde_json::json!({ + "enableAllTools": true, + "additionalToolsets": ["repos"], + "additionalTools": ["get_issue"], + "enableInsidersMode": true, + "disableFormDeferral": true, + }) + ); + + let (resume_wire, _) = ResumeSessionConfig::new(SessionId::from("github-mcp")) + .with_github_mcp_tool_config(github_config) + .into_wire() + .expect("resume config has no duplicate handlers"); + assert!(resume_wire.github_mcp_tool_config.is_some()); + + let (unset_wire, _) = SessionConfig::default() + .into_wire(Some(SessionId::from("github-mcp-unset"))) + .expect("default config has no duplicate handlers"); + assert!( + serde_json::to_value(&unset_wire) + .unwrap() + .get("githubMcpToolConfig") + .is_none() + ); + } + #[test] fn memory_configuration_constructors_and_serde() { assert!(MemoryConfiguration::enabled().enabled); @@ -6097,6 +6560,10 @@ mod tests { let cfg = SessionConfig { plugin_directories: Some(vec![PathBuf::from("/tmp/plugins")]), + disabled_mcp_servers: Some(vec![ + "local-files".to_string(), + "remote-github".to_string(), + ]), large_output: Some( LargeToolOutputConfig::new() .with_enabled(true) @@ -6111,6 +6578,10 @@ mod tests { .expect("no duplicate handlers"); let wire_json = serde_json::to_value(&wire).unwrap(); assert_eq!(wire_json["pluginDirectories"][0], "/tmp/plugins"); + assert_eq!( + wire_json["disabledMcpServers"], + serde_json::json!(["local-files", "remote-github"]) + ); assert_eq!(wire_json["largeOutput"]["enabled"], true); assert_eq!(wire_json["largeOutput"]["maxSizeBytes"], 1024); assert_eq!(wire_json["largeOutput"]["outputDir"], "/tmp/large-output"); @@ -6120,6 +6591,7 @@ mod tests { .expect("default has no duplicate handlers"); let empty_json = serde_json::to_value(&empty_wire).unwrap(); assert!(empty_json.get("pluginDirectories").is_none()); + assert!(empty_json.get("disabledMcpServers").is_none()); assert!(empty_json.get("largeOutput").is_none()); } @@ -6169,6 +6641,7 @@ mod tests { let mut cfg = ResumeSessionConfig::new(SessionId::from("sess-1")); cfg.plugin_directories = Some(vec![PathBuf::from("/tmp/plugins-r")]); + cfg.disabled_mcp_servers = Some(vec!["local-files-r".to_string()]); cfg.large_output = Some( LargeToolOutputConfig::new() .with_enabled(false) @@ -6179,6 +6652,10 @@ mod tests { let (wire, _) = cfg.into_wire().expect("no duplicate handlers"); let wire_json = serde_json::to_value(&wire).unwrap(); assert_eq!(wire_json["pluginDirectories"][0], "/tmp/plugins-r"); + assert_eq!( + wire_json["disabledMcpServers"], + serde_json::json!(["local-files-r"]) + ); assert_eq!(wire_json["largeOutput"]["enabled"], false); assert_eq!(wire_json["largeOutput"]["maxSizeBytes"], 2048); assert_eq!(wire_json["largeOutput"]["outputDir"], "/tmp/large-output-r"); @@ -6188,9 +6665,38 @@ mod tests { .expect("default resume has no duplicate handlers"); let empty_json = serde_json::to_value(&empty_wire).unwrap(); assert!(empty_json.get("pluginDirectories").is_none()); + assert!(empty_json.get("disabledMcpServers").is_none()); assert!(empty_json.get("largeOutput").is_none()); } + #[test] + fn session_config_clones_disabled_mcp_servers() { + let create = SessionConfig::default().with_disabled_mcp_servers(["local-files"]); + let mut create_clone = create.clone(); + create_clone + .disabled_mcp_servers + .as_mut() + .expect("configured disabled MCP servers") + .push("remote-github".to_string()); + assert_eq!( + create.disabled_mcp_servers.as_deref(), + Some(&["local-files".to_string()][..]) + ); + + let resume = ResumeSessionConfig::new(SessionId::from("sess-1")) + .with_disabled_mcp_servers(["local-files"]); + let mut resume_clone = resume.clone(); + resume_clone + .disabled_mcp_servers + .as_mut() + .expect("configured disabled MCP servers") + .push("remote-github".to_string()); + assert_eq!( + resume.disabled_mcp_servers.as_deref(), + Some(&["local-files".to_string()][..]) + ); + } + #[test] fn session_config_builder_composes() { use indexmap::IndexMap; @@ -6212,9 +6718,11 @@ mod tests { .with_enable_on_demand_instruction_discovery(true) .with_skill_directories([PathBuf::from("/tmp/skills")]) .with_disabled_skills(["broken-skill"]) + .with_disabled_mcp_servers(["local-files"]) .with_agent("researcher") .with_config_directory(PathBuf::from("/tmp/config")) .with_working_directory(PathBuf::from("/tmp/work")) + .with_additional_directories([PathBuf::from("/tmp/shared")]) .with_github_token("ghp_test") .with_capi(CapiSessionOptions::new().with_enable_web_socket_responses(false)) .with_enable_session_telemetry(false) @@ -6249,9 +6757,17 @@ mod tests { cfg.disabled_skills.as_deref(), Some(&["broken-skill".to_string()][..]) ); + assert_eq!( + cfg.disabled_mcp_servers.as_deref(), + Some(&["local-files".to_string()][..]) + ); assert_eq!(cfg.agent.as_deref(), Some("researcher")); assert_eq!(cfg.config_directory, Some(PathBuf::from("/tmp/config"))); assert_eq!(cfg.working_directory, Some(PathBuf::from("/tmp/work"))); + assert_eq!( + cfg.additional_directories.as_deref(), + Some(&[PathBuf::from("/tmp/shared")][..]) + ); assert_eq!(cfg.github_token.as_deref(), Some("ghp_test")); assert_eq!( cfg.capi, @@ -6283,9 +6799,11 @@ mod tests { .with_enable_on_demand_instruction_discovery(false) .with_skill_directories([PathBuf::from("/tmp/skills")]) .with_disabled_skills(["broken-skill"]) + .with_disabled_mcp_servers(["local-files"]) .with_agent("researcher") .with_config_directory(PathBuf::from("/tmp/config")) .with_working_directory(PathBuf::from("/tmp/work")) + .with_additional_directories([PathBuf::from("/tmp/shared")]) .with_github_token("ghp_test") .with_capi(CapiSessionOptions::new().with_enable_web_socket_responses(false)) .with_enable_session_telemetry(false) @@ -6320,9 +6838,17 @@ mod tests { cfg.disabled_skills.as_deref(), Some(&["broken-skill".to_string()][..]) ); + assert_eq!( + cfg.disabled_mcp_servers.as_deref(), + Some(&["local-files".to_string()][..]) + ); assert_eq!(cfg.agent.as_deref(), Some("researcher")); assert_eq!(cfg.config_directory, Some(PathBuf::from("/tmp/config"))); assert_eq!(cfg.working_directory, Some(PathBuf::from("/tmp/work"))); + assert_eq!( + cfg.additional_directories.as_deref(), + Some(&[PathBuf::from("/tmp/shared")][..]) + ); assert_eq!(cfg.github_token.as_deref(), Some("ghp_test")); assert_eq!( cfg.capi, @@ -6357,6 +6883,29 @@ mod tests { assert!(json.get("continuePendingWork").is_none()); } + #[test] + fn session_configs_serialize_additional_directories() { + let create = SessionConfig::default().with_additional_directories([ + PathBuf::from("/tmp/shared"), + PathBuf::from("/tmp/generated"), + ]); + let (create_wire, _) = create.into_wire(None).expect("no duplicate handlers"); + let create_json = serde_json::to_value(&create_wire).unwrap(); + assert_eq!( + create_json["additionalDirectories"], + serde_json::json!(["/tmp/shared", "/tmp/generated"]) + ); + + let resume = ResumeSessionConfig::new(SessionId::from("sess-1")) + .with_additional_directories([PathBuf::from("/tmp/resumed")]); + let (resume_wire, _) = resume.into_wire().expect("no duplicate handlers"); + let resume_json = serde_json::to_value(&resume_wire).unwrap(); + assert_eq!( + resume_json["additionalDirectories"], + serde_json::json!(["/tmp/resumed"]) + ); + } + /// The Rust field is `suppress_resume_event`, but the wire field stays /// `disableResume` to preserve compatibility with the runtime and other /// SDKs. @@ -7058,7 +7607,10 @@ mod permission_builder_tests { let h = resolve_create(cfg).expect("policy + handler yields handler"); assert!(matches!( dispatch(&h).await, - PermissionResult::Decision(PermissionDecision::ApproveOnce(_)) + PermissionResult::Decision { + decision: PermissionDecision::ApproveOnce(_), + .. + } )); } @@ -7068,7 +7620,10 @@ mod permission_builder_tests { let h = resolve_create(cfg).expect("policy alone yields handler"); assert!(matches!( dispatch(&h).await, - PermissionResult::Decision(PermissionDecision::ApproveOnce(_)) + PermissionResult::Decision { + decision: PermissionDecision::ApproveOnce(_), + .. + } )); } @@ -7086,11 +7641,17 @@ mod permission_builder_tests { let hb = resolve_create(b).unwrap(); assert!(matches!( dispatch(&ha).await, - PermissionResult::Decision(PermissionDecision::ApproveOnce(_)) + PermissionResult::Decision { + decision: PermissionDecision::ApproveOnce(_), + .. + } )); assert!(matches!( dispatch(&hb).await, - PermissionResult::Decision(PermissionDecision::ApproveOnce(_)) + PermissionResult::Decision { + decision: PermissionDecision::ApproveOnce(_), + .. + } )); } @@ -7106,11 +7667,17 @@ mod permission_builder_tests { let hb = resolve_create(b).unwrap(); assert!(matches!( dispatch(&ha).await, - PermissionResult::Decision(PermissionDecision::Reject(_)) + PermissionResult::Decision { + decision: PermissionDecision::Reject(_), + .. + } )); assert!(matches!( dispatch(&hb).await, - PermissionResult::Decision(PermissionDecision::Reject(_)) + PermissionResult::Decision { + decision: PermissionDecision::Reject(_), + .. + } )); } @@ -7122,7 +7689,10 @@ mod permission_builder_tests { let h = resolve_create(cfg).unwrap(); assert!(matches!( dispatch(&h).await, - PermissionResult::Decision(PermissionDecision::Reject(_)) + PermissionResult::Decision { + decision: PermissionDecision::Reject(_), + .. + } )); } @@ -7141,11 +7711,17 @@ mod permission_builder_tests { let hb = resolve_create(b).unwrap(); assert!(matches!( dispatch(&ha).await, - PermissionResult::Decision(PermissionDecision::Reject(_)) + PermissionResult::Decision { + decision: PermissionDecision::Reject(_), + .. + } )); assert!(matches!( dispatch(&hb).await, - PermissionResult::Decision(PermissionDecision::Reject(_)) + PermissionResult::Decision { + decision: PermissionDecision::Reject(_), + .. + } )); } @@ -7157,7 +7733,10 @@ mod permission_builder_tests { let h = resolve_resume(cfg).unwrap(); assert!(matches!( dispatch(&h).await, - PermissionResult::Decision(PermissionDecision::ApproveOnce(_)) + PermissionResult::Decision { + decision: PermissionDecision::ApproveOnce(_), + .. + } )); } @@ -7173,11 +7752,121 @@ mod permission_builder_tests { let hb = resolve_resume(b).unwrap(); assert!(matches!( dispatch(&ha).await, - PermissionResult::Decision(PermissionDecision::ApproveOnce(_)) + PermissionResult::Decision { + decision: PermissionDecision::ApproveOnce(_), + .. + } )); assert!(matches!( dispatch(&hb).await, - PermissionResult::Decision(PermissionDecision::ApproveOnce(_)) + PermissionResult::Decision { + decision: PermissionDecision::ApproveOnce(_), + .. + } )); } + + #[test] + fn session_config_enable_experimental_mode_serializes_when_set() { + let cfg = SessionConfig::default().with_enable_experimental_mode(false); + assert_eq!(cfg.enable_experimental_mode, Some(false)); + + let (wire, _runtime) = cfg + .into_wire(Some(SessionId::from("experimental-mode"))) + .expect("enable_experimental_mode config has no duplicate handlers"); + assert_eq!(wire.is_experimental_mode, Some(false)); + + let json = serde_json::to_value(&wire).unwrap(); + assert_eq!(json["isExperimentalMode"], serde_json::Value::Bool(false)); + } + + #[test] + fn session_config_enable_experimental_mode_omitted_when_none() { + let cfg = SessionConfig::default(); + assert_eq!(cfg.enable_experimental_mode, None); + + let (wire, _runtime) = cfg + .into_wire(Some(SessionId::from("no-experimental-mode"))) + .expect("default config has no duplicate handlers"); + assert_eq!(wire.is_experimental_mode, None); + + let json = serde_json::to_value(&wire).unwrap(); + assert!(json.get("isExperimentalMode").is_none()); + } + + #[test] + fn resume_session_config_enable_experimental_mode_serializes_when_set() { + let cfg = ResumeSessionConfig::new(SessionId::from("resume-experimental-mode")) + .with_enable_experimental_mode(false); + assert_eq!(cfg.enable_experimental_mode, Some(false)); + + let (wire, _runtime) = cfg + .into_wire() + .expect("resume enable_experimental_mode config has no duplicate handlers"); + assert_eq!(wire.is_experimental_mode, Some(false)); + + let json = serde_json::to_value(&wire).unwrap(); + assert_eq!(json["isExperimentalMode"], serde_json::Value::Bool(false)); + } + + #[test] + fn resume_session_config_enable_experimental_mode_omitted_when_none() { + let cfg = ResumeSessionConfig::new(SessionId::from("resume-no-experimental-mode")); + assert_eq!(cfg.enable_experimental_mode, None); + + let (wire, _runtime) = cfg + .into_wire() + .expect("default resume config has no duplicate handlers"); + assert_eq!(wire.is_experimental_mode, None); + + let json = serde_json::to_value(&wire).unwrap(); + assert!(json.get("isExperimentalMode").is_none()); + } +} + +#[cfg(test)] +mod is_terminal_tests { + use super::Tool; + + #[test] + fn is_terminal_serializes_as_camel_case_when_set() { + let tool = Tool { + name: "clear_context".to_owned(), + is_terminal: true, + ..Default::default() + }; + let value = serde_json::to_value(&tool).expect("tool serializes"); + assert_eq!( + value.get("isTerminal"), + Some(&serde_json::Value::Bool(true)) + ); + } + + #[test] + fn is_terminal_is_omitted_when_false() { + let tool = Tool { + name: "plain".to_owned(), + ..Default::default() + }; + let value = serde_json::to_value(&tool).expect("tool serializes"); + assert!(value.get("isTerminal").is_none()); + } + + /// `Tool` has a hand-written `Debug` impl, so a new field is only reported + /// if it is added there by hand. Guard against that drift. + #[test] + fn is_terminal_appears_in_debug_output() { + let terminal = Tool { + name: "clear_context".to_owned(), + is_terminal: true, + ..Default::default() + }; + assert!(format!("{terminal:?}").contains("is_terminal: true")); + + let plain = Tool { + name: "plain".to_owned(), + ..Default::default() + }; + assert!(format!("{plain:?}").contains("is_terminal: false")); + } } diff --git a/rust/src/wire.rs b/rust/src/wire.rs index 2eabbe848..21b61a7f9 100644 --- a/rust/src/wire.rs +++ b/rust/src/wire.rs @@ -25,9 +25,10 @@ use crate::generated::api_types::{ use crate::generated::session_events::ReasoningSummary; use crate::types::{ CanvasProviderIdentity, CapiSessionOptions, CloudSessionOptions, CustomAgentConfig, - DefaultAgentConfig, ExtensionInfo, InfiniteSessionConfig, LargeToolOutputConfig, - McpServerConfig, MemoryConfiguration, NamedProviderConfig, ProviderConfig, ProviderModelConfig, - SessionId, SessionLimitsConfig, SystemMessageConfig, Tool, ToolSearchConfig, + DefaultAgentConfig, ExtensionInfo, GitHubMcpToolConfig, InfiniteSessionConfig, + LargeToolOutputConfig, McpServerConfig, MemoryConfiguration, NamedProviderConfig, + ProviderConfig, ProviderModelConfig, SessionId, SessionLimitsConfig, SystemMessageConfig, Tool, + ToolSearchConfig, }; /// Wire representation of a slash command (name + description only). The @@ -113,6 +114,8 @@ pub(crate) struct SessionCreateWire { pub request_auto_mode_switch: bool, pub request_elicitation: bool, pub request_mcp_apps: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub github_mcp_tool_config: Option, pub hooks: bool, #[serde(skip_serializing_if = "Option::is_none")] pub skill_directories: Option>, @@ -127,8 +130,12 @@ pub(crate) struct SessionCreateWire { #[serde(skip_serializing_if = "Option::is_none")] pub disabled_skills: Option>, #[serde(skip_serializing_if = "Option::is_none")] + pub disabled_mcp_servers: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub custom_agents: Option>, #[serde(skip_serializing_if = "Option::is_none")] + pub custom_agents_local_only: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub default_agent: Option, #[serde(skip_serializing_if = "Option::is_none")] pub agent: Option, @@ -147,6 +154,8 @@ pub(crate) struct SessionCreateWire { #[serde(skip_serializing_if = "Option::is_none")] pub enable_citations: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub enable_file_change_tracking: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub session_limits: Option, #[serde(skip_serializing_if = "Option::is_none")] pub model_capabilities: Option, @@ -156,6 +165,8 @@ pub(crate) struct SessionCreateWire { pub config_dir: Option, #[serde(skip_serializing_if = "Option::is_none")] pub working_directory: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub additional_directories: Option>, #[serde(rename = "gitHubToken", skip_serializing_if = "Option::is_none")] pub github_token: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -175,6 +186,10 @@ pub(crate) struct SessionCreateWire { pub exp_assignments: Option, #[serde(skip_serializing_if = "Option::is_none")] pub enable_managed_settings: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub is_experimental_mode: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub managed_settings: Option, } /// The exact JSON shape sent on the `session.resume` JSON-RPC request. @@ -249,6 +264,8 @@ pub(crate) struct SessionResumeWire { pub request_auto_mode_switch: bool, pub request_elicitation: bool, pub request_mcp_apps: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub github_mcp_tool_config: Option, pub hooks: bool, #[serde(skip_serializing_if = "Option::is_none")] pub skill_directories: Option>, @@ -263,8 +280,12 @@ pub(crate) struct SessionResumeWire { #[serde(skip_serializing_if = "Option::is_none")] pub disabled_skills: Option>, #[serde(skip_serializing_if = "Option::is_none")] + pub disabled_mcp_servers: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub custom_agents: Option>, #[serde(skip_serializing_if = "Option::is_none")] + pub custom_agents_local_only: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub default_agent: Option, #[serde(skip_serializing_if = "Option::is_none")] pub agent: Option, @@ -283,6 +304,8 @@ pub(crate) struct SessionResumeWire { #[serde(skip_serializing_if = "Option::is_none")] pub enable_citations: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub enable_file_change_tracking: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub session_limits: Option, #[serde(skip_serializing_if = "Option::is_none")] pub model_capabilities: Option, @@ -292,6 +315,8 @@ pub(crate) struct SessionResumeWire { pub config_dir: Option, #[serde(skip_serializing_if = "Option::is_none")] pub working_directory: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub additional_directories: Option>, #[serde(rename = "gitHubToken", skip_serializing_if = "Option::is_none")] pub github_token: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -314,4 +339,8 @@ pub(crate) struct SessionResumeWire { pub exp_assignments: Option, #[serde(skip_serializing_if = "Option::is_none")] pub enable_managed_settings: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub is_experimental_mode: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub managed_settings: Option, } diff --git a/rust/tests/api_types_test.rs b/rust/tests/api_types_test.rs index bcf226691..9b86b1367 100644 --- a/rust/tests/api_types_test.rs +++ b/rust/tests/api_types_test.rs @@ -7,6 +7,7 @@ use github_copilot_sdk::rpc::{ Extension, ExtensionList, ExtensionSource, ExtensionStatus, ExtensionsDisableRequest, ExtensionsEnableRequest, FleetStartRequest, FleetStartResult, TasksStartAgentRequest, }; +use github_copilot_sdk::session_events::{PermissionRequest, PermissionRequestedData}; #[test] fn extension_running_has_expected_status_and_source() { @@ -84,6 +85,25 @@ fn tasks_start_agent_request_fields_are_accessible() { assert_eq!(request.description.as_deref(), Some("SDK task agent")); } +#[test] +fn permission_event_exposes_managed_approval_required() { + let data: PermissionRequestedData = serde_json::from_value(serde_json::json!({ + "permissionRequest": { + "kind": "read", + "intention": "Read managed content", + "path": "/workspace/file.txt", + "managedApprovalRequired": true + }, + "requestId": "permission-1" + })) + .unwrap(); + + let PermissionRequest::Read(request) = data.permission_request else { + panic!("expected read permission request"); + }; + assert_eq!(request.managed_approval_required, Some(true)); +} + fn running_extension(id: &str, name: &str) -> Extension { Extension { id: id.to_string(), diff --git a/rust/tests/builtin_plugin_directories_test.rs b/rust/tests/builtin_plugin_directories_test.rs new file mode 100644 index 000000000..f1310f9b0 --- /dev/null +++ b/rust/tests/builtin_plugin_directories_test.rs @@ -0,0 +1,135 @@ +#![allow(clippy::unwrap_used)] + +use std::path::PathBuf; + +use github_copilot_sdk::{CliProgram, Client, ClientOptions, ErrorKind, Transport}; +use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; +use tokio::net::TcpListener; + +async fn read_framed(reader: &mut (impl AsyncRead + Unpin)) -> serde_json::Value { + let mut header = String::new(); + loop { + let mut byte = [0u8; 1]; + reader.read_exact(&mut byte).await.unwrap(); + header.push(byte[0] as char); + if header.ends_with("\r\n\r\n") { + break; + } + } + let length = header + .trim() + .strip_prefix("Content-Length: ") + .unwrap() + .parse() + .unwrap(); + let mut body = vec![0; length]; + reader.read_exact(&mut body).await.unwrap(); + serde_json::from_slice(&body).unwrap() +} + +async fn write_result( + writer: &mut (impl AsyncWrite + Unpin), + request: &serde_json::Value, + result: serde_json::Value, +) { + let body = serde_json::to_vec(&serde_json::json!({ + "jsonrpc": "2.0", + "id": request["id"], + "result": result, + })) + .unwrap(); + writer + .write_all(format!("Content-Length: {}\r\n\r\n", body.len()).as_bytes()) + .await + .unwrap(); + writer.write_all(&body).await.unwrap(); + writer.flush().await.unwrap(); +} + +async fn run_start(paths: Option>) -> Vec { + let listener = TcpListener::bind(("127.0.0.1", 0)).await.unwrap(); + let address = listener.local_addr().unwrap(); + let expect_builtin = paths.as_ref().is_some_and(|paths| !paths.is_empty()); + + let server = tokio::spawn(async move { + let (stream, _) = listener.accept().await.unwrap(); + let (mut reader, mut writer) = tokio::io::split(stream); + let mut requests = Vec::new(); + + let connect = read_framed(&mut reader).await; + write_result( + &mut writer, + &connect, + serde_json::json!({ "ok": true, "protocolVersion": 3, "version": "test" }), + ) + .await; + requests.push(connect); + + if expect_builtin { + let builtin = read_framed(&mut reader).await; + write_result(&mut writer, &builtin, serde_json::json!({})).await; + requests.push(builtin); + } + requests + }); + + let mut options = ClientOptions::new() + .with_program(CliProgram::Path(std::env::current_exe().unwrap())) + .with_transport(Transport::External { + host: address.ip().to_string(), + port: address.port(), + connection_token: None, + }); + if let Some(paths) = paths { + options = options.with_builtin_plugin_directories(paths); + } + let client = Client::start(options).await.unwrap(); + let requests = server.await.unwrap(); + client.force_stop(); + requests +} + +#[tokio::test] +async fn default_and_empty_do_not_call_rpc() { + for paths in [None, Some(Vec::new())] { + let requests = run_start(paths).await; + assert_eq!(requests.len(), 1); + assert_eq!(requests[0]["method"], "connect"); + } +} + +#[tokio::test] +async fn configured_directories_call_rpc_once_before_start_completes() { + let cwd = std::env::current_dir().unwrap(); + let paths = vec![cwd.join("plugins/core"), cwd.join("plugins/github")]; + + let requests = run_start(Some(paths.clone())).await; + + assert_eq!(requests.len(), 2); + assert_eq!(requests[0]["method"], "connect"); + assert_eq!(requests[1]["method"], "plugins.builtin.set"); + assert_eq!( + requests[1]["params"], + serde_json::json!({ + "paths": paths + .iter() + .map(|path| path.to_str().unwrap()) + .collect::>() + }) + ); +} + +#[tokio::test] +async fn relative_directory_is_rejected() { + let options = ClientOptions::new() + .with_program(CliProgram::Path(std::env::current_exe().unwrap())) + .with_builtin_plugin_directories(["plugins/core"]); + + let error = match Client::start(options).await { + Ok(_) => panic!("relative path unexpectedly accepted"), + Err(error) => error, + }; + + assert_eq!(error.kind(), &ErrorKind::InvalidConfig); + assert!(error.to_string().contains("absolute paths")); +} diff --git a/rust/tests/e2e.rs b/rust/tests/e2e.rs index 3a698abd1..03723dfb1 100644 --- a/rust/tests/e2e.rs +++ b/rust/tests/e2e.rs @@ -66,6 +66,8 @@ mod permissions; mod pre_mcp_tool_call_hook; #[path = "e2e/provider_endpoint.rs"] mod provider_endpoint; +#[path = "e2e/rewind.rs"] +mod rewind; #[path = "e2e/rpc_additional_edge_cases.rs"] mod rpc_additional_edge_cases; #[path = "e2e/rpc_agent.rs"] diff --git a/rust/tests/e2e/abort.rs b/rust/tests/e2e/abort.rs index d4e79452b..34fc66b60 100644 --- a/rust/tests/e2e/abort.rs +++ b/rust/tests/e2e/abort.rs @@ -10,22 +10,24 @@ use tokio::sync::{Mutex, mpsc, oneshot}; use super::support::{ DEFAULT_TEST_TOKEN, assistant_message_content, recv_with_timeout, wait_for_event, - with_e2e_context, }; #[tokio::test] async fn should_abort_during_active_streaming() { - with_e2e_context("abort", "should_abort_during_active_streaming", |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let client = ctx.start_client().await; - let session = client - .create_session(ctx.approve_all_session_config().with_streaming(true)) - .await - .expect("create session"); - let events = session.subscribe(); + super::support::with_dedicated_e2e_context( + "abort", + "should_abort_during_active_streaming", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let client = ctx.start_client().await; + let session = client + .create_session(ctx.approve_all_session_config().with_streaming(true)) + .await + .expect("create session"); + let events = session.subscribe(); - session + session .send( "Write a very long essay about the history of computing, covering every decade \ from the 1940s to the 2020s in great detail.", @@ -33,54 +35,55 @@ async fn should_abort_during_active_streaming() { .await .expect("send long streaming turn"); - let delta = wait_for_event(events, "assistant.message_delta", |event| { - event.parsed_type() == SessionEventType::AssistantMessageDelta + let delta = wait_for_event(events, "assistant.message_delta", |event| { + event.parsed_type() == SessionEventType::AssistantMessageDelta + }) + .await; + assert!( + !delta + .typed_data::() + .expect("assistant.message_delta data") + .delta_content + .is_empty() + ); + + session.abort().await.expect("abort session"); + + // Session should be usable after abort. Wait for the specific recovery + // message rather than racing against a late idle from the aborted turn. + let recovery_events = session.subscribe(); + session + .send("Say 'abort_recovery_ok'.") + .await + .expect("send recovery"); + let recovery = wait_for_event( + recovery_events, + "assistant.message containing abort_recovery_ok", + |event| { + event.parsed_type() == SessionEventType::AssistantMessage + && assistant_message_content(event) + .to_lowercase() + .contains("abort_recovery_ok") + }, + ) + .await; + assert!( + assistant_message_content(&recovery) + .to_lowercase() + .contains("abort_recovery_ok") + ); + + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); }) - .await; - assert!( - !delta - .typed_data::() - .expect("assistant.message_delta data") - .delta_content - .is_empty() - ); - - session.abort().await.expect("abort session"); - - // Session should be usable after abort. Wait for the specific recovery - // message rather than racing against a late idle from the aborted turn. - let recovery_events = session.subscribe(); - session - .send("Say 'abort_recovery_ok'.") - .await - .expect("send recovery"); - let recovery = wait_for_event( - recovery_events, - "assistant.message containing abort_recovery_ok", - |event| { - event.parsed_type() == SessionEventType::AssistantMessage - && assistant_message_content(event) - .to_lowercase() - .contains("abort_recovery_ok") - }, - ) - .await; - assert!( - assistant_message_content(&recovery) - .to_lowercase() - .contains("abort_recovery_ok") - ); - - session.disconnect().await.expect("disconnect session"); - client.stop().await.expect("stop client"); - }) - }) + }, + ) .await; } #[tokio::test] async fn should_abort_during_active_tool_execution() { - with_e2e_context( + super::support::with_dedicated_e2e_context( "abort", "should_abort_during_active_tool_execution", |ctx| { diff --git a/rust/tests/e2e/ask_user.rs b/rust/tests/e2e/ask_user.rs index c134ad3c9..d7d089358 100644 --- a/rust/tests/e2e/ask_user.rs +++ b/rust/tests/e2e/ask_user.rs @@ -12,13 +12,11 @@ use github_copilot_sdk::{ use serde_json::json; use tokio::sync::{Notify, mpsc}; -use super::support::{ - DEFAULT_TEST_TOKEN, assistant_message_content, recv_with_timeout, with_e2e_context, -}; +use super::support::{DEFAULT_TEST_TOKEN, assistant_message_content, recv_with_timeout}; #[tokio::test] async fn should_invoke_user_input_handler_when_model_uses_ask_user_tool() { - with_e2e_context( + super::support::with_shared_e2e_context(&E2E, "ask_user", "should_invoke_user_input_handler_when_model_uses_ask_user_tool", |ctx| { @@ -62,7 +60,7 @@ async fn should_invoke_user_input_handler_when_model_uses_ask_user_tool() { #[tokio::test] async fn should_receive_choices_in_user_input_request() { - with_e2e_context( + super::support::with_shared_e2e_context(&E2E, "ask_user", "should_receive_choices_in_user_input_request", |ctx| { @@ -107,7 +105,7 @@ async fn should_receive_choices_in_user_input_request() { #[tokio::test] async fn should_handle_freeform_user_input_response() { - with_e2e_context( + super::support::with_shared_e2e_context(&E2E, "ask_user", "should_handle_freeform_user_input_response", |ctx| { @@ -164,7 +162,8 @@ async fn should_handle_freeform_user_input_response() { /// the handler observes the sibling tool while its own request is still pending. #[tokio::test] async fn ask_user_does_not_block_sibling_tool_call_in_same_turn() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "ask_user", "ask_user_does_not_block_sibling_tool_call_in_same_turn", |ctx| { @@ -346,3 +345,5 @@ impl ToolHandler for SetMarkerTool { Ok(ToolResult::Text(format!("MARKER_{}", value.to_uppercase()))) } } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("ask_user", 4); diff --git a/rust/tests/e2e/builtin_tools.rs b/rust/tests/e2e/builtin_tools.rs index 41584d3a0..12bcad4fa 100644 --- a/rust/tests/e2e/builtin_tools.rs +++ b/rust/tests/e2e/builtin_tools.rs @@ -2,7 +2,7 @@ use std::time::Duration; use github_copilot_sdk::MessageOptions; -use super::support::{assistant_message_content, with_e2e_context}; +use super::support::assistant_message_content; /// Built-in tool tests spawn a real CLI subprocess and execute actual shell / /// file tools. Under concurrent Windows CI load (e2e runs 4-wide on a 4-vCPU @@ -16,7 +16,8 @@ fn message(prompt: &str) -> MessageOptions { #[tokio::test] async fn should_capture_exit_code_in_output() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "builtin_tools", "should_capture_exit_code_in_output", |ctx| { @@ -49,7 +50,7 @@ async fn should_capture_exit_code_in_output() { #[tokio::test] async fn should_capture_stderr_output() { - with_e2e_context("builtin_tools", "should_capture_stderr_output", |ctx| { + super::support::with_shared_e2e_context(&E2E, "builtin_tools", "should_capture_stderr_output", |ctx| { Box::pin(async move { if cfg!(windows) { return; @@ -77,7 +78,7 @@ async fn should_capture_stderr_output() { #[tokio::test] async fn should_read_file_with_line_range() { - with_e2e_context("builtin_tools", "should_read_file_with_line_range", |ctx| { + super::support::with_shared_e2e_context(&E2E, "builtin_tools", "should_read_file_with_line_range", |ctx| { Box::pin(async move { ctx.set_default_copilot_user(); std::fs::write(ctx.work_dir().join("lines.txt"), "line1\nline2\nline3\nline4\nline5\n") @@ -106,7 +107,7 @@ async fn should_read_file_with_line_range() { #[tokio::test] async fn should_handle_nonexistent_file_gracefully() { - with_e2e_context( + super::support::with_shared_e2e_context(&E2E, "builtin_tools", "should_handle_nonexistent_file_gracefully", |ctx| { @@ -144,7 +145,7 @@ async fn should_handle_nonexistent_file_gracefully() { #[tokio::test] async fn should_edit_a_file_successfully() { - with_e2e_context("builtin_tools", "should_edit_a_file_successfully", |ctx| { + super::support::with_shared_e2e_context(&E2E, "builtin_tools", "should_edit_a_file_successfully", |ctx| { Box::pin(async move { ctx.set_default_copilot_user(); std::fs::write(ctx.work_dir().join("edit_me.txt"), "Hello World\nGoodbye World\n") @@ -171,7 +172,7 @@ async fn should_edit_a_file_successfully() { #[tokio::test] async fn should_create_a_new_file() { - with_e2e_context("builtin_tools", "should_create_a_new_file", |ctx| { + super::support::with_shared_e2e_context(&E2E, "builtin_tools", "should_create_a_new_file", |ctx| { Box::pin(async move { ctx.set_default_copilot_user(); let client = ctx.start_client().await; @@ -196,7 +197,7 @@ async fn should_create_a_new_file() { #[tokio::test] async fn should_search_for_patterns_in_files() { - with_e2e_context( + super::support::with_shared_e2e_context(&E2E, "builtin_tools", "should_search_for_patterns_in_files", |ctx| { @@ -229,7 +230,7 @@ async fn should_search_for_patterns_in_files() { #[tokio::test] async fn should_find_files_by_pattern() { - with_e2e_context("builtin_tools", "should_find_files_by_pattern", |ctx| { + super::support::with_shared_e2e_context(&E2E, "builtin_tools", "should_find_files_by_pattern", |ctx| { Box::pin(async move { ctx.set_default_copilot_user(); let src = ctx.work_dir().join("src"); @@ -256,3 +257,5 @@ async fn should_find_files_by_pattern() { }) .await; } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("builtin_tools", 8); diff --git a/rust/tests/e2e/canvas.rs b/rust/tests/e2e/canvas.rs index 1736e9711..2418e9e5a 100644 --- a/rust/tests/e2e/canvas.rs +++ b/rust/tests/e2e/canvas.rs @@ -10,8 +10,6 @@ use github_copilot_sdk::types::ExtensionInfo; use parking_lot::Mutex; use serde_json::{Value, json}; -use super::support::with_e2e_context; - struct TestCanvasHandler { open_calls: Mutex>, close_calls: Mutex>, @@ -74,33 +72,38 @@ fn canvas_session_config( #[tokio::test] async fn canvas_list_discovers_declared_canvases() { - with_e2e_context("canvas", "canvas_list_discovers_declared_canvases", |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let client = ctx.start_client().await; - let handler = Arc::new(TestCanvasHandler::new()); - let session = client - .create_session(canvas_session_config(ctx, handler)) - .await - .expect("create session"); - - let result = session.rpc().canvas().list().await.expect("list canvases"); - - assert_eq!(result.canvases.len(), 1); - assert_eq!(result.canvases[0].canvas_id, "counter"); - assert_eq!(result.canvases[0].display_name, "Counter"); - assert_eq!(result.canvases[0].description, "Tracks a counter value."); - - session.disconnect().await.expect("disconnect session"); - client.stop().await.expect("stop client"); - }) - }) + super::support::with_shared_e2e_context( + &E2E, + "canvas", + "canvas_list_discovers_declared_canvases", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let client = ctx.start_client().await; + let handler = Arc::new(TestCanvasHandler::new()); + let session = client + .create_session(canvas_session_config(ctx, handler)) + .await + .expect("create session"); + + let result = session.rpc().canvas().list().await.expect("list canvases"); + + assert_eq!(result.canvases.len(), 1); + assert_eq!(result.canvases[0].canvas_id, "counter"); + assert_eq!(result.canvases[0].display_name, "Counter"); + assert_eq!(result.canvases[0].description, "Tracks a counter value."); + + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) .await; } #[tokio::test] async fn canvas_open_round_trip() { - with_e2e_context("canvas", "canvas_open_round_trip", |ctx| { + super::support::with_shared_e2e_context(&E2E, "canvas", "canvas_open_round_trip", |ctx| { Box::pin(async move { ctx.set_default_copilot_user(); let client = ctx.start_client().await; @@ -158,64 +161,69 @@ async fn canvas_open_round_trip() { #[tokio::test] async fn canvas_invoke_action_round_trip() { - with_e2e_context("canvas", "canvas_invoke_action_round_trip", |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let client = ctx.start_client().await; - let handler = Arc::new(TestCanvasHandler::new()); - let session = client - .create_session(canvas_session_config(ctx, handler.clone())) - .await - .expect("create session"); - - let canvas_list = session.rpc().canvas().list().await.expect("list canvases"); - let canvas = &canvas_list.canvases[0]; - - session - .rpc() - .canvas() - .open(github_copilot_sdk::rpc::CanvasOpenRequest { - canvas_id: "counter".to_string(), - instance_id: "counter-2".to_string(), - extension_id: Some(canvas.extension_id.clone()), - input: Some(json!({})), - }) - .await - .expect("open canvas"); - - let result = session - .rpc() - .canvas() - .action() - .invoke(github_copilot_sdk::rpc::CanvasActionInvokeRequest { - instance_id: "counter-2".to_string(), - action_name: "increment".to_string(), - input: Some(json!({ "delta": 1 })), - }) - .await - .expect("invoke action"); - - assert_eq!(result.result, Some(json!({ "newValue": 42 }))); - - { - let actions = handler.action_calls.lock(); - assert_eq!(actions.len(), 1); - assert_eq!(actions[0].canvas_id, "counter"); - assert_eq!(actions[0].instance_id, "counter-2"); - assert_eq!(actions[0].action_name, "increment"); - assert_eq!(actions[0].input, Some(json!({ "delta": 1 }))); - } - - session.disconnect().await.expect("disconnect session"); - client.stop().await.expect("stop client"); - }) - }) + super::support::with_shared_e2e_context( + &E2E, + "canvas", + "canvas_invoke_action_round_trip", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let client = ctx.start_client().await; + let handler = Arc::new(TestCanvasHandler::new()); + let session = client + .create_session(canvas_session_config(ctx, handler.clone())) + .await + .expect("create session"); + + let canvas_list = session.rpc().canvas().list().await.expect("list canvases"); + let canvas = &canvas_list.canvases[0]; + + session + .rpc() + .canvas() + .open(github_copilot_sdk::rpc::CanvasOpenRequest { + canvas_id: "counter".to_string(), + instance_id: "counter-2".to_string(), + extension_id: Some(canvas.extension_id.clone()), + input: Some(json!({})), + }) + .await + .expect("open canvas"); + + let result = session + .rpc() + .canvas() + .action() + .invoke(github_copilot_sdk::rpc::CanvasActionInvokeRequest { + instance_id: "counter-2".to_string(), + action_name: "increment".to_string(), + input: Some(json!({ "delta": 1 })), + }) + .await + .expect("invoke action"); + + assert_eq!(result.result, Some(json!({ "newValue": 42 }))); + + { + let actions = handler.action_calls.lock(); + assert_eq!(actions.len(), 1); + assert_eq!(actions[0].canvas_id, "counter"); + assert_eq!(actions[0].instance_id, "counter-2"); + assert_eq!(actions[0].action_name, "increment"); + assert_eq!(actions[0].input, Some(json!({ "delta": 1 }))); + } + + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) .await; } #[tokio::test] async fn canvas_close_round_trip() { - with_e2e_context("canvas", "canvas_close_round_trip", |ctx| { + super::support::with_shared_e2e_context(&E2E, "canvas", "canvas_close_round_trip", |ctx| { Box::pin(async move { ctx.set_default_copilot_user(); let client = ctx.start_client().await; @@ -272,3 +280,4 @@ async fn canvas_close_round_trip() { }) .await; } +static E2E: super::support::SharedE2eGroup = super::support::SharedE2eGroup::standard("canvas", 4); diff --git a/rust/tests/e2e/client.rs b/rust/tests/e2e/client.rs index 6dd0f27ac..0ac4c9d45 100644 --- a/rust/tests/e2e/client.rs +++ b/rust/tests/e2e/client.rs @@ -6,13 +6,25 @@ use github_copilot_sdk::{ CliProgram, Client, ClientOptions, Error, ListModelsHandler, Model, Transport, }; -use super::support::with_e2e_context; +use super::support::{is_inprocess_default, with_e2e_context}; #[tokio::test] async fn should_start_ping_and_stop_stdio_client() { with_e2e_context("client", "should_start_ping_and_stop_stdio_client", |ctx| { Box::pin(async move { let client = ctx.start_client().await; + let timings = client.startup_timings().expect("startup timings"); + if is_inprocess_default() { + assert!(timings.program_resolve_ms.is_some()); + assert!(timings.process_spawn_ms.is_none()); + } else { + assert!(timings.program_resolve_ms.is_none()); + assert!(timings.process_spawn_ms.is_some()); + } + assert!(timings.port_wait_ms.is_none()); + assert!(timings.total_ms >= timings.transport_setup_ms); + assert!(timings.total_ms >= timings.handshake_ms); + let response = client.ping(Some("hello from rust")).await.expect("ping"); assert_eq!(response.message, "pong: hello from rust"); assert!(!response.timestamp.is_empty()); @@ -33,6 +45,13 @@ async fn should_start_ping_and_stop_tcp_client() { })) .await .expect("start TCP client"); + let timings = client.startup_timings().expect("startup timings"); + assert_eq!(timings.program_resolve_ms.is_some(), is_inprocess_default()); + assert!(timings.process_spawn_ms.is_some()); + assert!(timings.port_wait_ms.is_some()); + assert!(timings.total_ms >= timings.transport_setup_ms); + assert!(timings.total_ms >= timings.handshake_ms); + let response = client.ping(Some("tcp hello")).await.expect("ping"); assert_eq!(response.message, "pong: tcp hello"); diff --git a/rust/tests/e2e/client_api.rs b/rust/tests/e2e/client_api.rs index 951fe8720..35cdf6f28 100644 --- a/rust/tests/e2e/client_api.rs +++ b/rust/tests/e2e/client_api.rs @@ -1,41 +1,47 @@ use github_copilot_sdk::SessionId; -use super::support::{wait_for_condition, with_e2e_context}; +use super::support::wait_for_condition; #[tokio::test] async fn should_delete_session_by_id() { - with_e2e_context("client_api", "should_delete_session_by_id", |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let client = ctx.start_client().await; - let session = client - .create_session(ctx.approve_all_session_config()) - .await - .expect("create session"); - let session_id = session.id().clone(); - - session.send_and_wait("Say OK.").await.expect("send"); - session.disconnect().await.expect("disconnect session"); - client - .delete_session(&session_id) - .await - .expect("delete session"); - - let metadata = client - .get_session_metadata(&session_id) - .await - .expect("get metadata"); - assert!(metadata.is_none()); - - client.stop().await.expect("stop client"); - }) - }) + super::support::with_shared_e2e_context( + &E2E, + "client_api", + "should_delete_session_by_id", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let client = ctx.start_client().await; + let session = client + .create_session(ctx.approve_all_session_config()) + .await + .expect("create session"); + let session_id = session.id().clone(); + + session.send_and_wait("Say OK.").await.expect("send"); + session.disconnect().await.expect("disconnect session"); + client + .delete_session(&session_id) + .await + .expect("delete session"); + + let metadata = client + .get_session_metadata(&session_id) + .await + .expect("get metadata"); + assert!(metadata.is_none()); + + client.stop().await.expect("stop client"); + }) + }, + ) .await; } #[tokio::test] async fn should_report_error_when_deleting_unknown_session_id() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "client_api", "should_report_error_when_deleting_unknown_session_id", |ctx| { @@ -62,7 +68,7 @@ async fn should_report_error_when_deleting_unknown_session_id() { #[tokio::test] async fn should_get_null_last_session_id_before_any_sessions_exist() { - with_e2e_context( + super::support::with_dedicated_e2e_context( "client_api", "should_get_null_last_session_id_before_any_sessions_exist", |ctx| { @@ -81,7 +87,8 @@ async fn should_get_null_last_session_id_before_any_sessions_exist() { #[tokio::test] async fn should_track_last_session_id_after_session_created() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "client_api", "should_track_last_session_id_after_session_created", |ctx| { @@ -122,7 +129,8 @@ async fn should_track_last_session_id_after_session_created() { #[tokio::test] async fn should_get_null_foreground_session_id_in_headless_mode() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "client_api", "should_get_null_foreground_session_id_in_headless_mode", |ctx| { @@ -144,7 +152,8 @@ async fn should_get_null_foreground_session_id_in_headless_mode() { #[tokio::test] async fn should_report_error_when_setting_foreground_session_in_headless_mode() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "client_api", "should_report_error_when_setting_foreground_session_in_headless_mode", |ctx| { @@ -175,3 +184,5 @@ async fn should_report_error_when_setting_foreground_session_in_headless_mode() ) .await; } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("client_api", 5); diff --git a/rust/tests/e2e/commands.rs b/rust/tests/e2e/commands.rs index d6cb6699f..d110d3b35 100644 --- a/rust/tests/e2e/commands.rs +++ b/rust/tests/e2e/commands.rs @@ -11,11 +11,12 @@ use github_copilot_sdk::{CommandContext, CommandDefinition, CommandHandler, Requ use serde_json::json; use tokio::sync::mpsc; -use super::support::{recv_with_timeout, wait_for_event, with_e2e_context}; +use super::support::{recv_with_timeout, wait_for_event}; #[tokio::test] async fn session_commands_list_returns_builtins_and_respects_client_command_filter() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "commands", "session_with_commands_creates_successfully", |ctx| { @@ -85,7 +86,8 @@ async fn session_commands_list_returns_builtins_and_respects_client_command_filt #[tokio::test] async fn session_commands_invoke_known_builtin_returns_expected_result() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "commands", "session_with_no_commands_creates_successfully", |ctx| { @@ -129,7 +131,8 @@ async fn session_commands_invoke_known_builtin_returns_expected_result() { #[tokio::test] async fn session_commands_execute_runs_registered_command_handler() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "commands", "session_with_commands_creates_successfully", |ctx| { @@ -175,7 +178,8 @@ async fn session_commands_execute_runs_registered_command_handler() { #[tokio::test] async fn session_commands_enqueue_and_respond_to_queued_command() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "commands", "session_with_no_commands_creates_successfully", |ctx| { @@ -289,3 +293,5 @@ fn assert_command( assert_eq!(command.kind, kind); assert!(!command.description.trim().is_empty()); } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("commands", 4); diff --git a/rust/tests/e2e/compaction.rs b/rust/tests/e2e/compaction.rs index b9854ef1d..d56687d5f 100644 --- a/rust/tests/e2e/compaction.rs +++ b/rust/tests/e2e/compaction.rs @@ -1,10 +1,9 @@ use github_copilot_sdk::rpc::{LogRequest, SessionLogLevel}; -use super::support::with_e2e_context; - #[tokio::test] async fn should_return_empty_handoff_summary_for_fresh_session() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "compaction", "should_return_empty_handoff_summary_for_fresh_session", |ctx| { @@ -34,7 +33,8 @@ async fn should_return_empty_handoff_summary_for_fresh_session() { #[tokio::test] async fn should_report_noop_when_cancelling_compaction_without_inflight_work() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "compaction", "should_report_noop_when_cancelling_compaction_without_inflight_work", |ctx| { @@ -71,7 +71,8 @@ async fn should_report_noop_when_cancelling_compaction_without_inflight_work() { #[tokio::test] async fn should_summarize_for_handoff_after_non_ephemeral_log_event() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "compaction", "should_summarize_for_handoff_after_non_ephemeral_log_event", |ctx| { @@ -111,3 +112,5 @@ async fn should_summarize_for_handoff_after_non_ephemeral_log_event() { ) .await; } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("compaction", 3); diff --git a/rust/tests/e2e/elicitation.rs b/rust/tests/e2e/elicitation.rs index 5575e67f3..31da30adb 100644 --- a/rust/tests/e2e/elicitation.rs +++ b/rust/tests/e2e/elicitation.rs @@ -10,11 +10,12 @@ use github_copilot_sdk::{ use serde_json::json; use tokio::sync::Mutex; -use super::support::{DEFAULT_TEST_TOKEN, assert_uuid_like, with_e2e_context}; +use super::support::{DEFAULT_TEST_TOKEN, assert_uuid_like}; #[tokio::test] async fn defaults_capabilities_when_not_provided() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "elicitation", "defaults_capabilities_when_not_provided", |ctx| { @@ -39,7 +40,8 @@ async fn defaults_capabilities_when_not_provided() { #[tokio::test] async fn elicitation_throws_when_capability_is_missing() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "elicitation", "elicitation_throws_when_capability_is_missing", |ctx| { @@ -83,7 +85,8 @@ async fn elicitation_throws_when_capability_is_missing() { #[tokio::test] async fn sends_requestelicitation_when_handler_provided() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "elicitation", "sends_requestelicitation_when_handler_provided", |ctx| { @@ -115,7 +118,8 @@ async fn sends_requestelicitation_when_handler_provided() { #[tokio::test] async fn should_report_elicitation_capability_based_on_handler_presence() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "elicitation", "should_report_elicitation_capability_based_on_handler_presence", |ctx| { @@ -161,7 +165,8 @@ async fn should_report_elicitation_capability_based_on_handler_presence() { #[tokio::test] async fn session_without_elicitationhandler_creates_successfully() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "elicitation", "session_without_elicitationhandler_creates_successfully", |ctx| { @@ -185,7 +190,8 @@ async fn session_without_elicitationhandler_creates_successfully() { #[tokio::test] async fn confirm_returns_true_when_handler_accepts() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "elicitation", "confirm_returns_true_when_handler_accepts", |ctx| { @@ -215,7 +221,8 @@ async fn confirm_returns_true_when_handler_accepts() { #[tokio::test] async fn confirm_returns_false_when_handler_declines() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "elicitation", "confirm_returns_false_when_handler_declines", |ctx| { @@ -243,83 +250,94 @@ async fn confirm_returns_false_when_handler_declines() { #[tokio::test] async fn select_returns_selected_option() { - with_e2e_context("elicitation", "select_returns_selected_option", |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let client = ctx.start_client().await; - let session = client - .create_session( - SessionConfig::default() - .with_github_token(DEFAULT_TEST_TOKEN) - .pipe_handler(QueuedElicitationHandler::new([accept( - json!({ "selection": "beta" }), - )])), - ) - .await - .expect("create session"); - - assert_eq!( - session - .ui() - .select("Choose", &["alpha", "beta"]) + super::support::with_shared_e2e_context( + &E2E, + "elicitation", + "select_returns_selected_option", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let client = ctx.start_client().await; + let session = client + .create_session( + SessionConfig::default() + .with_github_token(DEFAULT_TEST_TOKEN) + .pipe_handler(QueuedElicitationHandler::new([accept( + json!({ "selection": "beta" }), + )])), + ) .await - .expect("select") - .as_deref(), - Some("beta") - ); - - session.disconnect().await.expect("disconnect session"); - client.stop().await.expect("stop client"); - }) - }) + .expect("create session"); + + assert_eq!( + session + .ui() + .select("Choose", &["alpha", "beta"]) + .await + .expect("select") + .as_deref(), + Some("beta") + ); + + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) .await; } #[tokio::test] async fn input_returns_freeform_value() { - with_e2e_context("elicitation", "input_returns_freeform_value", |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let client = ctx.start_client().await; - let session = client - .create_session( - SessionConfig::default() - .with_github_token(DEFAULT_TEST_TOKEN) - .pipe_handler(QueuedElicitationHandler::new([accept( - json!({ "value": "typed value" }), - )])), - ) - .await - .expect("create session"); - let options = UiInputOptions { - title: Some("Value"), - description: Some("A value to test"), - min_length: Some(1), - max_length: Some(20), - default: Some("default"), - ..UiInputOptions::default() - }; - - assert_eq!( - session - .ui() - .input("Enter value", Some(&options)) + super::support::with_shared_e2e_context( + &E2E, + "elicitation", + "input_returns_freeform_value", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let client = ctx.start_client().await; + let session = client + .create_session( + SessionConfig::default() + .with_github_token(DEFAULT_TEST_TOKEN) + .pipe_handler(QueuedElicitationHandler::new([accept( + json!({ "value": "typed value" }), + )])), + ) .await - .expect("input") - .as_deref(), - Some("typed value") - ); - - session.disconnect().await.expect("disconnect session"); - client.stop().await.expect("stop client"); - }) - }) + .expect("create session"); + let options = UiInputOptions { + title: Some("Value"), + description: Some("A value to test"), + min_length: Some(1), + max_length: Some(20), + default: Some("default"), + ..UiInputOptions::default() + }; + + assert_eq!( + session + .ui() + .input("Enter value", Some(&options)) + .await + .expect("input") + .as_deref(), + Some("typed value") + ); + + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) .await; } #[tokio::test] async fn elicitation_returns_all_action_shapes() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "elicitation", "elicitation_returns_all_action_shapes", |ctx| { @@ -606,3 +624,5 @@ fn cancel() -> ElicitationResult { content: None, } } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("elicitation", 10); diff --git a/rust/tests/e2e/event_fidelity.rs b/rust/tests/e2e/event_fidelity.rs index 770ed5da1..7176a7e66 100644 --- a/rust/tests/e2e/event_fidelity.rs +++ b/rust/tests/e2e/event_fidelity.rs @@ -3,11 +3,12 @@ use github_copilot_sdk::session_events::{ ToolExecutionCompleteData, ToolExecutionStartData, UserMessageData, }; -use super::support::{collect_until_idle, event_types, with_e2e_context}; +use super::support::{collect_until_idle, event_types}; #[tokio::test] async fn should_include_valid_fields_on_all_events() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "event_fidelity", "should_include_valid_fields_on_all_events", |ctx| { @@ -54,7 +55,8 @@ async fn should_include_valid_fields_on_all_events() { #[tokio::test] async fn should_emit_tool_execution_events_with_correct_fields() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "event_fidelity", "should_emit_tool_execution_events_with_correct_fields", |ctx| { @@ -99,7 +101,8 @@ async fn should_emit_tool_execution_events_with_correct_fields() { #[tokio::test] async fn should_emit_assistant_usage_event_after_model_call() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "event_fidelity", "should_emit_assistant_usage_event_after_model_call", |ctx| { @@ -136,7 +139,8 @@ async fn should_emit_assistant_usage_event_after_model_call() { #[tokio::test] async fn should_emit_session_usage_info_event_after_model_call() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "event_fidelity", "should_emit_session_usage_info_event_after_model_call", |ctx| { @@ -175,7 +179,8 @@ async fn should_emit_session_usage_info_event_after_model_call() { #[tokio::test] async fn should_emit_pending_messages_modified_event_when_message_queue_changes() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "event_fidelity", "should_emit_pending_messages_modified_event_when_message_queue_changes", |ctx| { @@ -218,7 +223,8 @@ async fn should_emit_pending_messages_modified_event_when_message_queue_changes( #[tokio::test] async fn should_emit_events_in_correct_order_for_tool_using_conversation() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "event_fidelity", "should_emit_events_in_correct_order_for_tool_using_conversation", |ctx| { @@ -265,7 +271,8 @@ async fn should_emit_events_in_correct_order_for_tool_using_conversation() { #[tokio::test] async fn should_emit_assistant_message_with_messageid() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "event_fidelity", "should_emit_assistant_message_with_messageid", |ctx| { @@ -299,7 +306,8 @@ async fn should_emit_assistant_message_with_messageid() { #[tokio::test] async fn should_preserve_message_order_in_getmessages_after_tool_use() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "event_fidelity", "should_preserve_message_order_in_getmessages_after_tool_use", |ctx| { @@ -366,3 +374,5 @@ async fn should_preserve_message_order_in_getmessages_after_tool_use() { ) .await; } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("event_fidelity", 8); diff --git a/rust/tests/e2e/hooks.rs b/rust/tests/e2e/hooks.rs index b4a211d87..051019073 100644 --- a/rust/tests/e2e/hooks.rs +++ b/rust/tests/e2e/hooks.rs @@ -7,11 +7,12 @@ use github_copilot_sdk::hooks::{ }; use tokio::sync::mpsc; -use super::support::{recv_with_timeout, with_e2e_context}; +use super::support::recv_with_timeout; #[tokio::test] async fn should_invoke_pretooluse_hook_when_model_runs_a_tool() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "hooks", "should_invoke_pretooluse_hook_when_model_runs_a_tool", |ctx| { @@ -51,7 +52,8 @@ async fn should_invoke_pretooluse_hook_when_model_runs_a_tool() { #[tokio::test] async fn should_invoke_posttooluse_hook_after_model_runs_a_tool() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "hooks", "should_invoke_posttooluse_hook_after_model_runs_a_tool", |ctx| { @@ -92,7 +94,7 @@ async fn should_invoke_posttooluse_hook_after_model_runs_a_tool() { #[tokio::test] async fn should_invoke_both_pretooluse_and_posttooluse_hooks_for_single_tool_call() { - with_e2e_context( + super::support::with_shared_e2e_context(&E2E, "hooks", "should_invoke_both_pretooluse_and_posttooluse_hooks_for_single_tool_call", |ctx| { @@ -147,7 +149,8 @@ async fn should_invoke_both_pretooluse_and_posttooluse_hooks_for_single_tool_cal #[tokio::test] async fn should_deny_tool_execution_when_pretooluse_returns_deny() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "hooks", "should_deny_tool_execution_when_pretooluse_returns_deny", |ctx| { @@ -226,3 +229,4 @@ impl SessionHooks for RecordingHooks { None } } +static E2E: super::support::SharedE2eGroup = super::support::SharedE2eGroup::standard("hooks", 4); diff --git a/rust/tests/e2e/hooks_extended.rs b/rust/tests/e2e/hooks_extended.rs index ab93a0c3c..dfd77ed7c 100644 --- a/rust/tests/e2e/hooks_extended.rs +++ b/rust/tests/e2e/hooks_extended.rs @@ -1,23 +1,26 @@ use std::sync::Arc; +use std::sync::atomic::{AtomicUsize, Ordering}; use async_trait::async_trait; use github_copilot_sdk::handler::ApproveAllHandler; use github_copilot_sdk::hooks::{ - ErrorOccurredInput, ErrorOccurredOutput, HookContext, PostToolUseFailureInput, - PostToolUseFailureOutput, PostToolUseInput, PostToolUseOutput, PreToolUseInput, - PreToolUseOutput, SessionEndInput, SessionEndOutput, SessionHooks, SessionStartInput, - SessionStartOutput, UserPromptSubmittedInput, UserPromptSubmittedOutput, + AgentStopInput, AgentStopOutput, ErrorOccurredInput, ErrorOccurredOutput, HookContext, + PostToolUseFailureInput, PostToolUseFailureOutput, PostToolUseInput, PostToolUseOutput, + PreToolUseInput, PreToolUseOutput, SessionEndInput, SessionEndOutput, SessionHooks, + SessionStartInput, SessionStartOutput, UserPromptSubmittedInput, UserPromptSubmittedOutput, + UserPromptTransformedInput, UserPromptTransformedOutput, }; use github_copilot_sdk::tool::ToolHandler; use github_copilot_sdk::{Error, SessionConfig, Tool, ToolInvocation, ToolResult}; use serde_json::json; use tokio::sync::mpsc; -use super::support::{assistant_message_content, recv_with_timeout, with_e2e_context}; +use super::support::{assistant_message_content, recv_with_timeout}; #[tokio::test] async fn should_invoke_onsessionstart_hook_on_new_session() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "hooks_extended", "should_invoke_onsessionstart_hook_on_new_session", |ctx| { @@ -49,7 +52,8 @@ async fn should_invoke_onsessionstart_hook_on_new_session() { #[tokio::test] async fn should_invoke_onuserpromptsubmitted_hook_when_sending_a_message() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "hooks_extended", "should_invoke_onuserpromptsubmitted_hook_when_sending_a_message", |ctx| { @@ -81,7 +85,8 @@ async fn should_invoke_onuserpromptsubmitted_hook_when_sending_a_message() { #[tokio::test] async fn should_invoke_onsessionend_hook_when_session_is_disconnected() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "hooks_extended", "should_invoke_onsessionend_hook_when_session_is_disconnected", |ctx| { @@ -112,7 +117,8 @@ async fn should_invoke_onsessionend_hook_when_session_is_disconnected() { #[tokio::test] async fn should_invoke_onerroroccurred_hook_when_error_occurs() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "hooks_extended", "should_invoke_onerroroccurred_hook_when_error_occurs", |ctx| { @@ -143,7 +149,8 @@ async fn should_invoke_onerroroccurred_hook_when_error_occurs() { #[tokio::test] async fn should_invoke_userpromptsubmitted_hook_and_modify_prompt() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "hooks_extended", "should_invoke_userpromptsubmitted_hook_and_modify_prompt", |ctx| { @@ -183,71 +190,126 @@ async fn should_invoke_userpromptsubmitted_hook_and_modify_prompt() { .await; } +#[tokio::test] +async fn should_invoke_userprompttransformed_hook_and_modify_transformed_prompt() { + super::support::with_shared_e2e_context( + &E2E, + "hooks_extended", + "should_invoke_userprompttransformed_hook_and_modify_transformed_prompt", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let (tx, mut rx) = mpsc::unbounded_channel(); + let client = ctx.start_client().await; + let session = client + .create_session( + ctx.approve_all_session_config() + .with_hooks(Arc::new(UserPromptTransformedHooks { tx })), + ) + .await + .expect("create session"); + + let answer = session + .send_and_wait("Answer the request above.") + .await + .expect("send") + .expect("assistant message"); + let input = recv_with_timeout(&mut rx, "userPromptTransformed hook").await; + assert!(input.prompt.contains("Answer the request above.")); + assert!( + input + .transformed_prompt + .contains("Answer the request above.") + ); + assert!(input.transformed_prompt.contains("")); + assert!(input.timestamp > 0.0); + assert!(!input.working_directory.as_os_str().is_empty()); + assert!(assistant_message_content(&answer).contains("HOOKED_TRANSFORMED_PROMPT")); + + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) + .await; +} + #[tokio::test] async fn should_invoke_sessionstart_hook() { - with_e2e_context("hooks_extended", "should_invoke_sessionstart_hook", |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let (tx, mut rx) = mpsc::unbounded_channel(); - let client = ctx.start_client().await; - let session = client - .create_session(ctx.approve_all_session_config().with_hooks(Arc::new( - RecordingHooks::session_start( - tx, - Some(SessionStartOutput { - additional_context: Some("Session start hook context.".to_string()), - ..SessionStartOutput::default() - }), - ), - ))) - .await - .expect("create session"); - - session.send_and_wait("Say hi").await.expect("send"); - let input = recv_with_timeout(&mut rx, "sessionStart hook").await; - assert_eq!(input.source, "new"); - - session.disconnect().await.expect("disconnect session"); - client.stop().await.expect("stop client"); - }) - }) + super::support::with_shared_e2e_context( + &E2E, + "hooks_extended", + "should_invoke_sessionstart_hook", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let (tx, mut rx) = mpsc::unbounded_channel(); + let client = ctx.start_client().await; + let session = client + .create_session(ctx.approve_all_session_config().with_hooks(Arc::new( + RecordingHooks::session_start( + tx, + Some(SessionStartOutput { + additional_context: Some("Session start hook context.".to_string()), + ..SessionStartOutput::default() + }), + ), + ))) + .await + .expect("create session"); + + session.send_and_wait("Say hi").await.expect("send"); + let input = recv_with_timeout(&mut rx, "sessionStart hook").await; + assert_eq!(input.source, "new"); + + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) .await; } #[tokio::test] async fn should_invoke_sessionend_hook() { - with_e2e_context("hooks_extended", "should_invoke_sessionend_hook", |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let (tx, mut rx) = mpsc::unbounded_channel(); - let client = ctx.start_client().await; - let session = client - .create_session(ctx.approve_all_session_config().with_hooks(Arc::new( - RecordingHooks::session_end( - tx, - Some(SessionEndOutput { - session_summary: Some("session ended".to_string()), - ..SessionEndOutput::default() - }), - ), - ))) - .await - .expect("create session"); - - session.send_and_wait("Say bye").await.expect("send"); - session.disconnect().await.expect("disconnect session"); - let input = recv_with_timeout(&mut rx, "sessionEnd hook").await; - assert!(input.timestamp > 0.0); - - client.stop().await.expect("stop client"); - }) - }) + super::support::with_shared_e2e_context( + &E2E, + "hooks_extended", + "should_invoke_sessionend_hook", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let (tx, mut rx) = mpsc::unbounded_channel(); + let client = ctx.start_client().await; + let session = client + .create_session(ctx.approve_all_session_config().with_hooks(Arc::new( + RecordingHooks::session_end( + tx, + Some(SessionEndOutput { + session_summary: Some("session ended".to_string()), + ..SessionEndOutput::default() + }), + ), + ))) + .await + .expect("create session"); + + session.send_and_wait("Say bye").await.expect("send"); + session.disconnect().await.expect("disconnect session"); + let input = recv_with_timeout(&mut rx, "sessionEnd hook").await; + assert!(input.timestamp > 0.0); + + client.stop().await.expect("stop client"); + }) + }, + ) .await; } #[tokio::test] async fn should_register_erroroccurred_hook() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "hooks_extended", "should_register_erroroccurred_hook", |ctx| { @@ -281,9 +343,53 @@ async fn should_register_erroroccurred_hook() { .await; } +#[tokio::test] +async fn should_invoke_agentstop_hook_and_apply_block_response() { + super::support::with_shared_e2e_context( + &E2E, + "hooks_extended", + "should_invoke_agentstop_hook_and_apply_block_response", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let (tx, mut rx) = mpsc::unbounded_channel(); + let client = ctx.start_client().await; + let session = client + .create_session(ctx.approve_all_session_config().with_hooks(Arc::new( + AgentStopHooks { + tx, + call_count: AtomicUsize::new(0), + }, + ))) + .await + .expect("create session"); + + let answer = session + .send_and_wait("Reply with exactly: AGENT_STOP_INITIAL") + .await + .expect("send") + .expect("assistant message"); + let first = recv_with_timeout(&mut rx, "first agentStop hook").await; + let second = recv_with_timeout(&mut rx, "second agentStop hook").await; + + assert_ne!(first.stop_hook_active, Some(true)); + assert_eq!(second.stop_hook_active, Some(true)); + assert_eq!(first.stop_reason.as_deref(), Some("end_turn")); + assert!(first.transcript_path.is_some()); + assert!(assistant_message_content(&answer).contains("AGENT_STOP_CONTINUED")); + + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) + .await; +} + #[tokio::test] async fn should_allow_pretooluse_to_return_modifiedargs_and_suppressoutput() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "hooks_extended", "should_allow_pretooluse_to_return_modifiedargs_and_suppressoutput", |ctx| { @@ -326,7 +432,8 @@ async fn should_allow_pretooluse_to_return_modifiedargs_and_suppressoutput() { #[tokio::test] async fn should_allow_posttooluse_to_return_modifiedresult() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "hooks_extended", "should_allow_posttooluse_to_return_modifiedresult", |ctx| { @@ -372,7 +479,7 @@ async fn should_allow_posttooluse_to_return_modifiedresult() { #[tokio::test] #[ignore = "Fails with 1.0.64-0 runtime: built-in tools are not available when hooks restrict availableTools, so the failure path cannot be exercised. Follow up with runtime team."] async fn should_invoke_posttoolusefailure_hook_for_failed_tool_result() { - with_e2e_context( + super::support::with_shared_e2e_context(&E2E, "hooks_extended", "should_invoke_posttoolusefailure_hook_for_failed_tool_result", |ctx| { @@ -441,6 +548,48 @@ struct RecordingHooks { post_tool_failure: Option>, } +struct AgentStopHooks { + tx: mpsc::UnboundedSender, + call_count: AtomicUsize, +} + +struct UserPromptTransformedHooks { + tx: mpsc::UnboundedSender, +} + +#[async_trait] +impl SessionHooks for UserPromptTransformedHooks { + async fn on_user_prompt_transformed( + &self, + input: UserPromptTransformedInput, + ctx: HookContext, + ) -> Option { + assert!(!ctx.session_id.as_str().is_empty()); + let _ = self.tx.send(input); + Some(UserPromptTransformedOutput { + modified_transformed_prompt: Some( + "Reply with exactly: HOOKED_TRANSFORMED_PROMPT".to_string(), + ), + }) + } +} + +#[async_trait] +impl SessionHooks for AgentStopHooks { + async fn on_agent_stop( + &self, + input: AgentStopInput, + ctx: HookContext, + ) -> Option { + assert!(!ctx.session_id.as_str().is_empty()); + let _ = self.tx.send(input); + (self.call_count.fetch_add(1, Ordering::SeqCst) == 0).then(|| AgentStopOutput { + decision: Some("block".to_string()), + reason: Some("Reply with exactly: AGENT_STOP_CONTINUED".to_string()), + }) + } +} + impl RecordingHooks { fn session_start( tx: mpsc::UnboundedSender, @@ -655,3 +804,5 @@ impl ToolHandler for EchoValueTool { )) } } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("hooks_extended", 12); diff --git a/rust/tests/e2e/inprocess.rs b/rust/tests/e2e/inprocess.rs index 0c183a27d..ead05a0b5 100644 --- a/rust/tests/e2e/inprocess.rs +++ b/rust/tests/e2e/inprocess.rs @@ -7,6 +7,12 @@ async fn should_start_ping_and_stop_inprocess_client() { with_e2e_context("client", "should_start_ping_and_stop_stdio_client", |ctx| { Box::pin(async move { let client = ctx.start_inprocess_client().await; + let timings = client.startup_timings().expect("startup timings"); + assert!(timings.program_resolve_ms.is_some()); + assert!(timings.process_spawn_ms.is_none()); + assert!(timings.port_wait_ms.is_none()); + assert!(timings.total_ms >= timings.transport_setup_ms); + assert!(timings.total_ms >= timings.handshake_ms); let response = client .ping(Some("hello from rust in-process")) diff --git a/rust/tests/e2e/mode_empty.rs b/rust/tests/e2e/mode_empty.rs index af1e9267e..2a62d66cf 100644 --- a/rust/tests/e2e/mode_empty.rs +++ b/rust/tests/e2e/mode_empty.rs @@ -12,10 +12,22 @@ use std::sync::Arc; use github_copilot_sdk::handler::ApproveAllHandler; use github_copilot_sdk::types::SystemMessageConfig; -use github_copilot_sdk::{BUILTIN_TOOLS_ISOLATED, Client, ClientMode, SessionConfig, ToolSet}; +use github_copilot_sdk::{BUILTIN_TOOLS_ISOLATED, ClientMode, SessionConfig, ToolSet}; use serde_json::Value; -use super::support::{assistant_message_content, with_e2e_context}; +use super::support::assistant_message_content; + +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::new("mode_empty", empty_shared_client_options, 6); + +fn empty_shared_client_options( + context: &super::support::E2eContext, +) -> github_copilot_sdk::ClientOptions { + context + .client_options() + .with_mode(ClientMode::Empty) + .with_base_directory(context.work_dir().to_path_buf()) +} const SHELL_TOOL_NAME: &str = if cfg!(windows) { "powershell" } else { "bash" }; @@ -85,17 +97,14 @@ fn system_message_from_request(exchange: &Value) -> String { #[tokio::test] async fn empty_mode_isolated_set_shell_tool_is_not_exposed() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "mode_empty", "empty_mode_isolated_set_shell_tool_is_not_exposed", |ctx| { Box::pin(async move { ctx.set_default_copilot_user(); - let options = ctx - .client_options() - .with_mode(ClientMode::Empty) - .with_base_directory(ctx.work_dir().to_path_buf()); - let client = Client::start(options).await.expect("start client"); + let client = ctx.start_client().await; let session = client .create_session( SessionConfig::default() @@ -135,17 +144,14 @@ async fn empty_mode_isolated_set_shell_tool_is_not_exposed() { #[tokio::test] async fn empty_mode_builtin_star_exposes_all_built_in_tools() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "mode_empty", "empty_mode_builtin_star_exposes_all_built_in_tools", |ctx| { Box::pin(async move { ctx.set_default_copilot_user(); - let options = ctx - .client_options() - .with_mode(ClientMode::Empty) - .with_base_directory(ctx.work_dir().to_path_buf()); - let client = Client::start(options).await.expect("start client"); + let client = ctx.start_client().await; let session = client .create_session( SessionConfig::default() @@ -175,17 +181,14 @@ async fn empty_mode_builtin_star_exposes_all_built_in_tools() { #[tokio::test] async fn empty_mode_excluded_tools_subtracts_from_available_tools() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "mode_empty", "empty_mode_excluded_tools_subtracts_from_available_tools", |ctx| { Box::pin(async move { ctx.set_default_copilot_user(); - let options = ctx - .client_options() - .with_mode(ClientMode::Empty) - .with_base_directory(ctx.work_dir().to_path_buf()); - let client = Client::start(options).await.expect("start client"); + let client = ctx.start_client().await; let session = client .create_session( SessionConfig::default() @@ -217,17 +220,14 @@ async fn empty_mode_excluded_tools_subtracts_from_available_tools() { #[tokio::test] async fn empty_mode_strips_environment_context_from_the_system_message_by_default() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "mode_empty", "empty_mode_strips_environment_context_from_the_system_message_by_default", |ctx| { Box::pin(async move { ctx.set_default_copilot_user(); - let options = ctx - .client_options() - .with_mode(ClientMode::Empty) - .with_base_directory(ctx.work_dir().to_path_buf()); - let client = Client::start(options).await.expect("start client"); + let client = ctx.start_client().await; let session = client .create_session( SessionConfig::default() @@ -274,17 +274,14 @@ async fn empty_mode_strips_environment_context_from_the_system_message_by_defaul #[tokio::test] async fn empty_mode_system_message_replace_llm_follows_caller_content_verbatim() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "mode_empty", "empty_mode_system_message_replace_llm_follows_caller_content_verbatim", |ctx| { Box::pin(async move { ctx.set_default_copilot_user(); - let options = ctx - .client_options() - .with_mode(ClientMode::Empty) - .with_base_directory(ctx.work_dir().to_path_buf()); - let client = Client::start(options).await.expect("start client"); + let client = ctx.start_client().await; let session = client .create_session( SessionConfig::default() @@ -320,17 +317,14 @@ async fn empty_mode_system_message_replace_llm_follows_caller_content_verbatim() #[tokio::test] async fn empty_mode_append_caller_instruction_takes_effect_and_env_context_stripped() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "mode_empty", "empty_mode_append_caller_instruction_takes_effect_and_env_context_stripped", |ctx| { Box::pin(async move { ctx.set_default_copilot_user(); - let options = ctx - .client_options() - .with_mode(ClientMode::Empty) - .with_base_directory(ctx.work_dir().to_path_buf()); - let client = Client::start(options).await.expect("start client"); + let client = ctx.start_client().await; let session = client .create_session( SessionConfig::default() diff --git a/rust/tests/e2e/mode_handlers.rs b/rust/tests/e2e/mode_handlers.rs index b4089ca28..7ab6fe5bf 100644 --- a/rust/tests/e2e/mode_handlers.rs +++ b/rust/tests/e2e/mode_handlers.rs @@ -15,9 +15,7 @@ use github_copilot_sdk::session_events::{ use github_copilot_sdk::{ExitPlanModeData, SessionConfig, SessionId}; use tokio::sync::mpsc; -use super::support::{ - recv_with_timeout, wait_for_event, wait_for_event_allowing_rate_limit, with_e2e_context, -}; +use super::support::{recv_with_timeout, wait_for_event, wait_for_event_allowing_rate_limit}; const MODE_HANDLER_TOKEN: &str = "mode-handler-token"; const PLAN_SUMMARY: &str = "Greeting file implementation plan"; @@ -64,7 +62,8 @@ impl AutoModeSwitchHandler for AutoModeHandler { #[tokio::test] async fn should_invoke_exit_plan_mode_handler_when_model_uses_tool() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "mode_handlers", "should_invoke_exit_plan_mode_handler_when_model_uses_tool", |ctx| { @@ -181,7 +180,8 @@ async fn should_invoke_exit_plan_mode_handler_when_model_uses_tool() { #[tokio::test] async fn should_invoke_auto_mode_switch_handler_when_rate_limited() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "mode_handlers", "should_invoke_auto_mode_switch_handler_when_rate_limited", |ctx| { @@ -288,3 +288,5 @@ async fn should_invoke_auto_mode_switch_handler_when_rate_limited() { ) .await; } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("mode_handlers", 2); diff --git a/rust/tests/e2e/multi_provider_registry.rs b/rust/tests/e2e/multi_provider_registry.rs index 8c37deaa2..d07acd356 100644 --- a/rust/tests/e2e/multi_provider_registry.rs +++ b/rust/tests/e2e/multi_provider_registry.rs @@ -5,8 +5,6 @@ use github_copilot_sdk::{ }; use serde_json::Value; -use super::support::with_e2e_context; - const CATEGORY: &str = "multi_provider_registry"; fn headers(provider: &str) -> HashMap { @@ -17,7 +15,8 @@ fn headers(provider: &str) -> HashMap { #[tokio::test] async fn should_register_multiple_providers_with_custom_agents_bound_to_their_models() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, CATEGORY, "should_register_multiple_providers_with_custom_agents_bound_to_their_models", |ctx| { @@ -124,7 +123,7 @@ async fn assert_routing( expected_wire_model: &'static str, expected_provider_header: &'static str, ) { - with_e2e_context(CATEGORY, snapshot_name, move |ctx| { + super::support::with_shared_e2e_context(&E2E, CATEGORY, snapshot_name, move |ctx| { Box::pin(async move { ctx.set_default_copilot_user(); let client = ctx.start_client().await; @@ -241,3 +240,4 @@ async fn should_route_delta_turbo_turn_to_its_provider_and_wire_model() { ) .await; } +static E2E: super::support::SharedE2eGroup = super::support::SharedE2eGroup::standard(CATEGORY, 4); diff --git a/rust/tests/e2e/multi_turn.rs b/rust/tests/e2e/multi_turn.rs index 8c3bc5cb9..e57fe2294 100644 --- a/rust/tests/e2e/multi_turn.rs +++ b/rust/tests/e2e/multi_turn.rs @@ -1,13 +1,12 @@ use github_copilot_sdk::SessionEvent; use github_copilot_sdk::session_events::SessionEventType; -use super::support::{ - assistant_message_content, collect_until_idle, event_types, with_e2e_context, -}; +use super::support::{assistant_message_content, collect_until_idle, event_types}; #[tokio::test] async fn should_use_tool_results_from_previous_turns() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "multi_turn", "should_use_tool_results_from_previous_turns", |ctx| { @@ -52,7 +51,8 @@ async fn should_use_tool_results_from_previous_turns() { #[tokio::test] async fn should_handle_file_creation_then_reading_across_turns() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "multi_turn", "should_handle_file_creation_then_reading_across_turns", |ctx| { @@ -154,3 +154,5 @@ fn index_of( .skip(start_index) .find_map(|(index, event)| (event.parsed_type() == event_type).then_some(index)) } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("multi_turn", 2); diff --git a/rust/tests/e2e/permissions.rs b/rust/tests/e2e/permissions.rs index e97aeacb0..65b37928d 100644 --- a/rust/tests/e2e/permissions.rs +++ b/rust/tests/e2e/permissions.rs @@ -11,12 +11,13 @@ use tokio::sync::{mpsc, oneshot}; use super::support::{ DEFAULT_TEST_TOKEN, assistant_message_content, recv_with_timeout, wait_for_condition, - wait_for_event, with_e2e_context, + wait_for_event, }; #[tokio::test] async fn should_work_with_approve_all_permission_handler() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "permissions", "should_work_with_approve_all_permission_handler", |ctx| { @@ -49,9 +50,10 @@ async fn should_handle_permission_handler_errors_gracefully() { assert!(matches!( result, - PermissionResult::Decision( - github_copilot_sdk::types::PermissionDecision::UserNotAvailable(_) - ) + PermissionResult::Decision { + decision: github_copilot_sdk::types::PermissionDecision::UserNotAvailable(_), + .. + } )); } @@ -68,7 +70,8 @@ async fn should_handle_concurrent_permission_requests_from_parallel_tools() { #[tokio::test] async fn should_deny_permission_when_handler_returns_denied() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "permissions", "should_deny_permission_when_handler_returns_denied", |ctx| { @@ -120,7 +123,8 @@ async fn should_deny_permission_when_handler_returns_denied() { #[tokio::test] async fn should_deny_tool_operations_when_handler_explicitly_denies() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "permissions", "should_deny_tool_operations_when_handler_explicitly_denies", |ctx| { @@ -159,7 +163,8 @@ async fn should_deny_tool_operations_when_handler_explicitly_denies() { #[tokio::test] async fn should_handle_async_permission_handler() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "permissions", "should_handle_async_permission_handler", |ctx| { @@ -195,7 +200,7 @@ async fn should_handle_async_permission_handler() { #[tokio::test] async fn should_resume_session_with_permission_handler() { - with_e2e_context( + super::support::with_dedicated_e2e_context( "permissions", "should_resume_session_with_permission_handler", |ctx| { @@ -250,7 +255,7 @@ async fn should_resume_session_with_permission_handler() { #[tokio::test] async fn should_deny_tool_operations_when_handler_explicitly_denies_after_resume() { - with_e2e_context( + super::support::with_dedicated_e2e_context( "permissions", "should_deny_tool_operations_when_handler_explicitly_denies_after_resume", |ctx| { @@ -310,7 +315,8 @@ async fn should_deny_tool_operations_when_handler_explicitly_denies_after_resume #[tokio::test] async fn should_receive_toolcallid_in_permission_requests() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "permissions", "should_receive_toolcallid_in_permission_requests", |ctx| { @@ -350,7 +356,8 @@ async fn should_receive_toolcallid_in_permission_requests() { #[tokio::test] async fn should_deny_permission_with_noresult_kind() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "permissions", "should_deny_permission_with_noresult_kind", |ctx| { @@ -385,7 +392,8 @@ async fn should_deny_permission_with_noresult_kind() { #[tokio::test] async fn should_short_circuit_permission_handler_when_set_approve_all_enabled() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "permissions", "should_short_circuit_permission_handler_when_set_approve_all_enabled", |ctx| { @@ -454,7 +462,8 @@ async fn should_short_circuit_permission_handler_when_set_approve_all_enabled() #[tokio::test] async fn should_wait_for_slow_permission_handler() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "permissions", "should_wait_for_slow_permission_handler", |ctx| { @@ -520,7 +529,8 @@ async fn should_wait_for_slow_permission_handler() { #[tokio::test] async fn should_invoke_permission_handler_for_write_operations() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "permissions", "should_invoke_permission_handler_for_write_operations", |ctx| { @@ -720,3 +730,5 @@ impl PermissionHandler for SlowPermissionHandler { PermissionResult::approve_once() } } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("permissions", 9); diff --git a/rust/tests/e2e/pre_mcp_tool_call_hook.rs b/rust/tests/e2e/pre_mcp_tool_call_hook.rs index fd05796fc..31e69d106 100644 --- a/rust/tests/e2e/pre_mcp_tool_call_hook.rs +++ b/rust/tests/e2e/pre_mcp_tool_call_hook.rs @@ -8,7 +8,7 @@ use github_copilot_sdk::{IndexMap, McpServerConfig, McpStdioServerConfig}; use serde_json::{Value, json}; use tokio::sync::mpsc; -use super::support::{assistant_message_content, recv_with_timeout, with_e2e_context}; +use super::support::{assistant_message_content, recv_with_timeout}; fn meta_echo_mcp_servers(repo_root: &std::path::Path) -> IndexMap { let harness_dir = repo_root.join("test").join("harness"); @@ -88,7 +88,7 @@ impl SessionHooks for RemoveMetaHooks { #[tokio::test] async fn should_set_meta_via_premcptoolcall_hook() { - with_e2e_context( + super::support::with_shared_e2e_context(&E2E, "pre_mcp_tool_call_hook", "should_set_meta_via_premcptoolcall_hook", |ctx| { @@ -138,7 +138,7 @@ async fn should_set_meta_via_premcptoolcall_hook() { #[tokio::test] async fn should_replace_meta_via_premcptoolcall_hook() { - with_e2e_context( + super::support::with_shared_e2e_context(&E2E, "pre_mcp_tool_call_hook", "should_replace_meta_via_premcptoolcall_hook", |ctx| { @@ -186,7 +186,7 @@ async fn should_replace_meta_via_premcptoolcall_hook() { #[tokio::test] async fn should_remove_meta_via_premcptoolcall_hook() { - with_e2e_context( + super::support::with_shared_e2e_context(&E2E, "pre_mcp_tool_call_hook", "should_remove_meta_via_premcptoolcall_hook", |ctx| { @@ -231,3 +231,5 @@ async fn should_remove_meta_via_premcptoolcall_hook() { ) .await; } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("pre_mcp_tool_call_hook", 3); diff --git a/rust/tests/e2e/rewind.rs b/rust/tests/e2e/rewind.rs new file mode 100644 index 000000000..990c45091 --- /dev/null +++ b/rust/tests/e2e/rewind.rs @@ -0,0 +1,131 @@ +use std::path::Path; +use std::time::Duration; + +use github_copilot_sdk::rpc::{ + HistoryListRewindPointsResult, HistoryPreviewRewindRequest, HistoryRewindMode, + HistoryRewindOutcome, HistoryRewindRequest, +}; + +use super::support::assistant_message_content; + +const FILE_NAME: &str = "rewind-sdk.txt"; +const FILE_CONTENT: &str = "SDK rewind content"; + +#[tokio::test] +async fn should_restore_tracked_file_and_conversation() { + super::support::with_shared_e2e_context( + &E2E, + "rewind", + "should_restore_tracked_file_and_conversation", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let file_path = ctx.work_dir().join(FILE_NAME); + let client = ctx.start_client().await; + let session = client + .create_session( + ctx.approve_all_session_config() + .with_model("claude-sonnet-4.5") + .with_enable_file_change_tracking(true), + ) + .await + .expect("create session"); + + let response = session + .send_and_wait(format!( + "Use the create tool to create {FILE_NAME} containing exactly \ + {FILE_CONTENT}. After the tool succeeds, reply with exactly \ + SDK_REWIND_DONE." + )) + .await + .expect("send rewind setup prompt") + .expect("assistant message"); + assert_eq!(assistant_message_content(&response), "SDK_REWIND_DONE"); + assert_eq!( + std::fs::read_to_string(&file_path).expect("read tracked file"), + FILE_CONTENT + ); + + let rewind_points = wait_for_rewind_points(&session).await; + assert!(rewind_points.file_change_tracking_enabled); + assert_eq!(rewind_points.points.len(), 1); + let rewind_point = &rewind_points.points[0]; + assert!(rewind_point.can_restore_files); + assert_eq!(rewind_point.file_count, 1); + + let preview = session + .rpc() + .history() + .preview_rewind(HistoryPreviewRewindRequest { + event_id: rewind_point.event_id.clone(), + }) + .await + .expect("preview rewind"); + assert!(preview.available); + assert_eq!(preview.files.len(), 1); + assert_same_path(&file_path, Path::new(&preview.files[0].path)); + + let rewind = session + .rpc() + .history() + .rewind(HistoryRewindRequest { + event_id: rewind_point.event_id.clone(), + mode: HistoryRewindMode::ConversationAndFiles, + }) + .await + .expect("rewind conversation and files"); + assert_eq!(rewind.outcome, HistoryRewindOutcome::Success); + assert!(rewind.events_removed.is_some_and(|count| count > 0)); + assert_eq!(rewind.restored_files.len(), 1); + assert_same_path(&file_path, Path::new(&rewind.restored_files[0])); + assert!(!file_path.exists()); + + let events = session.get_events().await.expect("get events after rewind"); + assert!(events.iter().all(|event| event.id != rewind_point.event_id)); + + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) + .await; +} + +async fn wait_for_rewind_points( + session: &github_copilot_sdk::session::Session, +) -> HistoryListRewindPointsResult { + let deadline = tokio::time::Instant::now() + Duration::from_secs(10); + loop { + let result = session + .rpc() + .history() + .list_rewind_points() + .await + .expect("list rewind points"); + if result.unavailable_reason.is_none() { + return result; + } + assert!( + tokio::time::Instant::now() < deadline, + "timed out waiting for rewind points" + ); + tokio::time::sleep(Duration::from_millis(100)).await; + } +} + +fn assert_same_path(expected: &Path, actual: &Path) { + let expected = expected.to_string_lossy(); + let actual = actual.to_string_lossy(); + if cfg!(windows) { + let expected = expected.replace('\\', "/"); + let actual = actual.replace('\\', "/"); + assert!( + expected.eq_ignore_ascii_case(&actual), + "expected path {expected:?}, got {actual:?}" + ); + } else { + assert_eq!(expected, actual); + } +} + +static E2E: super::support::SharedE2eGroup = super::support::SharedE2eGroup::standard("rewind", 1); diff --git a/rust/tests/e2e/rpc_additional_edge_cases.rs b/rust/tests/e2e/rpc_additional_edge_cases.rs index cce35a985..d7537f314 100644 --- a/rust/tests/e2e/rpc_additional_edge_cases.rs +++ b/rust/tests/e2e/rpc_additional_edge_cases.rs @@ -1,14 +1,16 @@ use github_copilot_sdk::rpc::{ - ModeSetRequest, NameSetRequest, PermissionsSetApproveAllRequest, PlanUpdateRequest, - ShellExecRequest, WorkspacesCreateFileRequest, WorkspacesReadFileRequest, + ModeSetRequest, NameSetRequest, PermissionsResetSessionApprovalsRequest, + PermissionsSetApproveAllRequest, PlanUpdateRequest, ShellExecRequest, + WorkspacesCreateFileRequest, WorkspacesReadFileRequest, }; use github_copilot_sdk::session_events::SessionMode; -use super::support::{wait_for_condition, with_e2e_context}; +use super::support::wait_for_condition; #[tokio::test] async fn shell_exec_with_zero_timeout_does_not_kill_long_running_command() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_additional_edge_cases", "shell_exec_with_zero_timeout_does_not_kill_long_running_command", |ctx| { @@ -48,7 +50,8 @@ async fn shell_exec_with_zero_timeout_does_not_kill_long_running_command() { #[tokio::test] async fn workspaces_create_file_with_empty_content_round_trips() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_additional_edge_cases", "workspaces_create_file_with_empty_content_round_trips", |ctx| { @@ -97,7 +100,8 @@ async fn workspaces_create_file_with_empty_content_round_trips() { #[tokio::test] async fn workspaces_create_file_with_unicode_content_round_trips() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_additional_edge_cases", "workspaces_create_file_with_unicode_content_round_trips", |ctx| { @@ -140,7 +144,8 @@ async fn workspaces_create_file_with_unicode_content_round_trips() { #[tokio::test] async fn workspaces_create_file_with_large_content_round_trips() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_additional_edge_cases", "workspaces_create_file_with_large_content_round_trips", |ctx| { @@ -186,7 +191,8 @@ async fn workspaces_create_file_with_large_content_round_trips() { #[tokio::test] async fn plan_update_with_empty_content_then_read_returns_empty() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_additional_edge_cases", "plan_update_with_empty_content_then_read_returns_empty", |ctx| { @@ -219,7 +225,8 @@ async fn plan_update_with_empty_content_then_read_returns_empty() { #[tokio::test] async fn plan_delete_when_none_exists_is_idempotent() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_additional_edge_cases", "plan_delete_when_none_exists_is_idempotent", |ctx| { @@ -251,7 +258,8 @@ async fn plan_delete_when_none_exists_is_idempotent() { #[tokio::test] async fn mode_set_to_same_value_multiple_times_stays_stable() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_additional_edge_cases", "mode_set_to_same_value_multiple_times_stays_stable", |ctx| { @@ -288,7 +296,8 @@ async fn mode_set_to_same_value_multiple_times_stays_stable() { #[tokio::test] async fn name_set_with_unicode_round_trips() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_additional_edge_cases", "name_set_with_unicode_round_trips", |ctx| { @@ -322,7 +331,8 @@ async fn name_set_with_unicode_round_trips() { #[tokio::test] async fn usage_get_metrics_on_fresh_session_returns_zero_tokens() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_additional_edge_cases", "usage_get_metrics_on_fresh_session_returns_zero_tokens", |ctx| { @@ -350,7 +360,8 @@ async fn usage_get_metrics_on_fresh_session_returns_zero_tokens() { #[tokio::test] async fn permissions_reset_session_approvals_on_fresh_session_is_noop() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_additional_edge_cases", "permissions_reset_session_approvals_on_fresh_session_is_noop", |ctx| { @@ -365,7 +376,7 @@ async fn permissions_reset_session_approvals_on_fresh_session_is_noop() { let result = session .rpc() .permissions() - .reset_session_approvals() + .reset_session_approvals(PermissionsResetSessionApprovalsRequest::default()) .await .expect("reset approvals"); assert!(result.success); @@ -380,7 +391,8 @@ async fn permissions_reset_session_approvals_on_fresh_session_is_noop() { #[tokio::test] async fn permissions_set_approve_all_toggle_round_trips() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_additional_edge_cases", "permissions_set_approve_all_toggle_round_trips", |ctx| { @@ -439,7 +451,8 @@ async fn permissions_set_approve_all_toggle_round_trips() { #[tokio::test] async fn workspaces_createfile_then_listfiles_returns_all_files() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_additional_edge_cases", "workspaces_createfile_then_listfiles_returns_all_files", |ctx| { @@ -491,7 +504,8 @@ async fn workspaces_createfile_then_listfiles_returns_all_files() { #[tokio::test] async fn workspaces_getworkspace_returns_stable_result_across_calls() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_additional_edge_cases", "workspaces_getworkspace_returns_stable_result_across_calls", |ctx| { @@ -544,3 +558,5 @@ fn delayed_marker_command(marker_path: &std::path::Path) -> String { marker_path.display() ) } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("rpc_additional_edge_cases", 13); diff --git a/rust/tests/e2e/rpc_agent.rs b/rust/tests/e2e/rpc_agent.rs index e254460bc..24fbd3067 100644 --- a/rust/tests/e2e/rpc_agent.rs +++ b/rust/tests/e2e/rpc_agent.rs @@ -3,41 +3,47 @@ use github_copilot_sdk::rpc::{AgentInfo, AgentSelectRequest}; use github_copilot_sdk::session_events::SessionEventType; use serde_json::json; -use super::support::{wait_for_event, with_e2e_context}; +use super::support::wait_for_event; #[tokio::test] async fn should_list_available_custom_agents() { - with_e2e_context("rpc_agents", "should_list_available_custom_agents", |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let client = ctx.start_client().await; - let session = client - .create_session( - ctx.approve_all_session_config() - .with_custom_agents(create_custom_agents()), - ) - .await - .expect("create session"); + super::support::with_shared_e2e_context( + &E2E, + "rpc_agents", + "should_list_available_custom_agents", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let client = ctx.start_client().await; + let session = client + .create_session( + ctx.approve_all_session_config() + .with_custom_agents(create_custom_agents()), + ) + .await + .expect("create session"); - let result = session.rpc().agent().list().await.expect("agent list"); - assert_agent(&result.agents, "test-agent", "Test Agent", "A test agent"); - assert_agent( - &result.agents, - "another-agent", - "Another Agent", - "Another test agent", - ); + let result = session.rpc().agent().list().await.expect("agent list"); + assert_agent(&result.agents, "test-agent", "Test Agent", "A test agent"); + assert_agent( + &result.agents, + "another-agent", + "Another Agent", + "Another test agent", + ); - session.disconnect().await.expect("disconnect session"); - client.stop().await.expect("stop client"); - }) - }) + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) .await; } #[tokio::test] async fn should_return_null_when_no_agent_is_selected() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_agents", "should_return_null_when_no_agent_is_selected", |ctx| { @@ -71,47 +77,53 @@ async fn should_return_null_when_no_agent_is_selected() { #[tokio::test] async fn should_select_and_get_current_agent() { - with_e2e_context("rpc_agents", "should_select_and_get_current_agent", |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let client = ctx.start_client().await; - let session = client - .create_session( - ctx.approve_all_session_config() - .with_custom_agents([create_custom_agents().remove(0)]), - ) - .await - .expect("create session"); + super::support::with_shared_e2e_context( + &E2E, + "rpc_agents", + "should_select_and_get_current_agent", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let client = ctx.start_client().await; + let session = client + .create_session( + ctx.approve_all_session_config() + .with_custom_agents([create_custom_agents().remove(0)]), + ) + .await + .expect("create session"); - let selected = session - .rpc() - .agent() - .select(AgentSelectRequest { - name: "test-agent".to_string(), - }) - .await - .expect("select agent"); - assert_eq!(selected.agent.name, "test-agent"); - assert_eq!(selected.agent.display_name, "Test Agent"); + let selected = session + .rpc() + .agent() + .select(AgentSelectRequest { + name: "test-agent".to_string(), + }) + .await + .expect("select agent"); + assert_eq!(selected.agent.name, "test-agent"); + assert_eq!(selected.agent.display_name, "Test Agent"); - let current = session - .rpc() - .agent() - .get_current() - .await - .expect("get selected agent"); - assert_eq!(current.agent.name, "test-agent"); + let current = session + .rpc() + .agent() + .get_current() + .await + .expect("get selected agent"); + assert_eq!(current.agent.name, "test-agent"); - session.disconnect().await.expect("disconnect session"); - client.stop().await.expect("stop client"); - }) - }) + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) .await; } #[tokio::test] async fn should_emit_subagent_selected_and_deselected_events() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_agents", "should_emit_subagent_selected_and_deselected_events", |ctx| { @@ -185,51 +197,57 @@ async fn should_emit_subagent_selected_and_deselected_events() { #[tokio::test] async fn should_deselect_current_agent() { - with_e2e_context("rpc_agents", "should_deselect_current_agent", |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let client = ctx.start_client().await; - let session = client - .create_session( - ctx.approve_all_session_config() - .with_custom_agents([create_custom_agents().remove(0)]), - ) - .await - .expect("create session"); + super::support::with_shared_e2e_context( + &E2E, + "rpc_agents", + "should_deselect_current_agent", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let client = ctx.start_client().await; + let session = client + .create_session( + ctx.approve_all_session_config() + .with_custom_agents([create_custom_agents().remove(0)]), + ) + .await + .expect("create session"); - session - .rpc() - .agent() - .select(AgentSelectRequest { - name: "test-agent".to_string(), - }) - .await - .expect("select agent"); - session - .rpc() - .agent() - .deselect() - .await - .expect("deselect agent"); - let value = client - .call( - "session.agent.getCurrent", - Some(json!({ "sessionId": session.id() })), - ) - .await - .expect("get current agent"); - assert!(value.get("agent").is_some_and(serde_json::Value::is_null)); + session + .rpc() + .agent() + .select(AgentSelectRequest { + name: "test-agent".to_string(), + }) + .await + .expect("select agent"); + session + .rpc() + .agent() + .deselect() + .await + .expect("deselect agent"); + let value = client + .call( + "session.agent.getCurrent", + Some(json!({ "sessionId": session.id() })), + ) + .await + .expect("get current agent"); + assert!(value.get("agent").is_some_and(serde_json::Value::is_null)); - session.disconnect().await.expect("disconnect session"); - client.stop().await.expect("stop client"); - }) - }) + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) .await; } #[tokio::test] async fn should_return_empty_list_when_no_custom_agents_configured() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_agents", "should_return_empty_list_when_no_custom_agents_configured", |ctx| { @@ -254,46 +272,53 @@ async fn should_return_empty_list_when_no_custom_agents_configured() { #[tokio::test] async fn should_call_agent_reload() { - with_e2e_context("rpc_agents", "should_call_agent_reload", |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let reload_agent = - CustomAgentConfig::new("reload-test-agent-rust", "You are a reload test agent.") - .with_display_name("Reload Test Agent") - .with_description("Used by the agent reload RPC test."); - let client = ctx.start_client().await; - let session = client - .create_session( - ctx.approve_all_session_config() - .with_custom_agents([reload_agent.clone()]), + super::support::with_shared_e2e_context( + &E2E, + "rpc_agents", + "should_call_agent_reload", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let reload_agent = CustomAgentConfig::new( + "reload-test-agent-rust", + "You are a reload test agent.", ) - .await - .expect("create session"); - - assert_agent( - &session - .rpc() - .agent() - .list() + .with_display_name("Reload Test Agent") + .with_description("Used by the agent reload RPC test."); + let client = ctx.start_client().await; + let session = client + .create_session( + ctx.approve_all_session_config() + .with_custom_agents([reload_agent.clone()]), + ) .await - .expect("list before") - .agents, - "reload-test-agent-rust", - "Reload Test Agent", - "Used by the agent reload RPC test.", - ); - let reloaded = session.rpc().agent().reload().await.expect("reload agents"); - let current = session.rpc().agent().list().await.expect("list after"); - assert_eq!( - agent_names(&reloaded.agents), - agent_names(¤t.agents), - "reload result should match current list" - ); + .expect("create session"); - session.disconnect().await.expect("disconnect session"); - client.stop().await.expect("stop client"); - }) - }) + assert_agent( + &session + .rpc() + .agent() + .list() + .await + .expect("list before") + .agents, + "reload-test-agent-rust", + "Reload Test Agent", + "Used by the agent reload RPC test.", + ); + let reloaded = session.rpc().agent().reload().await.expect("reload agents"); + let current = session.rpc().agent().list().await.expect("list after"); + assert_eq!( + agent_names(&reloaded.agents), + agent_names(¤t.agents), + "reload result should match current list" + ); + + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) .await; } @@ -322,3 +347,5 @@ fn agent_names(agents: &[AgentInfo]) -> Vec<&str> { names.sort_unstable(); names } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("rpc_agents", 7); diff --git a/rust/tests/e2e/rpc_event_log.rs b/rust/tests/e2e/rpc_event_log.rs index 96eccced9..b116f3e50 100644 --- a/rust/tests/e2e/rpc_event_log.rs +++ b/rust/tests/e2e/rpc_event_log.rs @@ -7,11 +7,10 @@ use github_copilot_sdk::session_events::{ }; use serde_json::json; -use super::support::with_e2e_context; - #[tokio::test] async fn should_read_persisted_events_from_beginning() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_event_log", "should_read_persisted_events_from_beginning", |ctx| { @@ -43,8 +42,11 @@ async fn should_read_persisted_events_from_beginning() { .rpc() .event_log() .read(EventLogReadRequest { + agent_ids: None, agent_scope: None, cursor: None, + direction: None, + include_ephemeral: None, max: Some(100), types: Some(json!("*")), wait_ms: Some(0), @@ -70,7 +72,8 @@ async fn should_read_persisted_events_from_beginning() { #[tokio::test] async fn should_return_tail_cursor_and_read_empty_when_no_new_events() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_event_log", "should_return_tail_cursor_and_read_empty_when_no_new_events", |ctx| { @@ -88,8 +91,11 @@ async fn should_return_tail_cursor_and_read_empty_when_no_new_events() { .rpc() .event_log() .read(EventLogReadRequest { + agent_ids: None, agent_scope: None, cursor: Some(tail.cursor), + direction: None, + include_ephemeral: None, max: Some(10), types: Some(json!("*")), wait_ms: Some(0), @@ -110,7 +116,8 @@ async fn should_return_tail_cursor_and_read_empty_when_no_new_events() { #[tokio::test] async fn should_register_and_release_event_interest_idempotently() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_event_log", "should_register_and_release_event_interest_idempotently", |ctx| { @@ -156,7 +163,8 @@ async fn should_register_and_release_event_interest_idempotently() { #[tokio::test] async fn should_longpoll_with_types_filter_for_titlechanged_event() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_event_log", "should_longpoll_with_types_filter_for_titlechanged_event", |ctx| { @@ -170,8 +178,11 @@ async fn should_longpoll_with_types_filter_for_titlechanged_event() { let tail = session.rpc().event_log().tail().await.expect("tail"); let event_log = session.rpc().event_log(); let read_future = event_log.read(EventLogReadRequest { + agent_ids: None, agent_scope: None, cursor: Some(tail.cursor), + direction: None, + include_ephemeral: None, max: Some(10), types: Some(json!(["session.title_changed"])), wait_ms: Some(5_000), @@ -204,3 +215,5 @@ async fn should_longpoll_with_types_filter_for_titlechanged_event() { ) .await; } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("rpc_event_log", 4); diff --git a/rust/tests/e2e/rpc_event_side_effects.rs b/rust/tests/e2e/rpc_event_side_effects.rs index 4b634cb89..e8d7b29b2 100644 --- a/rust/tests/e2e/rpc_event_side_effects.rs +++ b/rust/tests/e2e/rpc_event_side_effects.rs @@ -8,11 +8,12 @@ use github_copilot_sdk::session_events::{ SessionWorkspaceFileChangedData, }; -use super::support::{assistant_message_content, wait_for_event, with_e2e_context}; +use super::support::{assistant_message_content, wait_for_event}; #[tokio::test] async fn should_emit_mode_changed_event_when_mode_set() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_event_side_effects", "should_emit_mode_changed_event_when_mode_set", |ctx| { @@ -54,7 +55,8 @@ async fn should_emit_mode_changed_event_when_mode_set() { #[tokio::test] async fn should_emit_plan_changed_event_for_update_and_delete() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_event_side_effects", "should_emit_plan_changed_event_for_update_and_delete", |ctx| { @@ -91,7 +93,8 @@ async fn should_emit_plan_changed_event_for_update_and_delete() { #[tokio::test] async fn should_emit_plan_changed_update_operation_on_second_update() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_event_side_effects", "should_emit_plan_changed_update_operation_on_second_update", |ctx| { @@ -132,7 +135,8 @@ async fn should_emit_plan_changed_update_operation_on_second_update() { #[tokio::test] async fn should_emit_workspace_file_changed_event_when_file_created() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_event_side_effects", "should_emit_workspace_file_changed_event_when_file_created", |ctx| { @@ -177,7 +181,8 @@ async fn should_emit_workspace_file_changed_event_when_file_created() { #[tokio::test] async fn should_emit_title_changed_event_when_name_set() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_event_side_effects", "should_emit_title_changed_event_when_name_set", |ctx| { @@ -220,7 +225,8 @@ async fn should_emit_title_changed_event_when_name_set() { #[tokio::test] async fn should_emit_snapshot_rewind_event_and_remove_events_on_truncate() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_event_side_effects", "should_emit_snapshot_rewind_event_and_remove_events_on_truncate", |ctx| { @@ -281,7 +287,8 @@ async fn should_emit_snapshot_rewind_event_and_remove_events_on_truncate() { #[tokio::test] async fn should_allow_session_use_after_truncate() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_event_side_effects", "should_allow_session_use_after_truncate", |ctx| { @@ -351,3 +358,5 @@ fn wait_for_plan_event( == operation }) } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("rpc_event_side_effects", 7); diff --git a/rust/tests/e2e/rpc_mcp_and_skills.rs b/rust/tests/e2e/rpc_mcp_and_skills.rs index eb8368ebc..d5a295e07 100644 --- a/rust/tests/e2e/rpc_mcp_and_skills.rs +++ b/rust/tests/e2e/rpc_mcp_and_skills.rs @@ -14,11 +14,10 @@ use github_copilot_sdk::rpc::{ }; use github_copilot_sdk::{IndexMap, McpServerConfig, McpStdioServerConfig}; -use super::support::with_e2e_context; - #[tokio::test] async fn should_list_and_toggle_session_skills() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_mcp_and_skills", "should_list_and_toggle_session_skills", |ctx| { @@ -87,7 +86,8 @@ async fn should_list_and_toggle_session_skills() { #[tokio::test] async fn should_ensure_skills_are_loaded_and_list_invoked_skills() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_mcp_and_skills", "should_ensure_skills_are_loaded_and_list_invoked_skills", |ctx| { @@ -137,7 +137,8 @@ async fn should_ensure_skills_are_loaded_and_list_invoked_skills() { #[tokio::test] async fn should_reload_session_skills() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_mcp_and_skills", "should_reload_session_skills", |ctx| { @@ -183,7 +184,8 @@ async fn should_reload_session_skills() { #[tokio::test] async fn should_list_mcp_servers_with_configured_server() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_mcp_and_skills", "should_list_mcp_servers_with_configured_server", |ctx| { @@ -217,7 +219,8 @@ async fn should_list_mcp_servers_with_configured_server() { #[tokio::test] async fn should_set_mcp_env_value_mode_and_remove_github_server() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_mcp_and_skills", "should_set_mcp_env_value_mode_and_remove_github_server", |ctx| { @@ -256,7 +259,8 @@ async fn should_set_mcp_env_value_mode_and_remove_github_server() { #[tokio::test] async fn should_report_mcp_sampling_failure_and_cancel_missing_sampling() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_mcp_and_skills", "should_report_mcp_sampling_failure_and_cancel_missing_sampling", |ctx| { @@ -312,76 +316,87 @@ async fn should_report_mcp_sampling_failure_and_cancel_missing_sampling() { #[tokio::test] async fn should_list_plugins() { - with_e2e_context("rpc_mcp_and_skills", "should_list_plugins", |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let client = ctx.start_client().await; - let session = client - .create_session(ctx.approve_all_session_config()) - .await - .expect("create session"); - - let result = session.rpc().plugins().list().await.expect("plugins list"); - assert!( - result.plugins.iter().all(|plugin| !plugin.name.is_empty()), - "plugins should have names: {:?}", - result.plugins - ); - - session.disconnect().await.expect("disconnect session"); - client.stop().await.expect("stop client"); - }) - }) + super::support::with_shared_e2e_context( + &E2E, + "rpc_mcp_and_skills", + "should_list_plugins", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let client = ctx.start_client().await; + let session = client + .create_session(ctx.approve_all_session_config()) + .await + .expect("create session"); + + let result = session.rpc().plugins().list().await.expect("plugins list"); + assert!( + result.plugins.iter().all(|plugin| !plugin.name.is_empty()), + "plugins should have names: {:?}", + result.plugins + ); + + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) .await; } #[tokio::test] async fn should_list_extensions() { - with_e2e_context("rpc_mcp_and_skills", "should_list_extensions", |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let client = ctx.start_client().await; - let session = client - .create_session(ctx.approve_all_session_config()) - .await - .expect("create session"); - session - .rpc() - .permissions() - .set_allow_all(PermissionsSetAllowAllRequest { - enabled: None, - mode: Some(PermissionsAllowAllMode::On), - model: None, - source: None, - }) - .await - .expect("enable allow-all"); - - let result = session - .rpc() - .extensions() - .list() - .await - .expect("extensions list"); - assert!( - result - .extensions - .iter() - .all(|extension| !extension.id.is_empty() && !extension.name.is_empty()), - "extensions should have ids and names: {:?}", - result.extensions - ); - - session.disconnect().await.expect("disconnect session"); - client.stop().await.expect("stop client"); - }) - }) + super::support::with_shared_e2e_context( + &E2E, + "rpc_mcp_and_skills", + "should_list_extensions", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let client = ctx.start_client().await; + let session = client + .create_session(ctx.approve_all_session_config()) + .await + .expect("create session"); + session + .rpc() + .permissions() + .set_allow_all(PermissionsSetAllowAllRequest { + enabled: None, + mode: Some(PermissionsAllowAllMode::On), + model: None, + source: None, + }) + .await + .expect("enable allow-all"); + + let result = session + .rpc() + .extensions() + .list() + .await + .expect("extensions list"); + assert!( + result + .extensions + .iter() + .all(|extension| !extension.id.is_empty() && !extension.name.is_empty()), + "extensions should have ids and names: {:?}", + result.extensions + ); + + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) .await; } #[tokio::test] async fn should_round_trip_mcp_app_host_context() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_mcp_and_skills", "should_round_trip_mcp_app_host_context", |ctx| { @@ -439,7 +454,8 @@ async fn should_round_trip_mcp_app_host_context() { #[tokio::test] async fn should_diagnose_and_report_mcp_app_capability_errors() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_mcp_and_skills", "should_diagnose_and_report_mcp_app_capability_errors", |ctx| { @@ -503,7 +519,8 @@ async fn should_diagnose_and_report_mcp_app_capability_errors() { #[tokio::test] async fn should_report_error_when_mcp_app_resource_is_not_available() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_mcp_and_skills", "should_report_error_when_mcp_app_resource_is_not_available", |ctx| { @@ -544,7 +561,8 @@ async fn should_report_error_when_mcp_app_resource_is_not_available() { #[tokio::test] async fn should_report_error_when_mcp_host_is_not_initialized() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_mcp_and_skills", "should_report_error_when_mcp_host_is_not_initialized", |ctx| { @@ -600,7 +618,8 @@ async fn should_report_error_when_mcp_host_is_not_initialized() { #[tokio::test] async fn should_report_error_when_mcp_oauth_server_is_not_configured() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_mcp_and_skills", "should_report_error_when_mcp_oauth_server_is_not_configured", |ctx| { @@ -639,7 +658,8 @@ async fn should_report_error_when_mcp_oauth_server_is_not_configured() { #[tokio::test] async fn should_report_error_when_mcp_oauth_server_is_not_remote() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_mcp_and_skills", "should_report_error_when_mcp_oauth_server_is_not_remote", |ctx| { @@ -680,7 +700,8 @@ async fn should_report_error_when_mcp_oauth_server_is_not_remote() { #[tokio::test] async fn should_report_error_when_extensions_are_not_available() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_mcp_and_skills", "should_report_error_when_extensions_are_not_available", |ctx| { @@ -814,3 +835,5 @@ async fn expect_err_contains( "expected error to contain {expected:?}, got {err}" ); } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("rpc_mcp_and_skills", 15); diff --git a/rust/tests/e2e/rpc_mcp_config.rs b/rust/tests/e2e/rpc_mcp_config.rs index 506987fa1..591d7d247 100644 --- a/rust/tests/e2e/rpc_mcp_config.rs +++ b/rust/tests/e2e/rpc_mcp_config.rs @@ -4,11 +4,10 @@ use github_copilot_sdk::rpc::{ }; use serde_json::json; -use super::support::with_e2e_context; - #[tokio::test] async fn should_call_server_mcp_config_rpcs() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_mcp_config", "should_call_server_mcp_config_rpcs", |ctx| { @@ -91,7 +90,8 @@ async fn should_call_server_mcp_config_rpcs() { #[tokio::test] async fn should_round_trip_http_mcp_oauth_config_rpc() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_mcp_config", "should_round_trip_http_mcp_oauth_config_rpc", |ctx| { @@ -209,3 +209,5 @@ async fn should_round_trip_http_mcp_oauth_config_rpc() { ) .await; } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("rpc_mcp_config", 2); diff --git a/rust/tests/e2e/rpc_mcp_lifecycle.rs b/rust/tests/e2e/rpc_mcp_lifecycle.rs index 028b5a527..9e135f1e9 100644 --- a/rust/tests/e2e/rpc_mcp_lifecycle.rs +++ b/rust/tests/e2e/rpc_mcp_lifecycle.rs @@ -10,11 +10,12 @@ use github_copilot_sdk::{Error, IndexMap, McpServerConfig, McpStdioServerConfig} use serde::de::DeserializeOwned; use serde_json::{Value, json}; -use super::support::{wait_for_condition, with_e2e_context}; +use super::support::wait_for_condition; #[tokio::test] async fn should_list_tools_and_report_running_status_for_connected_server() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_mcp_lifecycle", "should_list_tools_and_report_running_status_for_connected_server", |ctx| { @@ -61,7 +62,8 @@ async fn should_list_tools_and_report_running_status_for_connected_server() { #[tokio::test] async fn should_throw_when_listing_tools_for_unconnected_server() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_mcp_lifecycle", "should_throw_when_listing_tools_for_unconnected_server", |ctx| { @@ -98,7 +100,8 @@ async fn should_throw_when_listing_tools_for_unconnected_server() { #[tokio::test] async fn should_stop_running_mcp_server() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_mcp_lifecycle", "should_stop_running_mcp_server", |ctx| { @@ -137,7 +140,8 @@ async fn should_stop_running_mcp_server() { #[tokio::test] async fn should_start_and_restart_mcp_server() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_mcp_lifecycle", "should_start_and_restart_mcp_server", |ctx| { @@ -194,62 +198,16 @@ async fn should_start_and_restart_mcp_server() { .await; } -#[tokio::test] -async fn should_register_and_unregister_external_mcp_client() { - with_e2e_context( - "rpc_mcp_lifecycle", - "should_register_and_unregister_external_mcp_client", - |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let host_server = "rpc-lifecycle-extclient-host"; - let client = ctx.start_client().await; - let session = - client - .create_session(ctx.approve_all_session_config().with_mcp_servers( - create_test_mcp_servers(ctx.repo_root(), host_server), - )) - .await - .expect("create session"); - wait_for_mcp_server_status(&session, host_server, McpServerStatus::Connected).await; - - let external_name = "rpc-lifecycle-external-client"; - assert!(!is_mcp_server_running(&session, external_name).await); - - call_session_rpc( - &session, - "session.mcp.registerExternalClient", - json!({ - "serverName": external_name, - "client": { "id": external_name }, - "transport": { "kind": "in-process" }, - "config": { "command": "noop" } - }), - ) - .await - .expect("register external MCP client"); - assert!(is_mcp_server_running(&session, external_name).await); - - call_session_rpc( - &session, - "session.mcp.unregisterExternalClient", - json!({ "serverName": external_name }), - ) - .await - .expect("unregister external MCP client"); - assert!(!is_mcp_server_running(&session, external_name).await); - - session.disconnect().await.expect("disconnect session"); - client.stop().await.expect("stop client"); - }) - }, - ) - .await; -} +// There is deliberately no e2e test for `session.mcp.registerExternalClient`. That method is +// marked `visibility: internal` in the shared API contract: its `client` and `transport` fields +// are live in-process MCP SDK instances, so it cannot be driven over JSON-RPC, and no SDK +// exposes it as a typed method. A raw-RPC test used to pass only because older CLIs routed +// internal methods generically; it never exercised a supported wire API. #[tokio::test] async fn should_reload_mcp_servers_with_config() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_mcp_lifecycle", "should_reload_mcp_servers_with_config", |ctx| { @@ -291,7 +249,8 @@ async fn should_reload_mcp_servers_with_config() { #[tokio::test] async fn should_configure_github_mcp_server() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_mcp_lifecycle", "should_configure_github_mcp_server", |ctx| { @@ -421,3 +380,5 @@ fn assert_error_contains(err: &Error, expected: &str) { "expected error to contain {expected:?}, got {message}" ); } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("rpc_mcp_lifecycle", 6); diff --git a/rust/tests/e2e/rpc_queue.rs b/rust/tests/e2e/rpc_queue.rs index 2c51f9e37..6f4f88165 100644 --- a/rust/tests/e2e/rpc_queue.rs +++ b/rust/tests/e2e/rpc_queue.rs @@ -7,7 +7,7 @@ use github_copilot_sdk::session_events::{CommandQueuedData, SessionEventType}; use serde_json::json; use uuid::Uuid; -use super::support::{wait_for_condition, wait_for_event, with_e2e_context}; +use super::support::{wait_for_condition, wait_for_event}; fn is_pending_command(item: &QueuePendingItems, command: &str) -> bool { item.kind == QueuePendingItemsKind::Command @@ -66,7 +66,8 @@ async fn wait_for_queue_empty(session: &Session) { #[tokio::test] async fn fresh_queue_is_empty_and_empty_mutations_are_noops() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_queue", "fresh_queue_is_empty_and_empty_mutations_are_noops", |ctx| { @@ -115,7 +116,8 @@ async fn fresh_queue_is_empty_and_empty_mutations_are_noops() { #[tokio::test] async fn pendingitems_reports_queued_command_and_remove_and_clear_update_queue() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_queue", "pendingitems_reports_queued_command_and_remove_and_clear_update_queue", |ctx| { @@ -223,3 +225,5 @@ async fn pendingitems_reports_queued_command_and_remove_and_clear_update_queue() ) .await; } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("rpc_queue", 2); diff --git a/rust/tests/e2e/rpc_remote.rs b/rust/tests/e2e/rpc_remote.rs index c34a8d5e5..e98f6c4fa 100644 --- a/rust/tests/e2e/rpc_remote.rs +++ b/rust/tests/e2e/rpc_remote.rs @@ -1,11 +1,12 @@ use github_copilot_sdk::rpc::{RemoteEnableRequest, RemoteSessionMode}; use github_copilot_sdk::session_events::{SessionEventType, SessionRemoteSteerableChangedData}; -use super::support::{wait_for_event, with_e2e_context}; +use super::support::wait_for_event; #[tokio::test] async fn should_treat_remote_off_as_noop_or_implemented_error() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_remote", "should_treat_remote_off_as_noop_or_implemented_error", |ctx| { @@ -45,7 +46,8 @@ async fn should_treat_remote_off_as_noop_or_implemented_error() { #[tokio::test] async fn should_treat_remote_disable_as_noop_or_implemented_error() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_remote", "should_treat_remote_disable_as_noop_or_implemented_error", |ctx| { @@ -74,7 +76,8 @@ async fn should_treat_remote_disable_as_noop_or_implemented_error() { #[tokio::test] async fn should_notify_steerable_changed_event_and_persist_flag() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_remote", "should_notify_steerable_changed_event_and_persist_flag", |ctx| { @@ -112,3 +115,5 @@ async fn should_notify_steerable_changed_event_and_persist_flag() { ) .await; } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("rpc_remote", 3); diff --git a/rust/tests/e2e/rpc_schedule.rs b/rust/tests/e2e/rpc_schedule.rs index fc782fe41..af8f6f59b 100644 --- a/rust/tests/e2e/rpc_schedule.rs +++ b/rust/tests/e2e/rpc_schedule.rs @@ -1,10 +1,9 @@ use github_copilot_sdk::rpc::ScheduleStopRequest; -use super::support::with_e2e_context; - #[tokio::test] async fn should_list_no_schedules_for_fresh_session() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_schedule", "should_list_no_schedules_for_fresh_session", |ctx| { @@ -34,7 +33,8 @@ async fn should_list_no_schedules_for_fresh_session() { #[tokio::test] async fn should_return_null_entry_when_stopping_unknown_schedule() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_schedule", "should_return_null_entry_when_stopping_unknown_schedule", |ctx| { @@ -71,3 +71,5 @@ async fn should_return_null_entry_when_stopping_unknown_schedule() { ) .await; } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("rpc_schedule", 2); diff --git a/rust/tests/e2e/rpc_server.rs b/rust/tests/e2e/rpc_server.rs index d0beab245..caa846ba0 100644 --- a/rust/tests/e2e/rpc_server.rs +++ b/rust/tests/e2e/rpc_server.rs @@ -21,7 +21,8 @@ use super::support::{with_e2e_context, with_e2e_context_no_snapshot}; #[tokio::test] async fn should_call_rpc_ping_with_typed_params_and_result() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_server", "should_call_rpc_ping_with_typed_params_and_result", |ctx| { @@ -118,7 +119,8 @@ async fn should_call_rpc_account_get_quota_when_authenticated() { #[tokio::test] async fn should_call_rpc_tools_list_with_typed_result() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_server", "should_call_rpc_tools_list_with_typed_result", |ctx| { @@ -186,7 +188,8 @@ async fn should_reject_llm_response_frames_for_unknown_request() { #[tokio::test] async fn should_discover_server_mcp_and_skills() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_server", "should_discover_server_mcp_and_skills", |ctx| { @@ -401,35 +404,41 @@ async fn should_call_rpc_sessionfs_setprovider_with_typed_result() { #[tokio::test] async fn should_add_secret_filter_values() { - with_e2e_context("rpc_server", "should_add_secret_filter_values", |ctx| { - Box::pin(async move { - let client = ctx.start_client().await; + super::support::with_shared_e2e_context( + &E2E, + "rpc_server", + "should_add_secret_filter_values", + |ctx| { + Box::pin(async move { + let client = ctx.start_client().await; - let result = client - .rpc() - .secrets() - .add_filter_values(SecretsAddFilterValuesRequest { - values: vec!["rust-secret-value".to_string()], - }) - .await; - match result { - Ok(result) => assert!(result.ok), - Err(err) => { - let message = err.to_string(); - assert!(message.contains("COPILOT_ENABLE_SECRET_FILTERING")); - assert!(!message.contains("Unhandled method secrets.addFilterValues")); + let result = client + .rpc() + .secrets() + .add_filter_values(SecretsAddFilterValuesRequest { + values: vec!["rust-secret-value".to_string()], + }) + .await; + match result { + Ok(response) => assert!(response.ok), + Err(err) => { + let message = err.to_string(); + assert!(message.contains("COPILOT_ENABLE_SECRET_FILTERING")); + assert!(!message.contains("Unhandled method secrets.addFilterValues")); + } } - } - client.stop().await.expect("stop client"); - }) - }) + client.stop().await.expect("stop client"); + }) + }, + ) .await; } #[tokio::test] async fn should_list_find_and_inspect_persisted_session_state() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_server", "should_list_find_and_inspect_persisted_session_state", |ctx| { @@ -550,7 +559,8 @@ async fn should_list_find_and_inspect_persisted_session_state() { #[tokio::test] async fn should_enrich_basic_session_metadata() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_server", "should_enrich_basic_session_metadata", |ctx| { @@ -604,7 +614,8 @@ async fn should_enrich_basic_session_metadata() { #[tokio::test] async fn should_close_active_session_and_release_lock() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_server", "should_close_active_session_and_release_lock", |ctx| { @@ -655,7 +666,8 @@ async fn should_close_active_session_and_release_lock() { #[tokio::test] async fn should_prune_dryrun_and_bulkdelete_persisted_session() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_server", "should_prune_dryrun_and_bulkdelete_persisted_session", |ctx| { @@ -702,7 +714,8 @@ async fn should_prune_dryrun_and_bulkdelete_persisted_session() { #[tokio::test] async fn should_set_additional_plugins_and_reload_deferred_hooks() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_server", "should_set_additional_plugins_and_reload_deferred_hooks", |ctx| { @@ -752,34 +765,40 @@ async fn should_set_additional_plugins_and_reload_deferred_hooks() { #[tokio::test] async fn should_save_and_get_event_file_path() { - with_e2e_context("rpc_server", "should_save_and_get_event_file_path", |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let client = ctx.start_client().await; - let session = client - .create_session(ctx.approve_all_session_config()) - .await - .expect("create session"); + super::support::with_shared_e2e_context( + &E2E, + "rpc_server", + "should_save_and_get_event_file_path", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let client = ctx.start_client().await; + let session = client + .create_session(ctx.approve_all_session_config()) + .await + .expect("create session"); - client - .rpc() - .sessions() - .save(SessionsSaveRequest { - session_id: session.id().clone(), - }) - .await - .expect("save session"); + client + .rpc() + .sessions() + .save(SessionsSaveRequest { + session_id: session.id().clone(), + }) + .await + .expect("save session"); - session.disconnect().await.expect("disconnect session"); - client.stop().await.expect("stop client"); - }) - }) + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) .await; } #[tokio::test] async fn should_report_implemented_error_when_connecting_unknown_remote_session() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_server", "should_report_implemented_error_when_connecting_unknown_remote_session", |ctx| { @@ -861,3 +880,5 @@ fn paths_equal(left: &str, right: &str) -> bool { normalize(left) == normalize(right) } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("rpc_server", 11); diff --git a/rust/tests/e2e/rpc_server_misc.rs b/rust/tests/e2e/rpc_server_misc.rs index b9e5cdf5c..47ae4ecbd 100644 --- a/rust/tests/e2e/rpc_server_misc.rs +++ b/rust/tests/e2e/rpc_server_misc.rs @@ -9,27 +9,33 @@ use super::support::{wait_for_condition, with_e2e_context}; #[tokio::test] async fn should_reload_user_settings() { - with_e2e_context("rpc_server_misc", "should_reload_user_settings", |ctx| { - Box::pin(async move { - let client = ctx.start_client().await; + super::support::with_shared_e2e_context( + &E2E, + "rpc_server_misc", + "should_reload_user_settings", + |ctx| { + Box::pin(async move { + let client = ctx.start_client().await; - client - .rpc() - .user() - .settings() - .reload() - .await - .expect("reload user settings"); + client + .rpc() + .user() + .settings() + .reload() + .await + .expect("reload user settings"); - client.stop().await.expect("stop client"); - }) - }) + client.stop().await.expect("stop client"); + }) + }, + ) .await; } #[tokio::test] async fn should_get_set_and_clear_user_settings() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_server_misc", "should_get_set_and_clear_user_settings", |ctx| { @@ -206,7 +212,8 @@ async fn should_login_list_getcurrentauth_and_logout_account() { #[tokio::test] async fn should_report_agent_registry_spawn_gate_closed() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_server_misc", "should_report_agent_registry_spawn_gate_closed", |ctx| { @@ -279,7 +286,8 @@ async fn should_shut_down_owned_runtime() { #[tokio::test] async fn should_report_not_found_when_opening_session_without_context() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_server_misc", "should_report_not_found_when_opening_session_without_context", |ctx| { @@ -305,7 +313,8 @@ async fn should_report_not_found_when_opening_session_without_context() { #[tokio::test] async fn should_reject_send_attachments_from_non_extension_connection() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_server_misc", "should_reject_send_attachments_from_non_extension_connection", |ctx| { @@ -353,3 +362,5 @@ fn setting_patch(key: &str, value: Value) -> Value { settings.insert(key.to_string(), value); Value::Object(settings) } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("rpc_server_misc", 5); diff --git a/rust/tests/e2e/rpc_server_plugins.rs b/rust/tests/e2e/rpc_server_plugins.rs index 054ffa359..df6072253 100644 --- a/rust/tests/e2e/rpc_server_plugins.rs +++ b/rust/tests/e2e/rpc_server_plugins.rs @@ -8,15 +8,14 @@ use github_copilot_sdk::rpc::{ PluginsUpdateRequest, }; -use super::support::with_e2e_context; - const MARKETPLACE_NAME: &str = "csharp-e2e-marketplace"; const PLUGIN_NAME: &str = "csharp-e2e-plugin"; const DIRECT_PLUGIN_NAME: &str = "csharp-e2e-direct"; #[tokio::test] async fn should_install_and_list_plugin_from_local_marketplace() { - with_e2e_context( + super::support::with_dedicated_group_e2e_context( + &E2E, "rpc_server_plugins", "should_install_and_list_plugin_from_local_marketplace", |ctx| { @@ -65,7 +64,8 @@ async fn should_install_and_list_plugin_from_local_marketplace() { #[tokio::test] async fn should_enable_and_disable_marketplace_plugin() { - with_e2e_context( + super::support::with_dedicated_group_e2e_context( + &E2E, "rpc_server_plugins", "should_enable_and_disable_marketplace_plugin", |ctx| { @@ -135,7 +135,8 @@ async fn should_enable_and_disable_marketplace_plugin() { #[tokio::test] async fn should_update_single_marketplace_plugin() { - with_e2e_context( + super::support::with_dedicated_group_e2e_context( + &E2E, "rpc_server_plugins", "should_update_single_marketplace_plugin", |ctx| { @@ -184,7 +185,8 @@ async fn should_update_single_marketplace_plugin() { #[tokio::test] async fn should_update_all_installed_plugins() { - with_e2e_context( + super::support::with_dedicated_group_e2e_context( + &E2E, "rpc_server_plugins", "should_update_all_installed_plugins", |ctx| { @@ -241,7 +243,8 @@ async fn should_update_all_installed_plugins() { #[tokio::test] async fn should_install_direct_local_plugin_with_deprecation_warning() { - with_e2e_context( + super::support::with_dedicated_group_e2e_context( + &E2E, "rpc_server_plugins", "should_install_direct_local_plugin_with_deprecation_warning", |ctx| { @@ -316,7 +319,8 @@ async fn should_install_direct_local_plugin_with_deprecation_warning() { #[tokio::test] async fn should_list_browse_refresh_and_remove_local_marketplace() { - with_e2e_context( + super::support::with_dedicated_group_e2e_context( + &E2E, "rpc_server_plugins", "should_list_browse_refresh_and_remove_local_marketplace", |ctx| { @@ -434,7 +438,8 @@ async fn should_list_browse_refresh_and_remove_local_marketplace() { #[tokio::test] async fn should_reload_mcp_config_cache() { - with_e2e_context( + super::support::with_dedicated_group_e2e_context( + &E2E, "rpc_server_plugins", "should_reload_mcp_config_cache", |ctx| { @@ -538,3 +543,5 @@ fn single_plugin<'a>( assert_eq!(matches.len(), 1, "expected one plugin in {list:?}"); matches[0] } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("rpc_server_plugins", 7); diff --git a/rust/tests/e2e/rpc_server_remote_control.rs b/rust/tests/e2e/rpc_server_remote_control.rs index a49f1d12a..49809235c 100644 --- a/rust/tests/e2e/rpc_server_remote_control.rs +++ b/rust/tests/e2e/rpc_server_remote_control.rs @@ -6,11 +6,10 @@ use github_copilot_sdk::rpc::{ }; use serde_json::Value; -use super::support::with_e2e_context; - #[tokio::test] async fn should_report_remote_control_status_as_off() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_server_remote_control", "should_report_remote_control_status_as_off", |ctx| { @@ -34,7 +33,8 @@ async fn should_report_remote_control_status_as_off() { #[tokio::test] async fn should_treat_set_steering_as_no_op_when_off() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_server_remote_control", "should_treat_set_steering_as_no_op_when_off", |ctx| { @@ -60,7 +60,8 @@ async fn should_treat_set_steering_as_no_op_when_off() { #[tokio::test] async fn should_report_not_stopped_when_remote_control_is_off() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_server_remote_control", "should_report_not_stopped_when_remote_control_is_off", |ctx| { @@ -85,7 +86,8 @@ async fn should_report_not_stopped_when_remote_control_is_off() { #[tokio::test] async fn should_reject_transfer_when_off_with_compare_and_swap() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_server_remote_control", "should_reject_transfer_when_off_with_compare_and_swap", |ctx| { @@ -116,7 +118,8 @@ async fn should_reject_transfer_when_off_with_compare_and_swap() { #[tokio::test] async fn should_reach_runtime_when_starting_remote_control_for_unknown_session() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_server_remote_control", "should_reach_runtime_when_starting_remote_control_for_unknown_session", |ctx| { @@ -177,3 +180,5 @@ fn assert_not_unhandled(message: &str) { "{message}" ); } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("rpc_server_remote_control", 5); diff --git a/rust/tests/e2e/rpc_session_state.rs b/rust/tests/e2e/rpc_session_state.rs index 199ff2a2b..c705d231c 100644 --- a/rust/tests/e2e/rpc_session_state.rs +++ b/rust/tests/e2e/rpc_session_state.rs @@ -5,8 +5,8 @@ use github_copilot_sdk::rpc::{ MetadataRecomputeContextTokensRequest, MetadataRecordContextChangeRequest, MetadataSetWorkingDirectoryRequest, MetadataSnapshotCurrentMode, ModeSetRequest, ModelSetReasoningEffortRequest, ModelSwitchToRequest, NameSetAutoRequest, NameSetRequest, - PermissionsSetApproveAllRequest, PlanUpdateRequest, SessionSetCredentialsParams, - SessionUpdateOptionsParams, SessionWorkingDirectoryContext, + PermissionsResetSessionApprovalsRequest, PermissionsSetApproveAllRequest, PlanUpdateRequest, + SessionSetCredentialsParams, SessionUpdateOptionsParams, SessionWorkingDirectoryContext, SessionWorkingDirectoryContextHostType, SessionsForkRequest, ShutdownRequest, TelemetrySetFeatureOverridesRequest, WorkspacesCreateFileRequest, WorkspacesReadFileRequest, }; @@ -17,15 +17,14 @@ use github_copilot_sdk::session_events::{ }; use serde_json::json; -use super::support::{ - assistant_message_content, wait_for_condition, wait_for_event, with_e2e_context, -}; +use super::support::{assistant_message_content, wait_for_condition, wait_for_event}; const MODEL_ID: &str = "claude-sonnet-4.5"; #[tokio::test] async fn should_call_session_rpc_model_getcurrent() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_session_state", "should_call_session_rpc_model_getcurrent", |ctx| { @@ -55,7 +54,8 @@ async fn should_call_session_rpc_model_getcurrent() { #[tokio::test] async fn should_call_session_rpc_model_switchto() { - with_e2e_context( + super::support::with_dedicated_group_e2e_context( + &E2E, "rpc_session_state", "should_call_session_rpc_model_switchto", |ctx| { @@ -107,7 +107,8 @@ async fn should_call_session_rpc_model_switchto() { #[tokio::test] async fn should_get_and_set_session_mode() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_session_state", "should_get_and_set_session_mode", |ctx| { @@ -146,7 +147,8 @@ async fn should_get_and_set_session_mode() { #[tokio::test] async fn should_shutdown_session_with_routine_type() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_session_state", "should_shutdown_session_with_routine_type", |ctx| { @@ -184,7 +186,8 @@ async fn should_shutdown_session_with_routine_type() { #[tokio::test] async fn should_set_and_get_each_session_mode_value() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_session_state", "should_set_and_get_each_session_mode_value", |ctx| { @@ -220,7 +223,8 @@ async fn should_set_and_get_each_session_mode_value() { #[tokio::test] async fn should_read_update_and_delete_plan() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_session_state", "should_read_update_and_delete_plan", |ctx| { @@ -285,7 +289,8 @@ async fn should_read_update_and_delete_plan() { #[tokio::test] async fn should_call_workspace_file_rpc_methods() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_session_state", "should_call_workspace_file_rpc_methods", |ctx| { @@ -342,7 +347,8 @@ async fn should_call_workspace_file_rpc_methods() { #[tokio::test] async fn should_reject_workspace_file_path_traversal() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_session_state", "should_reject_workspace_file_path_traversal", |ctx| { @@ -386,7 +392,8 @@ async fn should_reject_workspace_file_path_traversal() { #[tokio::test] async fn should_create_workspace_file_with_nested_path_auto_creating_dirs() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_session_state", "should_create_workspace_file_with_nested_path_auto_creating_dirs", |ctx| { @@ -428,7 +435,8 @@ async fn should_create_workspace_file_with_nested_path_auto_creating_dirs() { #[tokio::test] async fn should_report_error_reading_nonexistent_workspace_file() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_session_state", "should_report_error_reading_nonexistent_workspace_file", |ctx| { @@ -461,7 +469,8 @@ async fn should_report_error_reading_nonexistent_workspace_file() { #[tokio::test] async fn should_update_existing_workspace_file_with_update_operation() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_session_state", "should_update_existing_workspace_file_with_update_operation", |ctx| { @@ -516,7 +525,8 @@ async fn should_update_existing_workspace_file_with_update_operation() { #[tokio::test] async fn should_reject_empty_or_whitespace_session_name() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_session_state", "should_reject_empty_or_whitespace_session_name", |ctx| { @@ -551,7 +561,8 @@ async fn should_reject_empty_or_whitespace_session_name() { #[tokio::test] async fn should_emit_title_changed_event_each_time_name_set_is_called() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_session_state", "should_emit_title_changed_event_each_time_name_set_is_called", |ctx| { @@ -602,7 +613,8 @@ async fn should_emit_title_changed_event_each_time_name_set_is_called() { #[tokio::test] async fn should_get_and_set_session_metadata() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_session_state", "should_call_metadata_snapshot_setworkingdirectory_and_recordcontextchange", |ctx| { @@ -651,7 +663,8 @@ async fn should_get_and_set_session_metadata() { #[tokio::test] async fn should_call_metadata_snapshot_setworkingdirectory_and_recordcontextchange() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_session_state", "should_get_and_set_session_metadata", |ctx| { @@ -731,7 +744,8 @@ async fn should_call_metadata_snapshot_setworkingdirectory_and_recordcontextchan #[tokio::test] async fn should_update_options_and_initialize_session_services() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_session_state", "should_update_options_and_initialize_session_services", |ctx| { @@ -796,7 +810,8 @@ async fn should_update_options_and_initialize_session_services() { #[tokio::test] async fn should_set_reasoningeffort_and_auto_name() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_session_state", "should_set_reasoningeffort_and_auto_name", |ctx| { @@ -854,51 +869,57 @@ async fn should_set_reasoningeffort_and_auto_name() { #[tokio::test] async fn should_set_auth_credentials() { - with_e2e_context("rpc_session_state", "should_set_auth_credentials", |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let token = "rpc-session-auth-token"; - ctx.set_copilot_user_by_token_with_login(token, "rpc-session-user"); - let client = ctx.start_client().await; - let session = client - .create_session(ctx.approve_all_session_config()) - .await - .expect("create session"); - - let set = session - .rpc() - .git_hub_auth() - .set_credentials(SessionSetCredentialsParams { - credentials: Some(json!({ - "type": "user", - "host": "github.com", - "login": "rpc-session-user" - })), - }) - .await - .expect("set credentials"); - assert!(set.success); - let status = session - .rpc() - .git_hub_auth() - .get_status() - .await - .expect("auth status"); - assert!(status.is_authenticated); - assert_eq!(status.auth_type, Some(AuthInfoType::User)); - assert_eq!(status.host.as_deref(), Some("github.com")); - assert_eq!(status.login.as_deref(), Some("rpc-session-user")); - - session.disconnect().await.expect("disconnect session"); - client.stop().await.expect("stop client"); - }) - }) + super::support::with_shared_e2e_context( + &E2E, + "rpc_session_state", + "should_set_auth_credentials", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let token = "rpc-session-auth-token"; + ctx.set_copilot_user_by_token_with_login(token, "rpc-session-user"); + let client = ctx.start_client().await; + let session = client + .create_session(ctx.approve_all_session_config()) + .await + .expect("create session"); + + let set = session + .rpc() + .git_hub_auth() + .set_credentials(SessionSetCredentialsParams { + credentials: Some(json!({ + "type": "user", + "host": "github.com", + "login": "rpc-session-user" + })), + }) + .await + .expect("set credentials"); + assert!(set.success); + let status = session + .rpc() + .git_hub_auth() + .get_status() + .await + .expect("auth status"); + assert!(status.is_authenticated); + assert_eq!(status.auth_type, Some(AuthInfoType::User)); + assert_eq!(status.host.as_deref(), Some("github.com")); + assert_eq!(status.login.as_deref(), Some("rpc-session-user")); + + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) .await; } #[tokio::test] async fn should_fork_session_with_persisted_messages() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_session_state", "should_fork_session_with_persisted_messages", |ctx| { @@ -956,7 +977,8 @@ async fn should_fork_session_with_persisted_messages() { #[tokio::test] async fn should_report_error_when_forking_session_to_unknown_event_id() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_session_state", "should_report_error_when_forking_session_to_unknown_event_id", |ctx| { @@ -992,7 +1014,8 @@ async fn should_report_error_when_forking_session_to_unknown_event_id() { #[tokio::test] async fn should_call_session_usage_and_permission_rpcs() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_session_state", "should_call_session_usage_and_permission_rpcs", |ctx| { @@ -1023,7 +1046,7 @@ async fn should_call_session_usage_and_permission_rpcs() { session .rpc() .permissions() - .reset_session_approvals() + .reset_session_approvals(PermissionsResetSessionApprovalsRequest::default()) .await .expect("reset approvals") .success @@ -1039,7 +1062,8 @@ async fn should_call_session_usage_and_permission_rpcs() { #[tokio::test] async fn should_report_implemented_errors_for_unsupported_session_rpc_paths() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_session_state", "should_report_implemented_errors_for_unsupported_session_rpc_paths", |ctx| { @@ -1074,10 +1098,11 @@ async fn should_report_implemented_errors_for_unsupported_session_rpc_paths() { } #[tokio::test] -async fn should_compact_session_history_after_messages() { - with_e2e_context( +async fn should_report_processing_and_context_metadata() { + super::support::with_shared_e2e_context( + &E2E, "rpc_session_state", - "should_compact_session_history_after_messages", + "should_report_processing_and_context_metadata", |ctx| { Box::pin(async move { ctx.set_default_copilot_user(); @@ -1181,3 +1206,5 @@ fn assistant_message_content_if_present( None } } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("rpc_session_state", 22); diff --git a/rust/tests/e2e/rpc_session_state_extras.rs b/rust/tests/e2e/rpc_session_state_extras.rs index 3bd6c99be..f43359f0b 100644 --- a/rust/tests/e2e/rpc_session_state_extras.rs +++ b/rust/tests/e2e/rpc_session_state_extras.rs @@ -54,7 +54,8 @@ async fn should_list_models_for_session() { #[tokio::test] async fn should_report_session_activity_when_idle() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_session_state_extras", "should_report_session_activity_when_idle", |ctx| { @@ -86,7 +87,8 @@ async fn should_report_session_activity_when_idle() { #[tokio::test] async fn should_get_and_set_allowall_permissions() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_session_state_extras", "should_get_and_set_allowall_permissions", |ctx| { @@ -162,7 +164,8 @@ async fn should_get_and_set_allowall_permissions() { #[tokio::test] async fn should_read_empty_sql_todos_for_fresh_session() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_session_state_extras", "should_read_empty_sql_todos_for_fresh_session", |ctx| { @@ -193,7 +196,8 @@ async fn should_read_empty_sql_todos_for_fresh_session() { #[tokio::test] async fn should_get_telemetry_engagement_id() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_session_state_extras", "should_get_telemetry_engagement_id", |ctx| { @@ -222,7 +226,8 @@ async fn should_get_telemetry_engagement_id() { #[tokio::test] async fn should_get_current_tool_metadata_after_initialization() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_session_state_extras", "should_get_current_tool_metadata_after_initialization", |ctx| { @@ -262,7 +267,8 @@ async fn should_get_current_tool_metadata_after_initialization() { #[tokio::test] async fn should_add_byok_provider_and_model_at_runtime() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_session_state_extras", "should_add_byok_provider_and_model_at_runtime", |ctx| { @@ -315,6 +321,7 @@ async fn should_add_byok_provider_and_model_at_runtime() { .model() .switch_to(ModelSwitchToRequest { context_tier: None, + defer_if_model_change_queued: None, model_capabilities: None, model_id: selection_id.to_string(), reasoning_effort: None, @@ -341,7 +348,8 @@ async fn should_add_byok_provider_and_model_at_runtime() { #[tokio::test] async fn should_return_empty_completions_when_host_does_not_provide_them() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_session_state_extras", "should_return_empty_completions_when_host_does_not_provide_them", |ctx| { @@ -374,7 +382,8 @@ async fn should_return_empty_completions_when_host_does_not_provide_them() { #[tokio::test] async fn should_report_visibility_as_unsynced_for_local_session() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_session_state_extras", "should_report_visibility_as_unsynced_for_local_session", |ctx| { @@ -417,7 +426,8 @@ async fn should_report_visibility_as_unsynced_for_local_session() { #[tokio::test] async fn should_get_context_attribution_and_heaviest_messages_after_turn() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_session_state_extras", "should_get_context_attribution_and_heaviest_messages_after_turn", |ctx| { @@ -463,7 +473,8 @@ async fn should_get_context_attribution_and_heaviest_messages_after_turn() { #[tokio::test] async fn should_update_and_clear_live_subagent_settings() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_session_state_extras", "should_update_and_clear_live_subagent_settings", |ctx| { @@ -514,7 +525,8 @@ async fn should_update_and_clear_live_subagent_settings() { #[tokio::test] async fn should_reload_session_plugins() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_session_state_extras", "should_reload_session_plugins", |ctx| { @@ -553,3 +565,5 @@ async fn should_reload_session_plugins() { ) .await; } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("rpc_session_state_extras", 11); diff --git a/rust/tests/e2e/rpc_shell_and_fleet.rs b/rust/tests/e2e/rpc_shell_and_fleet.rs index 219929c44..968d51147 100644 --- a/rust/tests/e2e/rpc_shell_and_fleet.rs +++ b/rust/tests/e2e/rpc_shell_and_fleet.rs @@ -1,10 +1,11 @@ use github_copilot_sdk::rpc::{ShellExecRequest, ShellKillRequest}; -use super::support::{wait_for_condition, with_e2e_context}; +use super::support::wait_for_condition; #[tokio::test] async fn should_execute_shell_command() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_shell_and_fleet", "should_execute_shell_command", |ctx| { @@ -41,42 +42,47 @@ async fn should_execute_shell_command() { #[tokio::test] async fn should_kill_shell_process() { - with_e2e_context("rpc_shell_and_fleet", "should_kill_shell_process", |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let client = ctx.start_client().await; - let session = client - .create_session(ctx.approve_all_session_config()) - .await - .expect("create session"); + super::support::with_shared_e2e_context( + &E2E, + "rpc_shell_and_fleet", + "should_kill_shell_process", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let client = ctx.start_client().await; + let session = client + .create_session(ctx.approve_all_session_config()) + .await + .expect("create session"); - let exec = session - .rpc() - .shell() - .exec(ShellExecRequest { - command: long_running_command(), - cwd: Some(ctx.work_dir().display().to_string()), - timeout: None, - }) - .await - .expect("start shell process"); - assert!(!exec.process_id.trim().is_empty()); + let exec = session + .rpc() + .shell() + .exec(ShellExecRequest { + command: long_running_command(), + cwd: Some(ctx.work_dir().display().to_string()), + timeout: None, + }) + .await + .expect("start shell process"); + assert!(!exec.process_id.trim().is_empty()); - let killed = session - .rpc() - .shell() - .kill(ShellKillRequest { - process_id: exec.process_id, - signal: None, - }) - .await - .expect("kill shell process"); - assert!(killed.killed); + let killed = session + .rpc() + .shell() + .kill(ShellKillRequest { + process_id: exec.process_id, + signal: None, + }) + .await + .expect("kill shell process"); + assert!(killed.killed); - session.disconnect().await.expect("disconnect session"); - client.stop().await.expect("stop client"); - }) - }) + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) .await; } @@ -113,3 +119,5 @@ fn long_running_command() -> String { fn long_running_command() -> String { "sleep 30".to_string() } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("rpc_shell_and_fleet", 2); diff --git a/rust/tests/e2e/rpc_shell_edge_cases.rs b/rust/tests/e2e/rpc_shell_edge_cases.rs index 74ae89736..df5ddb1dc 100644 --- a/rust/tests/e2e/rpc_shell_edge_cases.rs +++ b/rust/tests/e2e/rpc_shell_edge_cases.rs @@ -1,17 +1,20 @@ use std::path::Path; +use std::time::Duration; use github_copilot_sdk::rpc::{ShellExecRequest, ShellKillRequest, ShellKillSignal}; -use super::support::{wait_for_condition, with_e2e_context}; +use super::support::wait_for_condition; #[tokio::test] async fn shell_exec_with_timeout_kills_long_running_command() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_shell_edge_cases", "shell_exec_with_timeout_kills_long_running_command", |ctx| { Box::pin(async move { ctx.set_default_copilot_user(); + let timeout = shell_timeout(); let started_path = ctx.work_dir().join("shell-timeout-started.txt"); let marker_path = ctx.work_dir().join("shell-timeout-marker.txt"); let client = ctx.start_client().await; @@ -26,13 +29,20 @@ async fn shell_exec_with_timeout_kills_long_running_command() { .exec(ShellExecRequest { command: delayed_write_command(&started_path, &marker_path), cwd: Some(ctx.work_dir().display().to_string()), - timeout: Some(200), + timeout: Some( + timeout + .as_millis() + .try_into() + .expect("shell timeout fits in i64"), + ), }) .await .expect("execute timed command"); assert!(!result.process_id.trim().is_empty()); wait_for_exists(&started_path).await; + // The cleanup probe should not terminate a process before its timeout expires. + tokio::time::sleep(timeout).await; wait_for_process_cleanup(&session, result.process_id, "timed-out command").await; assert!( !marker_path.exists(), @@ -49,7 +59,8 @@ async fn shell_exec_with_timeout_kills_long_running_command() { #[tokio::test] async fn shell_exec_with_custom_cwd_honors_override() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_shell_edge_cases", "shell_exec_with_custom_cwd_honors_override", |ctx| { @@ -88,7 +99,8 @@ async fn shell_exec_with_custom_cwd_honors_override() { #[tokio::test] async fn shell_exec_with_nonexistent_command_returns_processid_and_cleans_up() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_shell_edge_cases", "shell_exec_with_nonexistent_command_returns_processid_and_cleans_up", |ctx| { @@ -124,7 +136,8 @@ async fn shell_exec_with_nonexistent_command_returns_processid_and_cleans_up() { #[tokio::test] async fn shell_kill_unknown_processid_returns_false() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_shell_edge_cases", "shell_kill_unknown_processid_returns_false", |ctx| { @@ -158,7 +171,8 @@ async fn shell_kill_unknown_processid_returns_false() { #[tokio::test] async fn shell_kill_cleans_up_after_terminating_signal() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_shell_edge_cases", "shell_kill_cleans_up_after_terminating_signal", |ctx| { @@ -203,7 +217,8 @@ async fn shell_kill_cleans_up_after_terminating_signal() { #[tokio::test] async fn shell_exec_with_stderr_output_cleans_up() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_shell_edge_cases", "shell_exec_with_stderr_output_cleans_up", |ctx| { @@ -240,7 +255,8 @@ async fn shell_exec_with_stderr_output_cleans_up() { #[tokio::test] async fn shell_exec_with_large_stdout_cleans_up() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_shell_edge_cases", "shell_exec_with_large_stdout_cleans_up", |ctx| { @@ -316,6 +332,11 @@ fn delayed_write_command(started_path: &Path, marker_path: &Path) -> String { ) } +#[cfg(windows)] +fn shell_timeout() -> Duration { + Duration::from_secs(2) +} + #[cfg(not(windows))] fn delayed_write_command(started_path: &Path, marker_path: &Path) -> String { format!( @@ -325,6 +346,11 @@ fn delayed_write_command(started_path: &Path, marker_path: &Path) -> String { ) } +#[cfg(not(windows))] +fn shell_timeout() -> Duration { + Duration::from_millis(200) +} + #[cfg(windows)] fn write_relative_marker_command(marker: &str) -> String { format!( @@ -388,3 +414,5 @@ fn large_stdout_command(marker_path: &Path) -> String { marker_path.display() ) } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("rpc_shell_edge_cases", 7); diff --git a/rust/tests/e2e/rpc_shell_user_requested.rs b/rust/tests/e2e/rpc_shell_user_requested.rs index 7bd52ae9f..43de1c2cc 100644 --- a/rust/tests/e2e/rpc_shell_user_requested.rs +++ b/rust/tests/e2e/rpc_shell_user_requested.rs @@ -5,11 +5,12 @@ use std::time::Duration; use github_copilot_sdk::RequestId; use github_copilot_sdk::rpc::{ShellCancelUserRequestedRequest, ShellExecuteUserRequestedRequest}; -use super::support::{wait_for_condition, with_e2e_context}; +use super::support::wait_for_condition; #[tokio::test] async fn should_execute_user_requested_shell_command() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_shell_user_requested", "should_execute_user_requested_shell_command", |ctx| { @@ -52,7 +53,8 @@ async fn should_execute_user_requested_shell_command() { #[tokio::test] async fn should_cancel_user_requested_shell_command() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_shell_user_requested", "should_cancel_user_requested_shell_command", |ctx| { @@ -171,3 +173,5 @@ fn powershell_quote(path: &Path) -> String { fn posix_shell_quote(path: &Path) -> String { format!("'{}'", path.display().to_string().replace('\'', "'\\''")) } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("rpc_shell_user_requested", 2); diff --git a/rust/tests/e2e/rpc_tasks_and_handlers.rs b/rust/tests/e2e/rpc_tasks_and_handlers.rs index 601cc70bf..b046687f4 100644 --- a/rust/tests/e2e/rpc_tasks_and_handlers.rs +++ b/rust/tests/e2e/rpc_tasks_and_handlers.rs @@ -27,11 +27,10 @@ use github_copilot_sdk::rpc::{ UIUnregisterDirectAutoModeSwitchHandlerRequest, UIUserInputResponse, }; -use super::support::with_e2e_context; - #[tokio::test] async fn should_list_task_state_and_return_false_for_missing_task_operations() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_tasks_and_handlers", "should_list_task_state_and_return_false_for_missing_task_operations", |ctx| { @@ -145,7 +144,8 @@ async fn should_list_task_state_and_return_false_for_missing_task_operations() { #[tokio::test] async fn should_report_implemented_error_for_missing_task_agent_type() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_tasks_and_handlers", "should_report_implemented_error_for_missing_task_agent_type", |ctx| { @@ -182,7 +182,8 @@ async fn should_report_implemented_error_for_missing_task_agent_type() { #[tokio::test] async fn should_report_implemented_error_for_invalid_task_agent_model() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_tasks_and_handlers", "should_report_implemented_error_for_invalid_task_agent_model", |ctx| { @@ -229,7 +230,7 @@ async fn should_report_implemented_error_for_invalid_task_agent_model() { #[tokio::test] async fn should_return_expected_results_for_missing_pending_handler_requestids() { - with_e2e_context( + super::support::with_shared_e2e_context(&E2E, "rpc_tasks_and_handlers", "should_return_expected_results_for_missing_pending_handler_requestids", |ctx| { @@ -322,6 +323,7 @@ async fn should_return_expected_results_for_missing_pending_handler_requestids() response: UIExitPlanModeResponse { approved: false, auto_approve_edits: None, + defer_implementation: None, feedback: Some("not now".to_string()), selected_action: None, }, @@ -358,6 +360,7 @@ async fn should_return_expected_results_for_missing_pending_handler_requestids() ( "missing-approve-once-request", PermissionDecision::ApproveOnce(PermissionDecisionApproveOnce { + approved_interactively: None, kind: PermissionDecisionApproveOnceKind::ApproveOnce, }), ), @@ -401,6 +404,7 @@ async fn should_return_expected_results_for_missing_pending_handler_requestids() .rpc() .permissions() .handle_pending_permission_request(PermissionDecisionRequest { + decision_context: None, request_id: request_id.into(), result, }) @@ -441,7 +445,8 @@ async fn should_return_expected_results_for_missing_pending_handler_requestids() #[tokio::test] async fn should_register_and_unregister_direct_auto_mode_switch_handler() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_tasks_and_handlers", "should_register_and_unregister_direct_auto_mode_switch_handler", |ctx| { @@ -501,3 +506,5 @@ fn assert_implemented_error(result: Result, met "expected implemented error for {method}, got {message}" ); } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("rpc_tasks_and_handlers", 5); diff --git a/rust/tests/e2e/rpc_ui_ephemeral_query.rs b/rust/tests/e2e/rpc_ui_ephemeral_query.rs index 83852d092..2fa421cc6 100644 --- a/rust/tests/e2e/rpc_ui_ephemeral_query.rs +++ b/rust/tests/e2e/rpc_ui_ephemeral_query.rs @@ -1,10 +1,9 @@ use github_copilot_sdk::rpc::UIEphemeralQueryRequest; -use super::support::with_e2e_context; - #[tokio::test] async fn should_answer_ephemeral_query() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_ui_ephemeral_query", "should_answer_ephemeral_query", |ctx| { @@ -36,3 +35,5 @@ async fn should_answer_ephemeral_query() { ) .await; } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("rpc_ui_ephemeral_query", 1); diff --git a/rust/tests/e2e/rpc_workspace_checkpoints.rs b/rust/tests/e2e/rpc_workspace_checkpoints.rs index 0a8bf5615..48145970c 100644 --- a/rust/tests/e2e/rpc_workspace_checkpoints.rs +++ b/rust/tests/e2e/rpc_workspace_checkpoints.rs @@ -6,11 +6,10 @@ use github_copilot_sdk::rpc::{ WorkspacesReadCheckpointRequest, WorkspacesReadFileRequest, WorkspacesSaveLargePasteRequest, }; -use super::support::with_e2e_context; - #[tokio::test] async fn should_list_no_checkpoints_for_fresh_session() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_workspace_checkpoints", "should_list_no_checkpoints_for_fresh_session", |ctx| { @@ -40,13 +39,16 @@ async fn should_list_no_checkpoints_for_fresh_session() { #[tokio::test] async fn should_return_null_or_empty_content_for_unknown_checkpoint() { - // In-process, session.workspaces.readCheckpoint is answered by the native runtime, - // which decodes the checkpoint number as a u32 and rejects the i64::MAX sentinel this - // test uses. Covered by the default (stdio) transport. See issue #1934. - if super::support::skip_inprocess("readCheckpoint decodes the id as u32 in-process") { + if super::support::skip_shared_e2e_inprocess( + &E2E, + "readCheckpoint decodes the id as u32 in-process", + ) + .await + { return; } - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_workspace_checkpoints", "should_return_null_or_empty_content_for_unknown_checkpoint", |ctx| { @@ -76,7 +78,8 @@ async fn should_return_null_or_empty_content_for_unknown_checkpoint() { #[tokio::test] async fn should_return_typed_workspace_diff_result() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_workspace_checkpoints", "should_return_typed_workspace_diff_result", |ctx| { @@ -128,7 +131,8 @@ async fn should_return_typed_workspace_diff_result() { #[tokio::test] async fn should_save_large_paste_and_expose_readable_content() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "rpc_workspace_checkpoints", "should_save_large_paste_and_expose_readable_content", |ctx| { @@ -188,3 +192,5 @@ fn init_git_repository(path: &Path) { .expect("run git init"); assert!(status.success(), "git init should succeed"); } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("rpc_workspace_checkpoints", 4); diff --git a/rust/tests/e2e/session.rs b/rust/tests/e2e/session.rs index 45932ffdd..e2ca76c47 100644 --- a/rust/tests/e2e/session.rs +++ b/rust/tests/e2e/session.rs @@ -21,39 +21,44 @@ use serde_json::json; use super::support::{ assert_uuid_like, assistant_message_content, collect_until_idle, event_types, - get_system_message, get_tool_names, wait_for_condition, wait_for_event, with_e2e_context, + get_system_message, get_tool_names, wait_for_condition, wait_for_event, }; #[tokio::test] async fn shouldcreateanddisconnectsessions() { - with_e2e_context("session", "shouldcreateanddisconnectsessions", |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let client = ctx.start_client().await; - let session = client - .create_session( - ctx.approve_all_session_config() - .with_model("claude-sonnet-4.5"), - ) - .await - .expect("create session"); + super::support::with_shared_e2e_context( + &E2E, + "session", + "shouldcreateanddisconnectsessions", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let client = ctx.start_client().await; + let session = client + .create_session( + ctx.approve_all_session_config() + .with_model("claude-sonnet-4.5"), + ) + .await + .expect("create session"); - assert_uuid_like(session.id()); - let messages = session.get_events().await.expect("get messages"); - assert!(!messages.is_empty(), "expected initial session events"); - let start = messages[0] - .typed_data::() - .expect("session.start data"); - assert_eq!(start.session_id, session.id().clone()); + assert_uuid_like(session.id()); + let messages = session.get_events().await.expect("get messages"); + assert!(!messages.is_empty(), "expected initial session events"); + let start = messages[0] + .typed_data::() + .expect("session.start data"); + assert_eq!(start.session_id, session.id().clone()); - session.disconnect().await.expect("disconnect session"); - assert!( - session.get_events().await.is_err(), - "disconnected session should no longer serve message history" - ); - client.stop().await.expect("stop client"); - }) - }) + session.disconnect().await.expect("disconnect session"); + assert!( + session.get_events().await.is_err(), + "disconnected session should no longer serve message history" + ); + client.stop().await.expect("stop client"); + }) + }, + ) .await; } @@ -88,39 +93,45 @@ async fn disposeasync_from_handler_does_not_deadlock() { #[tokio::test] async fn should_have_stateful_conversation() { - with_e2e_context("session", "should_have_stateful_conversation", |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let client = ctx.start_client().await; - let session = client - .create_session(ctx.approve_all_session_config()) - .await - .expect("create session"); + super::support::with_shared_e2e_context( + &E2E, + "session", + "should_have_stateful_conversation", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let client = ctx.start_client().await; + let session = client + .create_session(ctx.approve_all_session_config()) + .await + .expect("create session"); - let first = session - .send_and_wait("What is 1+1?") - .await - .expect("first send") - .expect("first assistant message"); - assert!(assistant_message_content(&first).contains('2')); + let first = session + .send_and_wait("What is 1+1?") + .await + .expect("first send") + .expect("first assistant message"); + assert!(assistant_message_content(&first).contains('2')); - let second = session - .send_and_wait("Now if you double that, what do you get?") - .await - .expect("second send") - .expect("second assistant message"); - assert!(assistant_message_content(&second).contains('4')); + let second = session + .send_and_wait("Now if you double that, what do you get?") + .await + .expect("second send") + .expect("second assistant message"); + assert!(assistant_message_content(&second).contains('4')); - session.disconnect().await.expect("disconnect session"); - client.stop().await.expect("stop client"); - }) - }) + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) .await; } #[tokio::test] async fn should_create_a_session_with_appended_systemmessage_config() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "session", "should_create_a_session_with_appended_systemmessage_config", |ctx| { @@ -164,7 +175,8 @@ async fn should_create_a_session_with_appended_systemmessage_config() { #[tokio::test] async fn should_create_a_session_with_replaced_systemmessage_config() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "session", "should_create_a_session_with_replaced_systemmessage_config", |ctx| { @@ -206,7 +218,8 @@ async fn should_create_a_session_with_replaced_systemmessage_config() { #[tokio::test] async fn should_create_a_session_with_customized_systemmessage_config() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "session", "should_create_a_session_with_customized_systemmessage_config", |ctx| { @@ -260,7 +273,8 @@ async fn should_create_a_session_with_customized_systemmessage_config() { #[tokio::test] async fn should_create_a_session_with_availabletools() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "session", "should_create_a_session_with_availabletools", |ctx| { @@ -296,7 +310,8 @@ async fn should_create_a_session_with_availabletools() { #[tokio::test] async fn should_create_a_session_with_excludedtools() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "session", "should_create_a_session_with_excludedtools", |ctx| { @@ -332,7 +347,8 @@ async fn should_create_a_session_with_excludedtools() { #[tokio::test] async fn should_create_a_session_with_defaultagent_excludedtools() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "session", "should_create_a_session_with_defaultagent_excludedtools", |ctx| { @@ -371,37 +387,43 @@ async fn should_create_a_session_with_defaultagent_excludedtools() { #[tokio::test] async fn should_create_session_with_custom_tool() { - with_e2e_context("session", "should_create_session_with_custom_tool", |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let client = ctx.start_client().await; - let session = client - .create_session( - SessionConfig::default() - .with_github_token(super::support::DEFAULT_TEST_TOKEN) - .with_permission_handler(Arc::new(ApproveAllHandler)) - .with_tools(vec![secret_number_tool()]), - ) - .await - .expect("create session"); + super::support::with_shared_e2e_context( + &E2E, + "session", + "should_create_session_with_custom_tool", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let client = ctx.start_client().await; + let session = client + .create_session( + SessionConfig::default() + .with_github_token(super::support::DEFAULT_TEST_TOKEN) + .with_permission_handler(Arc::new(ApproveAllHandler)) + .with_tools(vec![secret_number_tool()]), + ) + .await + .expect("create session"); - let answer = session - .send_and_wait("What is the secret number for key ALPHA?") - .await - .expect("send") - .expect("assistant message"); - assert!(assistant_message_content(&answer).contains("54321")); + let answer = session + .send_and_wait("What is the secret number for key ALPHA?") + .await + .expect("send") + .expect("assistant message"); + assert!(assistant_message_content(&answer).contains("54321")); - session.disconnect().await.expect("disconnect session"); - client.stop().await.expect("stop client"); - }) - }) + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) .await; } #[tokio::test] async fn should_throw_error_when_resuming_non_existent_session() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "session", "should_throw_error_when_resuming_non_existent_session", |ctx| { @@ -425,7 +447,7 @@ async fn should_throw_error_when_resuming_non_existent_session() { #[tokio::test] async fn should_abort_a_session() { - with_e2e_context("session", "should_abort_a_session", |ctx| { + super::support::with_shared_e2e_context(&E2E, "session", "should_abort_a_session", |ctx| { Box::pin(async move { ctx.set_default_copilot_user(); let client = ctx.start_client().await; @@ -479,7 +501,8 @@ async fn should_abort_a_session() { #[tokio::test] async fn should_resume_a_session_using_the_same_client() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "session", "should_resume_a_session_using_the_same_client", |ctx| { @@ -535,7 +558,7 @@ async fn should_resume_a_session_using_the_same_client() { #[tokio::test] async fn should_resume_a_session_using_a_new_client() { - with_e2e_context( + super::support::with_dedicated_e2e_context( "session", "should_resume_a_session_using_a_new_client", |ctx| { @@ -607,7 +630,7 @@ async fn should_resume_a_session_using_a_new_client() { #[tokio::test] async fn resumes_a_persisted_session_from_a_new_client_when_an_mcp_oauth_handler_is_configured() { - with_e2e_context( + super::support::with_dedicated_e2e_context( "session", "resumes_a_persisted_session_from_a_new_client_when_an_mcp_oauth_handler_is_configured", |ctx| { @@ -661,38 +684,44 @@ async fn resumes_a_persisted_session_from_a_new_client_when_an_mcp_oauth_handler #[tokio::test] async fn should_receive_session_events() { - with_e2e_context("session", "should_receive_session_events", |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let client = ctx.start_client().await; - let session = client - .create_session(ctx.approve_all_session_config()) - .await - .expect("create session"); + super::support::with_shared_e2e_context( + &E2E, + "session", + "should_receive_session_events", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let client = ctx.start_client().await; + let session = client + .create_session(ctx.approve_all_session_config()) + .await + .expect("create session"); - let events = session.subscribe(); - let answer = session - .send_and_wait("What is 100+200?") - .await - .expect("send") - .expect("assistant message"); - assert!(assistant_message_content(&answer).contains("300")); - let observed = collect_until_idle(events).await; - let types = event_types(&observed); - assert!(types.contains(&"user.message")); - assert!(types.contains(&"assistant.message")); - assert!(types.contains(&"session.idle")); + let events = session.subscribe(); + let answer = session + .send_and_wait("What is 100+200?") + .await + .expect("send") + .expect("assistant message"); + assert!(assistant_message_content(&answer).contains("300")); + let observed = collect_until_idle(events).await; + let types = event_types(&observed); + assert!(types.contains(&"user.message")); + assert!(types.contains(&"assistant.message")); + assert!(types.contains(&"session.idle")); - session.disconnect().await.expect("disconnect session"); - client.stop().await.expect("stop client"); - }) - }) + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) .await; } #[tokio::test] async fn send_returns_immediately_while_events_stream_in_background() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "session", "send_returns_immediately_while_events_stream_in_background", |ctx| { @@ -731,7 +760,8 @@ async fn send_returns_immediately_while_events_stream_in_background() { #[tokio::test] async fn sendandwait_blocks_until_session_idle_and_returns_final_assistant_message() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "session", "sendandwait_blocks_until_session_idle_and_returns_final_assistant_message", |ctx| { @@ -767,127 +797,143 @@ async fn sendandwait_blocks_until_session_idle_and_returns_final_assistant_messa #[tokio::test] async fn should_list_sessions_with_context() { - with_e2e_context("session", "should_list_sessions_with_context", |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let client = ctx.start_client().await; - let session = client - .create_session(ctx.approve_all_session_config()) - .await - .expect("create session"); - let session_id = session.id().clone(); - - session.send_and_wait("Say OK.").await.expect("send"); - wait_for_condition("session to appear in list", || { - let client = client.clone(); - let session_id = session_id.clone(); - async move { - client.list_sessions(None).await.is_ok_and(|sessions| { - sessions - .iter() - .any(|session| session.session_id == session_id) - }) - } - }) - .await; + super::support::with_shared_e2e_context( + &E2E, + "session", + "should_list_sessions_with_context", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let client = ctx.start_client().await; + let session = client + .create_session(ctx.approve_all_session_config()) + .await + .expect("create session"); + let session_id = session.id().clone(); - let all_sessions = client.list_sessions(None).await.expect("list sessions"); - assert!(!all_sessions.is_empty()); + session.send_and_wait("Say OK.").await.expect("send"); + wait_for_condition("session to appear in list", || { + let client = client.clone(); + let session_id = session_id.clone(); + async move { + client.list_sessions(None).await.is_ok_and(|sessions| { + sessions + .iter() + .any(|session| session.session_id == session_id) + }) + } + }) + .await; - session.disconnect().await.expect("disconnect session"); - client.stop().await.expect("stop client"); - }) - }) + let all_sessions = client.list_sessions(None).await.expect("list sessions"); + assert!(!all_sessions.is_empty()); + + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) .await; } #[tokio::test] async fn should_get_session_metadata_by_id() { - with_e2e_context("session", "should_get_session_metadata_by_id", |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let client = ctx.start_client().await; - let session = client - .create_session(ctx.approve_all_session_config()) - .await - .expect("create session"); - let session_id = session.id().clone(); + super::support::with_shared_e2e_context( + &E2E, + "session", + "should_get_session_metadata_by_id", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let client = ctx.start_client().await; + let session = client + .create_session(ctx.approve_all_session_config()) + .await + .expect("create session"); + let session_id = session.id().clone(); + + session.send_and_wait("Say hello").await.expect("send"); + wait_for_condition("session metadata to persist", || { + let client = client.clone(); + let session_id = session_id.clone(); + async move { + client + .get_session_metadata(&session_id) + .await + .is_ok_and(|metadata| metadata.is_some()) + } + }) + .await; - session.send_and_wait("Say hello").await.expect("send"); - wait_for_condition("session metadata to persist", || { - let client = client.clone(); - let session_id = session_id.clone(); - async move { + let metadata = client + .get_session_metadata(&session_id) + .await + .expect("get metadata") + .expect("session metadata"); + assert_eq!(metadata.session_id, session_id); + assert!(!metadata.start_time.is_empty()); + assert!(!metadata.modified_time.is_empty()); + assert!( client - .get_session_metadata(&session_id) + .get_session_metadata(&github_copilot_sdk::SessionId::new( + "non-existent-session-id" + )) .await - .is_ok_and(|metadata| metadata.is_some()) - } - }) - .await; - - let metadata = client - .get_session_metadata(&session_id) - .await - .expect("get metadata") - .expect("session metadata"); - assert_eq!(metadata.session_id, session_id); - assert!(!metadata.start_time.is_empty()); - assert!(!metadata.modified_time.is_empty()); - assert!( - client - .get_session_metadata(&github_copilot_sdk::SessionId::new( - "non-existent-session-id" - )) - .await - .expect("get missing metadata") - .is_none() - ); + .expect("get missing metadata") + .is_none() + ); - session.disconnect().await.expect("disconnect session"); - client.stop().await.expect("stop client"); - }) - }) + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) .await; } #[tokio::test] async fn sendandwait_throws_on_timeout() { - with_e2e_context("session", "sendandwait_throws_on_timeout", |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let client = ctx.start_client().await; - let session = client - .create_session(ctx.approve_all_session_config()) - .await - .expect("create session"); - let idle = tokio::spawn(wait_for_event( - session.subscribe(), - "session.idle after timeout abort", - |event| event.parsed_type() == SessionEventType::SessionIdle, - )); - - let error = session - .send_and_wait( - MessageOptions::new("Run 'sleep 2 && echo done'") - .with_wait_timeout(Duration::from_millis(100)), - ) - .await - .expect_err("send_and_wait should time out"); - assert!(error.to_string().contains("timed out")); + super::support::with_shared_e2e_context( + &E2E, + "session", + "sendandwait_throws_on_timeout", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let client = ctx.start_client().await; + let session = client + .create_session(ctx.approve_all_session_config()) + .await + .expect("create session"); + let idle = tokio::spawn(wait_for_event( + session.subscribe(), + "session.idle after timeout abort", + |event| event.parsed_type() == SessionEventType::SessionIdle, + )); + + let error = session + .send_and_wait( + MessageOptions::new("Run 'sleep 2 && echo done'") + .with_wait_timeout(Duration::from_millis(100)), + ) + .await + .expect_err("send_and_wait should time out"); + assert!(error.to_string().contains("timed out")); - session.abort().await.expect("abort session"); - idle.await.expect("idle task"); - session.disconnect().await.expect("disconnect session"); - client.stop().await.expect("stop client"); - }) - }) + session.abort().await.expect("abort session"); + idle.await.expect("idle task"); + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) .await; } #[tokio::test] async fn should_create_session_with_custom_config_dir() { - with_e2e_context( + super::support::with_dedicated_group_e2e_context( + &E2E, "session", "should_create_session_with_custom_config_dir", |ctx| { @@ -921,183 +967,198 @@ async fn should_create_session_with_custom_config_dir() { #[tokio::test] async fn should_set_model_on_existing_session() { - with_e2e_context("session", "should_set_model_on_existing_session", |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let client = ctx.start_client().await; - let session = client - .create_session(ctx.approve_all_session_config()) - .await - .expect("create session"); - let model_changed = tokio::spawn(wait_for_event( - session.subscribe(), - "session.model_change", - |event| event.parsed_type() == SessionEventType::SessionModelChange, - )); - - session.set_model("gpt-4.1", None).await.expect("set model"); - let event = model_changed.await.expect("model change task"); - let data = event - .typed_data::() - .expect("session.model_change data"); - assert_eq!(data.new_model, "gpt-4.1"); - - session.disconnect().await.expect("disconnect session"); - client.stop().await.expect("stop client"); - }) - }) + super::support::with_shared_e2e_context( + &E2E, + "session", + "should_set_model_on_existing_session", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let client = ctx.start_client().await; + let session = client + .create_session(ctx.approve_all_session_config()) + .await + .expect("create session"); + let model_changed = tokio::spawn(wait_for_event( + session.subscribe(), + "session.model_change", + |event| event.parsed_type() == SessionEventType::SessionModelChange, + )); + + session.set_model("gpt-4.1", None).await.expect("set model"); + let event = model_changed.await.expect("model change task"); + let data = event + .typed_data::() + .expect("session.model_change data"); + assert_eq!(data.new_model, "gpt-4.1"); + + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) .await; } #[tokio::test] async fn should_set_model_with_reasoningeffort() { - with_e2e_context("session", "should_set_model_with_reasoningeffort", |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let client = ctx.start_client().await; - let session = client - .create_session(ctx.approve_all_session_config()) - .await - .expect("create session"); - let model_changed = tokio::spawn(wait_for_event( - session.subscribe(), - "session.model_change with reasoning effort", - |event| event.parsed_type() == SessionEventType::SessionModelChange, - )); + super::support::with_dedicated_group_e2e_context( + &E2E, + "session", + "should_set_model_with_reasoningeffort", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let client = ctx.start_client().await; + let session = client + .create_session(ctx.approve_all_session_config()) + .await + .expect("create session"); + let model_changed = tokio::spawn(wait_for_event( + session.subscribe(), + "session.model_change with reasoning effort", + |event| event.parsed_type() == SessionEventType::SessionModelChange, + )); - session - .set_model( - "gpt-4.1", - Some(SetModelOptions::default().with_reasoning_effort("high")), - ) - .await - .expect("set model"); - let event = model_changed.await.expect("model change task"); - let data = event - .typed_data::() - .expect("session.model_change data"); - assert_eq!(data.new_model, "gpt-4.1"); - assert_eq!(data.reasoning_effort.as_deref(), Some("high")); + session + .set_model( + "gpt-5.4", + Some(SetModelOptions::default().with_reasoning_effort("high")), + ) + .await + .expect("set model"); + let event = model_changed.await.expect("model change task"); + let data = event + .typed_data::() + .expect("session.model_change data"); + assert_eq!(data.new_model, "gpt-5.4"); + assert_eq!(data.reasoning_effort.as_deref(), Some("high")); - session.disconnect().await.expect("disconnect session"); - client.stop().await.expect("stop client"); - }) - }) + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) .await; } #[tokio::test] async fn should_log_messages_at_various_levels() { - with_e2e_context("session", "should_log_messages_at_various_levels", |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let client = ctx.start_client().await; - let session = client - .create_session(ctx.approve_all_session_config()) - .await - .expect("create session"); - let mut events = session.subscribe(); + super::support::with_shared_e2e_context( + &E2E, + "session", + "should_log_messages_at_various_levels", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let client = ctx.start_client().await; + let session = client + .create_session(ctx.approve_all_session_config()) + .await + .expect("create session"); + let mut events = session.subscribe(); - session.log("Info message", None).await.expect("info log"); - session - .log( - "Warning message", - Some(LogOptions::default().with_level(SessionLogLevel::Warning)), - ) - .await - .expect("warning log"); - session - .log( - "Error message", - Some(LogOptions::default().with_level(SessionLogLevel::Error)), - ) - .await - .expect("error log"); - session - .log( - "Ephemeral message", - Some(LogOptions::default().with_ephemeral(true)), - ) - .await - .expect("ephemeral log"); - - let mut observed = Vec::new(); - tokio::time::timeout(Duration::from_secs(10), async { - while observed.len() < 4 { - let event = events.recv().await.expect("session event"); - if matches!( - event.parsed_type(), - SessionEventType::SessionInfo - | SessionEventType::SessionWarning - | SessionEventType::SessionError - ) { - observed.push(event); + session.log("Info message", None).await.expect("info log"); + session + .log( + "Warning message", + Some(LogOptions::default().with_level(SessionLogLevel::Warning)), + ) + .await + .expect("warning log"); + session + .log( + "Error message", + Some(LogOptions::default().with_level(SessionLogLevel::Error)), + ) + .await + .expect("error log"); + session + .log( + "Ephemeral message", + Some(LogOptions::default().with_ephemeral(true)), + ) + .await + .expect("ephemeral log"); + + let mut observed = Vec::new(); + tokio::time::timeout(Duration::from_secs(10), async { + while observed.len() < 4 { + let event = events.recv().await.expect("session event"); + if matches!( + event.parsed_type(), + SessionEventType::SessionInfo + | SessionEventType::SessionWarning + | SessionEventType::SessionError + ) { + observed.push(event); + } } - } - }) - .await - .expect("log events"); - - let info = observed - .iter() - .find(|event| { - event - .typed_data::() - .is_some_and(|data| data.message == "Info message") }) - .expect("info message"); - assert_eq!( - info.typed_data::() - .expect("info data") - .info_type, - "notification" - ); - let warning = observed - .iter() - .find(|event| { - event + .await + .expect("log events"); + + let info = observed + .iter() + .find(|event| { + event + .typed_data::() + .is_some_and(|data| data.message == "Info message") + }) + .expect("info message"); + assert_eq!( + info.typed_data::() + .expect("info data") + .info_type, + "notification" + ); + let warning = observed + .iter() + .find(|event| { + event + .typed_data::() + .is_some_and(|data| data.message == "Warning message") + }) + .expect("warning message"); + assert_eq!( + warning .typed_data::() - .is_some_and(|data| data.message == "Warning message") - }) - .expect("warning message"); - assert_eq!( - warning - .typed_data::() - .expect("warning data") - .warning_type, - "notification" - ); - let error = observed - .iter() - .find(|event| { - event + .expect("warning data") + .warning_type, + "notification" + ); + let error = observed + .iter() + .find(|event| { + event + .typed_data::() + .is_some_and(|data| data.message == "Error message") + }) + .expect("error message"); + assert_eq!( + error .typed_data::() - .is_some_and(|data| data.message == "Error message") - }) - .expect("error message"); - assert_eq!( - error - .typed_data::() - .expect("error data") - .error_type, - "notification" - ); - assert!(observed.iter().any(|event| { - event - .typed_data::() - .is_some_and(|data| data.message == "Ephemeral message") - })); + .expect("error data") + .error_type, + "notification" + ); + assert!(observed.iter().any(|event| { + event + .typed_data::() + .is_some_and(|data| data.message == "Ephemeral message") + })); - session.disconnect().await.expect("disconnect session"); - client.stop().await.expect("stop client"); - }) - }) + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) .await; } #[tokio::test] async fn should_accept_blob_attachments() { - with_e2e_context("session", "should_accept_blob_attachments", |ctx| { + super::support::with_shared_e2e_context(&E2E, "session", "should_accept_blob_attachments", |ctx| { Box::pin(async move { ctx.set_default_copilot_user(); let png_base64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="; @@ -1139,197 +1200,213 @@ async fn should_accept_blob_attachments() { #[tokio::test] async fn should_send_with_file_attachment() { - with_e2e_context("session", "should_send_with_file_attachment", |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let file_path = ctx.work_dir().join("attached-file.txt"); - std::fs::write(&file_path, "FILE_ATTACHMENT_SENTINEL").expect("write attached file"); - let client = ctx.start_client().await; - let session = client - .create_session(ctx.approve_all_session_config()) - .await - .expect("create session"); + super::support::with_shared_e2e_context( + &E2E, + "session", + "should_send_with_file_attachment", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let file_path = ctx.work_dir().join("attached-file.txt"); + std::fs::write(&file_path, "FILE_ATTACHMENT_SENTINEL") + .expect("write attached file"); + let client = ctx.start_client().await; + let session = client + .create_session(ctx.approve_all_session_config()) + .await + .expect("create session"); - session - .send_and_wait( - MessageOptions::new("Read the attached file and reply with its contents.") - .with_attachments(vec![Attachment::File { - path: file_path.clone(), - display_name: Some("attached-file.txt".to_string()), - line_range: Some(AttachmentLineRange { start: 1, end: 1 }), - }]), - ) - .await - .expect("send"); + session + .send_and_wait( + MessageOptions::new("Read the attached file and reply with its contents.") + .with_attachments(vec![Attachment::File { + path: file_path.clone(), + display_name: Some("attached-file.txt".to_string()), + line_range: Some(AttachmentLineRange { start: 1, end: 1 }), + }]), + ) + .await + .expect("send"); - let user = latest_user_message(&session).await; - let attachments = user - .typed_data::() - .expect("user message data") - .attachments - .expect("attachments"); - assert_eq!(attachments.len(), 1); - assert_eq!( - attachments[0] - .get("displayName") - .and_then(serde_json::Value::as_str), - Some("attached-file.txt") - ); - assert_eq!( - attachments[0] - .get("path") - .and_then(serde_json::Value::as_str), - Some(file_path.to_string_lossy().as_ref()) - ); - assert_eq!( - attachments[0] - .get("lineRange") - .and_then(|value| value.get("start")) - .and_then(serde_json::Value::as_u64), - Some(1) - ); + let user = latest_user_message(&session).await; + let attachments = user + .typed_data::() + .expect("user message data") + .attachments + .expect("attachments"); + assert_eq!(attachments.len(), 1); + assert_eq!( + attachments[0] + .get("displayName") + .and_then(serde_json::Value::as_str), + Some("attached-file.txt") + ); + assert_eq!( + attachments[0] + .get("path") + .and_then(serde_json::Value::as_str), + Some(file_path.to_string_lossy().as_ref()) + ); + assert_eq!( + attachments[0] + .get("lineRange") + .and_then(|value| value.get("start")) + .and_then(serde_json::Value::as_u64), + Some(1) + ); - session.disconnect().await.expect("disconnect session"); - client.stop().await.expect("stop client"); - }) - }) + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) .await; } #[tokio::test] async fn should_send_with_directory_attachment() { - with_e2e_context("session", "should_send_with_directory_attachment", |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let directory_path = ctx.work_dir().join("attached-directory"); - std::fs::create_dir(&directory_path).expect("create attached directory"); - std::fs::write( - directory_path.join("readme.txt"), - "DIRECTORY_ATTACHMENT_SENTINEL", - ) - .expect("write attached directory file"); - let client = ctx.start_client().await; - let session = client - .create_session(ctx.approve_all_session_config()) - .await - .expect("create session"); - - session - .send_and_wait( - MessageOptions::new("List the attached directory.").with_attachments(vec![ - Attachment::Directory { - path: directory_path.clone(), - display_name: Some("attached-directory".to_string()), - }, - ]), + super::support::with_shared_e2e_context( + &E2E, + "session", + "should_send_with_directory_attachment", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let directory_path = ctx.work_dir().join("attached-directory"); + std::fs::create_dir(&directory_path).expect("create attached directory"); + std::fs::write( + directory_path.join("readme.txt"), + "DIRECTORY_ATTACHMENT_SENTINEL", ) - .await - .expect("send"); + .expect("write attached directory file"); + let client = ctx.start_client().await; + let session = client + .create_session(ctx.approve_all_session_config()) + .await + .expect("create session"); - let user = latest_user_message(&session).await; - let attachments = user - .typed_data::() - .expect("user message data") - .attachments - .expect("attachments"); - assert_eq!(attachments.len(), 1); - assert_eq!( - attachments[0] - .get("displayName") - .and_then(serde_json::Value::as_str), - Some("attached-directory") - ); - assert_eq!( - attachments[0] - .get("path") - .and_then(serde_json::Value::as_str), - Some(directory_path.to_string_lossy().as_ref()) - ); + session + .send_and_wait( + MessageOptions::new("List the attached directory.").with_attachments(vec![ + Attachment::Directory { + path: directory_path.clone(), + display_name: Some("attached-directory".to_string()), + }, + ]), + ) + .await + .expect("send"); - session.disconnect().await.expect("disconnect session"); - client.stop().await.expect("stop client"); - }) - }) + let user = latest_user_message(&session).await; + let attachments = user + .typed_data::() + .expect("user message data") + .attachments + .expect("attachments"); + assert_eq!(attachments.len(), 1); + assert_eq!( + attachments[0] + .get("displayName") + .and_then(serde_json::Value::as_str), + Some("attached-directory") + ); + assert_eq!( + attachments[0] + .get("path") + .and_then(serde_json::Value::as_str), + Some(directory_path.to_string_lossy().as_ref()) + ); + + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) .await; } #[tokio::test] async fn should_send_with_selection_attachment() { - with_e2e_context("session", "should_send_with_selection_attachment", |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let file_path = std::path::PathBuf::from("selected-file.cs"); - let absolute_file_path = ctx.work_dir().join(&file_path); - std::fs::write( - &absolute_file_path, - "class C { string Value = \"SELECTION_SENTINEL\"; }", - ) - .expect("write selection file"); - let client = ctx.start_client().await; - let session = client - .create_session(ctx.approve_all_session_config()) - .await - .expect("create session"); + super::support::with_shared_e2e_context( + &E2E, + "session", + "should_send_with_selection_attachment", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let file_path = std::path::PathBuf::from("selected-file.cs"); + let absolute_file_path = ctx.work_dir().join(&file_path); + std::fs::write( + &absolute_file_path, + "class C { string Value = \"SELECTION_SENTINEL\"; }", + ) + .expect("write selection file"); + let client = ctx.start_client().await; + let session = client + .create_session(ctx.approve_all_session_config()) + .await + .expect("create session"); - session - .send_and_wait( - MessageOptions::new("Summarize the selected code.").with_attachments(vec![ - Attachment::Selection { - file_path: file_path.clone(), - text: "string Value = \"SELECTION_SENTINEL\";".to_string(), - display_name: Some("selected-file.cs".to_string()), - selection: AttachmentSelectionRange { - start: AttachmentSelectionPosition { - line: 1, - character: 10, - }, - end: AttachmentSelectionPosition { - line: 1, - character: 45, + session + .send_and_wait( + MessageOptions::new("Summarize the selected code.").with_attachments(vec![ + Attachment::Selection { + file_path: file_path.clone(), + text: "string Value = \"SELECTION_SENTINEL\";".to_string(), + display_name: Some("selected-file.cs".to_string()), + selection: AttachmentSelectionRange { + start: AttachmentSelectionPosition { + line: 1, + character: 10, + }, + end: AttachmentSelectionPosition { + line: 1, + character: 45, + }, }, }, - }, - ]), - ) - .await - .expect("send"); + ]), + ) + .await + .expect("send"); - let user = latest_user_message(&session).await; - let attachment = user - .typed_data::() - .expect("user message data") - .attachments - .expect("attachments") - .into_iter() - .next() - .expect("attachment"); - assert_eq!( - attachment - .get("displayName") - .and_then(serde_json::Value::as_str), - Some("selected-file.cs") - ); - assert_eq!( - attachment - .get("filePath") - .and_then(serde_json::Value::as_str), - Some(file_path.to_string_lossy().as_ref()) - ); - assert_eq!( - attachment.get("text").and_then(serde_json::Value::as_str), - Some("string Value = \"SELECTION_SENTINEL\";") - ); + let user = latest_user_message(&session).await; + let attachment = user + .typed_data::() + .expect("user message data") + .attachments + .expect("attachments") + .into_iter() + .next() + .expect("attachment"); + assert_eq!( + attachment + .get("displayName") + .and_then(serde_json::Value::as_str), + Some("selected-file.cs") + ); + assert_eq!( + attachment + .get("filePath") + .and_then(serde_json::Value::as_str), + Some(file_path.to_string_lossy().as_ref()) + ); + assert_eq!( + attachment.get("text").and_then(serde_json::Value::as_str), + Some("string Value = \"SELECTION_SENTINEL\";") + ); - session.disconnect().await.expect("disconnect session"); - client.stop().await.expect("stop client"); - }) - }) + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) .await; } #[tokio::test] async fn should_send_with_github_reference_attachment() { - with_e2e_context( + super::support::with_shared_e2e_context(&E2E, "session", "should_send_with_github_reference_attachment", |ctx| { @@ -1394,101 +1471,114 @@ async fn should_send_with_github_reference_attachment() { #[tokio::test] async fn should_send_with_custom_requestheaders() { - with_e2e_context("session", "should_send_with_custom_requestheaders", |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let client = ctx.start_client().await; - let session = client - .create_session(ctx.approve_all_session_config()) - .await - .expect("create session"); - let mut headers = HashMap::new(); - headers.insert( - "x-copilot-sdk-test-header".to_string(), - "csharp-request-headers".to_string(), - ); + super::support::with_shared_e2e_context( + &E2E, + "session", + "should_send_with_custom_requestheaders", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let client = ctx.start_client().await; + let session = client + .create_session(ctx.approve_all_session_config()) + .await + .expect("create session"); + let mut headers = HashMap::new(); + headers.insert( + "x-copilot-sdk-test-header".to_string(), + "csharp-request-headers".to_string(), + ); - session - .send_and_wait(MessageOptions::new("What is 1+1?").with_request_headers(headers)) - .await - .expect("send"); + session + .send_and_wait( + MessageOptions::new("What is 1+1?").with_request_headers(headers), + ) + .await + .expect("send"); - let exchanges = ctx.exchanges(); - assert!(!exchanges.is_empty(), "expected captured CAPI exchange"); - let request_headers = exchanges - .last() - .and_then(|exchange| exchange.get("requestHeaders")) - .and_then(serde_json::Value::as_object) - .expect("request headers"); - let header = request_headers - .iter() - .find(|(key, _)| key.eq_ignore_ascii_case("x-copilot-sdk-test-header")) - .and_then(|(_, value)| value.as_str()) - .expect("test header"); - assert!(header.contains("csharp-request-headers")); + let exchanges = ctx.exchanges(); + assert!(!exchanges.is_empty(), "expected captured CAPI exchange"); + let request_headers = exchanges + .last() + .and_then(|exchange| exchange.get("requestHeaders")) + .and_then(serde_json::Value::as_object) + .expect("request headers"); + let header = request_headers + .iter() + .find(|(key, _)| key.eq_ignore_ascii_case("x-copilot-sdk-test-header")) + .and_then(|(_, value)| value.as_str()) + .expect("test header"); + assert!(header.contains("csharp-request-headers")); - session.disconnect().await.expect("disconnect session"); - client.stop().await.expect("stop client"); - }) - }) + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) .await; } #[tokio::test] async fn should_send_with_mode_property() { - with_e2e_context("session", "should_send_with_mode_property", |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let client = ctx.start_client().await; - let session = client - .create_session(ctx.approve_all_session_config()) - .await - .expect("create session"); + super::support::with_shared_e2e_context( + &E2E, + "session", + "should_send_with_mode_property", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let client = ctx.start_client().await; + let session = client + .create_session(ctx.approve_all_session_config()) + .await + .expect("create session"); - session - .client() - .call( - "session.send", - Some(json!({ - "sessionId": session.id().as_str(), - "prompt": "Say mode ok.", - "mode": "plan", - })), - ) - .await - .expect("send with agent mode"); - wait_for_event(session.subscribe(), "session.idle", |event| { - event.parsed_type() == SessionEventType::SessionIdle - }) - .await; + session + .client() + .call( + "session.send", + Some(json!({ + "sessionId": session.id().as_str(), + "prompt": "Say mode ok.", + "mode": "plan", + })), + ) + .await + .expect("send with agent mode"); + wait_for_event(session.subscribe(), "session.idle", |event| { + event.parsed_type() == SessionEventType::SessionIdle + }) + .await; - let user_message = session - .get_events() - .await - .expect("get messages") - .into_iter() - .rev() - .find(|event| event.parsed_type() == SessionEventType::UserMessage) - .expect("user.message"); - let data = user_message - .typed_data::() - .expect("user.message data"); - assert_eq!(data.content, "Say mode ok."); - assert!( - data.agent_mode.is_none(), - "runtime should accept but not echo per-message mode" - ); + let user_message = session + .get_events() + .await + .expect("get messages") + .into_iter() + .rev() + .find(|event| event.parsed_type() == SessionEventType::UserMessage) + .expect("user.message"); + let data = user_message + .typed_data::() + .expect("user.message data"); + assert_eq!(data.content, "Say mode ok."); + assert!( + data.agent_mode.is_none(), + "runtime should accept but not echo per-message mode" + ); - session.disconnect().await.expect("disconnect session"); - client.stop().await.expect("stop client"); - }) - }) + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) .await; } #[tokio::test] async fn should_create_session_with_custom_provider() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "session", "should_create_session_with_custom_provider", |ctx| { @@ -1515,7 +1605,8 @@ async fn should_create_session_with_custom_provider() { #[tokio::test] async fn should_create_session_with_azure_provider() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "session", "should_create_session_with_azure_provider", |ctx| { @@ -1545,7 +1636,8 @@ async fn should_create_session_with_azure_provider() { #[tokio::test] async fn should_resume_session_with_custom_provider() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "session", "should_resume_session_with_custom_provider", |ctx| { @@ -1659,3 +1751,5 @@ fn secret_number_tool() -> Tool { })) .with_handler(Arc::new(SecretNumberTool)) } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("session", 30); diff --git a/rust/tests/e2e/session_config.rs b/rust/tests/e2e/session_config.rs index dd498e376..c3f6b57ae 100644 --- a/rust/tests/e2e/session_config.rs +++ b/rust/tests/e2e/session_config.rs @@ -15,9 +15,10 @@ use http::{HeaderMap, HeaderValue}; use parking_lot::Mutex; use serde_json::{Value, json}; -use super::support::{ - DEFAULT_TEST_TOKEN, E2eContext, with_e2e_context, with_e2e_context_no_snapshot, -}; +use super::support::{DEFAULT_TEST_TOKEN, E2eContext, with_e2e_context_no_snapshot}; + +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("session_config", 4); const SYNTHETIC_TEXT: &str = "OK from the synthetic stream."; const CITATION_PROMPT: &str = "Summarize the attached PDF with citations enabled."; @@ -90,7 +91,8 @@ fn task_agent_types(exchange: &Value) -> Vec { #[tokio::test] async fn should_apply_session_limits_on_create() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "session_config", "should_apply_session_limits_on_create", |ctx| { @@ -123,7 +125,8 @@ async fn should_apply_session_limits_on_create() { #[tokio::test] async fn should_apply_session_limits_on_resume() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "session_config", "should_apply_session_limits_on_resume", |ctx| { @@ -169,7 +172,8 @@ async fn should_apply_session_limits_on_resume() { #[tokio::test] async fn should_apply_excluded_built_in_agents_on_create() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "session_config", "should_apply_excluded_built_in_agents_on_create", |ctx| { @@ -222,7 +226,8 @@ async fn should_apply_excluded_built_in_agents_on_create() { #[tokio::test] async fn should_apply_excluded_built_in_agents_on_resume() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "session_config", "should_apply_excluded_built_in_agents_on_resume", |ctx| { diff --git a/rust/tests/e2e/session_fs_sqlite.rs b/rust/tests/e2e/session_fs_sqlite.rs index 0b99d951b..8ba712bb4 100644 --- a/rust/tests/e2e/session_fs_sqlite.rs +++ b/rust/tests/e2e/session_fs_sqlite.rs @@ -4,13 +4,14 @@ use std::sync::{Arc, Mutex}; use async_trait::async_trait; use github_copilot_sdk::session_fs::{FsError, FsErrorKind}; use github_copilot_sdk::{ - Client, DirEntry, DirEntryKind, FileInfo, SessionConfig, SessionFsCapabilities, - SessionFsConfig, SessionFsConventions, SessionFsProvider, SessionFsSqliteProvider, - SessionFsSqliteQueryResult, SessionFsSqliteQueryType, + DirEntry, DirEntryKind, FileInfo, SessionConfig, SessionFsCapabilities, SessionFsConfig, + SessionFsConventions, SessionFsProvider, SessionFsSqliteProvider, SessionFsSqliteQueryResult, + SessionFsSqliteQueryType, SessionFsSqliteTransactionError, SessionFsSqliteTransactionStatement, }; use rusqlite::Connection; -use super::support::with_e2e_context; +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::new("session_fs_sqlite", sqlite_client_options, 2); #[derive(Debug)] struct SqliteCall { @@ -219,40 +220,93 @@ impl SessionFsSqliteProvider for InMemorySqliteProvider { query: &str, _params: Option<&HashMap>, ) -> Result, FsError> { + let mut db_guard = self.db.lock().unwrap(); + let db = Self::get_or_create_db(&mut db_guard)?; + Ok(Some(Self::run_statement( + db, + query_type, + query, + &self.session_id, + &self.sqlite_calls, + )?)) + } + + async fn sqlite_transaction( + &self, + statements: &[SessionFsSqliteTransactionStatement], + ) -> Result, SessionFsSqliteTransactionError> { + let mut db_guard = self.db.lock().unwrap(); + let db = Self::get_or_create_db(&mut db_guard)?; + db.execute_batch("BEGIN IMMEDIATE") + .map_err(|e| Self::classify_sqlite_error(&e))?; + let mut results = Vec::with_capacity(statements.len()); + for statement in statements { + match Self::run_statement( + db, + statement.query_type.clone(), + &statement.query, + &self.session_id, + &self.sqlite_calls, + ) { + Ok(result) => results.push(result), + Err(e) => { + let _ = db.execute_batch("ROLLBACK"); + return Err(Self::classify_error_message(e.to_string())); + } + } + } + db.execute_batch("COMMIT") + .map_err(|e| SessionFsSqliteTransactionError::post_commit_ambiguous(e.to_string()))?; + Ok(results) + } + + async fn sqlite_exists(&self) -> Result { + Ok(self.db.lock().unwrap().is_some()) + } +} + +impl InMemorySqliteProvider { + fn classify_sqlite_error(error: &rusqlite::Error) -> SessionFsSqliteTransactionError { + Self::classify_error_message(error.to_string()) + } + + fn classify_error_message(message: String) -> SessionFsSqliteTransactionError { + if message.contains("locked") || message.contains("busy") { + SessionFsSqliteTransactionError::busy_or_locked(message) + } else { + SessionFsSqliteTransactionError::fatal(message) + } + } + + fn run_statement( + db: &Connection, + query_type: SessionFsSqliteQueryType, + query: &str, + session_id: &str, + sqlite_calls: &Arc>>, + ) -> Result { let qt_str = match query_type { SessionFsSqliteQueryType::Exec => "exec", SessionFsSqliteQueryType::Query => "query", SessionFsSqliteQueryType::Run => "run", SessionFsSqliteQueryType::Unknown => "unknown", }; - self.sqlite_calls.lock().unwrap().push(SqliteCall { - session_id: self.session_id.clone(), + sqlite_calls.lock().unwrap().push(SqliteCall { + session_id: session_id.to_string(), query_type: qt_str.to_string(), query: query.to_string(), }); - let mut db_guard = self.db.lock().unwrap(); - let db = Self::get_or_create_db(&mut db_guard)?; let trimmed = query.trim(); if trimmed.is_empty() { - return Ok(Some(SessionFsSqliteQueryResult { - columns: vec![], - rows: vec![], - rows_affected: 0, - last_insert_rowid: None, - })); + return Ok(SessionFsSqliteQueryResult::default()); } match query_type { SessionFsSqliteQueryType::Exec => { db.execute_batch(trimmed) .map_err(|e| FsError::new(FsErrorKind::Other, e))?; - Ok(Some(SessionFsSqliteQueryResult { - columns: vec![], - rows: vec![], - rows_affected: 0, - last_insert_rowid: None, - })) + Ok(SessionFsSqliteQueryResult::default()) } SessionFsSqliteQueryType::Query => { let mut stmt = db @@ -292,37 +346,28 @@ impl SessionFsSqliteProvider for InMemorySqliteProvider { } rows.push(map); } - Ok(Some(SessionFsSqliteQueryResult { + Ok(SessionFsSqliteQueryResult { columns, rows, rows_affected: 0, last_insert_rowid: None, - })) + }) } SessionFsSqliteQueryType::Run => { let affected = db .execute(trimmed, []) .map_err(|e| FsError::new(FsErrorKind::Other, e))?; let last_id = db.last_insert_rowid(); - Ok(Some(SessionFsSqliteQueryResult { + Ok(SessionFsSqliteQueryResult { columns: vec![], rows: vec![], rows_affected: affected as i64, last_insert_rowid: Some(last_id), - })) + }) } - _ => Ok(Some(SessionFsSqliteQueryResult { - columns: vec![], - rows: vec![], - rows_affected: 0, - last_insert_rowid: None, - })), + _ => Ok(SessionFsSqliteQueryResult::default()), } } - - async fn sqlite_exists(&self) -> Result { - Ok(self.db.lock().unwrap().is_some()) - } } fn session_state_path_sqlite() -> String { @@ -346,13 +391,12 @@ fn sqlite_session_fs_config() -> SessionFsConfig { .with_capabilities(SessionFsCapabilities::new().with_sqlite(true)) } -async fn start_sqlite_client(ctx: &super::support::E2eContext) -> Client { - Client::start( - ctx.client_options() - .with_session_fs(sqlite_session_fs_config()), - ) - .await - .expect("start sqlite client") +fn sqlite_client_options( + context: &super::support::E2eContext, +) -> github_copilot_sdk::ClientOptions { + context + .client_options() + .with_session_fs(sqlite_session_fs_config()) } fn sqlite_session_config( @@ -365,7 +409,8 @@ fn sqlite_session_config( #[tokio::test] async fn should_route_sql_queries_through_the_sessionfs_sqlite_handler() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "session_fs_sqlite", "should_route_sql_queries_through_the_sessionfs_sqlite_handler", |ctx| { @@ -377,7 +422,7 @@ async fn should_route_sql_queries_through_the_sessionfs_sqlite_handler() { session_id, sqlite_calls.clone(), )); - let client = start_sqlite_client(ctx).await; + let client = ctx.start_client().await; let session = client .create_session( sqlite_session_config(ctx, provider).with_session_id(session_id), @@ -435,7 +480,8 @@ async fn should_route_sql_queries_through_the_sessionfs_sqlite_handler() { #[tokio::test] async fn should_allow_subagents_to_use_sql_tool_via_inherited_sessionfs() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "session_fs_sqlite", "should_allow_subagents_to_use_sql_tool_via_inherited_sessionfs", |ctx| { @@ -445,7 +491,7 @@ async fn should_allow_subagents_to_use_sql_tool_via_inherited_sessionfs() { let sqlite_calls = Arc::new(Mutex::new(Vec::new())); let provider = Arc::new(InMemorySqliteProvider::new(session_id, sqlite_calls.clone())); let provider_ref = provider.clone(); - let client = start_sqlite_client(ctx).await; + let client = ctx.start_client().await; let session = client .create_session( sqlite_session_config(ctx, provider).with_session_id(session_id), diff --git a/rust/tests/e2e/session_lifecycle.rs b/rust/tests/e2e/session_lifecycle.rs index 24938776f..545bb4988 100644 --- a/rust/tests/e2e/session_lifecycle.rs +++ b/rust/tests/e2e/session_lifecycle.rs @@ -2,12 +2,12 @@ use github_copilot_sdk::session_events::SessionEventType; use super::support::{ assistant_message_content, collect_until_idle, event_types, wait_for_condition, - with_e2e_context, }; #[tokio::test] async fn should_list_created_sessions_after_sending_a_message() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "session_lifecycle", "should_list_created_sessions_after_sending_a_message", |ctx| { @@ -59,7 +59,8 @@ async fn should_list_created_sessions_after_sending_a_message() { #[tokio::test] async fn should_delete_session_permanently() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "session_lifecycle", "should_delete_session_permanently", |ctx| { @@ -103,7 +104,8 @@ async fn should_delete_session_permanently() { #[tokio::test] async fn should_return_events_via_getmessages_after_conversation() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "session_lifecycle", "should_return_events_via_getmessages_after_conversation", |ctx| { @@ -136,7 +138,8 @@ async fn should_return_events_via_getmessages_after_conversation() { #[tokio::test] async fn should_support_multiple_concurrent_sessions() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "session_lifecycle", "should_support_multiple_concurrent_sessions", |ctx| { @@ -180,7 +183,8 @@ async fn should_support_multiple_concurrent_sessions() { #[tokio::test] async fn should_isolate_events_between_concurrent_sessions() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "session_lifecycle", "should_isolate_events_between_concurrent_sessions", |ctx| { @@ -255,3 +259,5 @@ async fn should_isolate_events_between_concurrent_sessions() { ) .await; } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("session_lifecycle", 5); diff --git a/rust/tests/e2e/session_todos_changed.rs b/rust/tests/e2e/session_todos_changed.rs index ebace39b3..4b6245206 100644 --- a/rust/tests/e2e/session_todos_changed.rs +++ b/rust/tests/e2e/session_todos_changed.rs @@ -1,6 +1,6 @@ use github_copilot_sdk::session_events::SessionEventType; -use super::support::{wait_for_event, with_e2e_context}; +use super::support::wait_for_event; const PROMPT: &str = concat!( "Use the sql tool exactly once to execute all three of the following statements ", @@ -14,7 +14,8 @@ const PROMPT: &str = concat!( #[tokio::test] async fn fires_session_todos_changed_and_exposes_rows_and_dependencies() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "session_todos_changed", "fires_session_todos_changed_and_exposes_rows_and_dependencies", |ctx| { @@ -59,3 +60,5 @@ async fn fires_session_todos_changed_and_exposes_rows_and_dependencies() { ) .await; } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("session_todos_changed", 1); diff --git a/rust/tests/e2e/skills.rs b/rust/tests/e2e/skills.rs index e0005ddf0..769b28b5f 100644 --- a/rust/tests/e2e/skills.rs +++ b/rust/tests/e2e/skills.rs @@ -2,13 +2,14 @@ use std::path::{Path, PathBuf}; use github_copilot_sdk::CustomAgentConfig; -use super::support::{assert_uuid_like, assistant_message_content, with_e2e_context}; +use super::support::{assert_uuid_like, assistant_message_content}; const SKILL_MARKER: &str = "PINEAPPLE_COCONUT_42"; #[tokio::test] async fn should_load_and_apply_skill_from_skilldirectories() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "skills", "should_load_and_apply_skill_from_skilldirectories", |ctx| { @@ -42,7 +43,8 @@ async fn should_load_and_apply_skill_from_skilldirectories() { #[tokio::test] async fn should_not_apply_skill_when_disabled_via_disabledskills() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "skills", "should_not_apply_skill_when_disabled_via_disabledskills", |ctx| { @@ -77,7 +79,8 @@ async fn should_not_apply_skill_when_disabled_via_disabledskills() { #[tokio::test] async fn should_allow_agent_with_skills_to_invoke_skill() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "skills", "should_allow_agent_with_skills_to_invoke_skill", |ctx| { @@ -118,7 +121,8 @@ async fn should_allow_agent_with_skills_to_invoke_skill() { #[tokio::test] async fn should_not_provide_skills_to_agent_without_skills_field() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "skills", "should_not_provide_skills_to_agent_without_skills_field", |ctx| { @@ -176,3 +180,4 @@ fn create_skill_dir(work_dir: &Path) -> PathBuf { .expect("write skill file"); skills_dir } +static E2E: super::support::SharedE2eGroup = super::support::SharedE2eGroup::standard("skills", 4); diff --git a/rust/tests/e2e/streaming_fidelity.rs b/rust/tests/e2e/streaming_fidelity.rs index 920ad695d..a48177174 100644 --- a/rust/tests/e2e/streaming_fidelity.rs +++ b/rust/tests/e2e/streaming_fidelity.rs @@ -7,11 +7,12 @@ use github_copilot_sdk::session_events::{ SessionStartData, }; -use super::support::{collect_until_idle, event_types, with_e2e_context}; +use super::support::{collect_until_idle, event_types}; #[tokio::test] async fn should_produce_delta_events_when_streaming_is_enabled() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "streaming_fidelity", "should_produce_delta_events_when_streaming_is_enabled", |ctx| { @@ -65,7 +66,7 @@ async fn should_produce_delta_events_when_streaming_is_enabled() { #[tokio::test] async fn should_not_produce_deltas_when_streaming_is_disabled() { - with_e2e_context( + super::support::with_shared_e2e_context(&E2E, "streaming_fidelity", "should_not_produce_deltas_when_streaming_is_disabled", |ctx| { @@ -107,7 +108,7 @@ async fn should_not_produce_deltas_when_streaming_is_disabled() { #[tokio::test] async fn should_produce_deltas_after_session_resume() { - with_e2e_context( + super::support::with_dedicated_e2e_context( "streaming_fidelity", "should_produce_deltas_after_session_resume", |ctx| { @@ -164,8 +165,7 @@ async fn should_produce_deltas_after_session_resume() { #[tokio::test] async fn should_not_produce_deltas_after_session_resume_with_streaming_disabled() { - with_e2e_context( - "streaming_fidelity", + super::support::with_dedicated_e2e_context("streaming_fidelity", "should_not_produce_deltas_after_session_resume_with_streaming_disabled", |ctx| { Box::pin(async move { @@ -227,7 +227,8 @@ async fn should_not_produce_deltas_after_session_resume_with_streaming_disabled( #[tokio::test] async fn should_emit_streaming_deltas_with_reasoning_effort_configured() { - with_e2e_context( + super::support::with_dedicated_group_e2e_context( + &E2E, "streaming_fidelity", "should_emit_streaming_deltas_with_reasoning_effort_configured", |ctx| { @@ -237,6 +238,7 @@ async fn should_emit_streaming_deltas_with_reasoning_effort_configured() { let session = client .create_session( ctx.approve_all_session_config() + .with_model("gpt-5.4") .with_streaming(true) .with_reasoning_effort("high"), ) @@ -279,7 +281,8 @@ async fn should_emit_streaming_deltas_with_reasoning_effort_configured() { #[tokio::test] async fn should_emit_assistantmessage_start_before_deltas_with_matching_messageid() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "streaming_fidelity", "should_emit_assistantmessagestart_before_deltas_with_matching_messageid", |ctx| { @@ -361,3 +364,5 @@ fn assert_has_content_deltas(events: &[github_copilot_sdk::SessionEvent]) { assert!(!data.delta_content.is_empty()); } } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("streaming_fidelity", 3); diff --git a/rust/tests/e2e/support.rs b/rust/tests/e2e/support.rs index 6ad609f58..d65b049f9 100644 --- a/rust/tests/e2e/support.rs +++ b/rust/tests/e2e/support.rs @@ -1,13 +1,17 @@ use std::ffi::{OsStr, OsString}; use std::future::Future; use std::io::{BufRead, BufReader, Read, Write}; -use std::net::TcpStream; +use std::net::{TcpStream, ToSocketAddrs}; +use std::ops::Deref; +use std::panic::AssertUnwindSafe; use std::path::{Path, PathBuf}; use std::pin::Pin; use std::process::{Child, Command, Stdio}; use std::sync::LazyLock; -use std::time::Duration; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::time::{Duration, Instant}; +use futures_util::FutureExt; use github_copilot_sdk::handler::ApproveAllHandler; use github_copilot_sdk::session::Session; use github_copilot_sdk::subscription::{EventSubscription, LifecycleSubscription}; @@ -16,15 +20,280 @@ use github_copilot_sdk::{ SessionId, SessionLifecycleEvent, Transport, }; use serde_json::json; -use tokio::sync::Semaphore; +use tokio::sync::{Mutex, Semaphore}; static E2E_CONCURRENCY: LazyLock = LazyLock::new(|| Semaphore::new(e2e_concurrency())); +static SHARED_E2E_RUNTIME: LazyLock = LazyLock::new(|| { + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .thread_name("rust-e2e-shared") + .build() + .expect("create shared E2E runtime") +}); +const SHARED_E2E_CLEANUP_TIMEOUT: Duration = Duration::from_secs(10); pub const DEFAULT_TEST_TOKEN: &str = "rust-e2e-token"; type TestFuture<'a> = Pin + 'a>>; -pub async fn with_e2e_context(category: &str, snapshot_name: &str, test: F) +/// Fixed client options for one explicitly declared shared E2E group. +pub type SharedClientOptions = fn(&E2eContext) -> ClientOptions; + +/// A file- or group-scoped shared E2E runtime. +/// +/// This deliberately has no options-keyed registry: every Rust source group owns +/// its own static instance and selects its options at that declaration site. +pub struct SharedE2eGroup { + category: &'static str, + client_options: SharedClientOptions, + expected_invocations: usize, + completed_invocations: AtomicUsize, + state: Mutex>, +} + +struct SharedE2eState { + context: E2eContext, + client: Client, +} + +/// Test facade over a group's shared context and client. +/// +/// It dereferences to [`E2eContext`] for proxy and fixture helpers, while +/// [`Self::start_client`] returns a clone of the group's already-started client. +pub struct SharedE2eContext<'a> { + context: &'a mut E2eContext, + client: Client, +} + +/// A clone of a group's shared client. +/// +/// `stop` is deliberately a no-op: tests retain their existing local teardown +/// shape without shutting down the next test's runtime. The group stops the +/// actual client after its final expected invocation. Tests that verify +/// stopping or force-stopping a client stay on the dedicated helper. +#[derive(Clone)] +pub struct SharedE2eClient(Client); + +impl Deref for SharedE2eClient { + type Target = Client; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl SharedE2eClient { + pub async fn stop(&self) -> std::result::Result<(), github_copilot_sdk::StopErrors> { + Ok(()) + } +} + +impl Deref for SharedE2eContext<'_> { + type Target = E2eContext; + + fn deref(&self) -> &Self::Target { + self.context + } +} + +impl SharedE2eContext<'_> { + /// Clone the group client. Shared tests must not call `Client::stop`; the + /// group tears it down after its final expected test invocation. + pub async fn start_client(&self) -> SharedE2eClient { + SharedE2eClient(self.client.clone()) + } +} + +impl SharedE2eGroup { + pub const fn new( + category: &'static str, + client_options: SharedClientOptions, + expected_invocations: usize, + ) -> Self { + Self { + category, + client_options, + expected_invocations, + completed_invocations: AtomicUsize::new(0), + state: Mutex::const_new(None), + } + } + + pub const fn standard(category: &'static str, expected_invocations: usize) -> Self { + Self::new( + category, + standard_shared_client_options, + expected_invocations, + ) + } +} + +/// The standard stdio/default-transport options used by most shared groups. +pub fn standard_shared_client_options(context: &E2eContext) -> ClientOptions { + context.client_options() +} + +/// Run a test against an explicitly declared, file/group-scoped shared client. +/// +/// Calls using one group serialize, while different groups still use the suite +/// concurrency limit. Before and after every test, sessions are disconnected and +/// deleted, the work directory is emptied, and the proxy is reconfigured for the +/// test's snapshot so exchanges cannot bleed across tests. After the declared +/// number of invocations completes, the group's client and proxy are stopped. +pub async fn with_shared_e2e_context( + group: &'static SharedE2eGroup, + category: &str, + snapshot_name: &str, + test: F, +) where + F: for<'a> FnOnce(&'a mut SharedE2eContext<'a>) -> TestFuture<'a>, +{ + assert_eq!( + category, group.category, + "shared E2E group category must match the test's snapshots" + ); + let mut state = group.state.lock().await; + let _permit = E2E_CONCURRENCY + .acquire() + .await + .expect("E2E concurrency semaphore should stay open"); + let completed = group.completed_invocations.fetch_add(1, Ordering::Relaxed) + 1; + if state.is_none() { + let context = E2eContext::new(group.category, snapshot_name) + .await + .unwrap_or_else(|err| panic!("create shared E2E context: {err}")); + let _env_guard = InProcessEnvGuard::activate(&context); + let options = (group.client_options)(&context); + let mut startup = SHARED_E2E_RUNTIME.spawn(async move { + let client = Client::start(options).await?; + client.start_router_for_test(); + Ok::<_, github_copilot_sdk::Error>(client) + }); + let client = match tokio::time::timeout(default_test_timeout(), &mut startup).await { + Ok(result) => result + .expect("join shared E2E client startup") + .expect("start shared E2E client"), + Err(_) => { + startup.abort(); + let _ = tokio::time::timeout(SHARED_E2E_CLEANUP_TIMEOUT, startup).await; + panic!( + "timed out after {:?} starting shared E2E client", + default_test_timeout() + ); + } + }; + *state = Some(SharedE2eState { context, client }); + } + + let _env_guard = InProcessEnvGuard::activate( + &state + .as_ref() + .expect("shared E2E state initialized") + .context, + ); + let (result, cleanup_result) = { + let state = state.as_mut().expect("shared E2E state initialized"); + let result = match tokio::time::timeout( + SHARED_E2E_CLEANUP_TIMEOUT, + state.prepare_test(group.category, snapshot_name), + ) + .await + { + Ok(Ok(())) => Ok({ + let mut context = SharedE2eContext { + context: &mut state.context, + client: state.client.clone(), + }; + AssertUnwindSafe(tokio::time::timeout( + default_test_timeout(), + test(&mut context), + )) + .catch_unwind() + .await + }), + Ok(Err(error)) => Err(error), + Err(_) => Err(std::io::Error::other(format!( + "timed out after {SHARED_E2E_CLEANUP_TIMEOUT:?} preparing shared E2E test" + ))), + }; + let cleanup_result = match tokio::time::timeout( + SHARED_E2E_CLEANUP_TIMEOUT, + state.cleanup_after_test(), + ) + .await + { + Ok(result) => result, + Err(_) => { + state.client.force_stop(); + Err(std::io::Error::other(format!( + "timed out after {SHARED_E2E_CLEANUP_TIMEOUT:?} cleaning up shared E2E test" + ))) + } + }; + (result, cleanup_result) + }; + + let test_succeeded = matches!(&result, Ok(Ok(Ok(())))); + let skip_writing_cache = !test_succeeded || cleanup_result.is_err(); + let teardown_result = if !test_succeeded + || cleanup_result.is_err() + || is_filtered_test_run() + || completed == group.expected_invocations + { + state + .take() + .expect("shared E2E state initialized") + .shutdown_bounded(skip_writing_cache) + .await + } else { + Ok(()) + }; + + match result { + Ok(Ok(Ok(()))) => { + cleanup_result.unwrap_or_else(|error| panic!("clean up shared E2E test: {error}")); + teardown_result.unwrap_or_else(|error| panic!("tear down shared E2E group: {error}")); + } + Ok(Ok(Err(_))) => { + if let Err(error) = cleanup_result { + eprintln!("failed to clean up timed-out shared E2E test: {error}"); + } + if let Err(error) = teardown_result { + eprintln!("failed to tear down shared E2E group after timeout: {error}"); + } + panic!( + "timed out after {:?} running shared E2E test {}/{}", + default_test_timeout(), + group.category, + snapshot_name + ); + } + Ok(Err(payload)) => { + if let Err(error) = cleanup_result { + eprintln!("failed to clean up shared E2E test after panic: {error}"); + } + if let Err(error) = teardown_result { + eprintln!("failed to tear down shared E2E group after panic: {error}"); + } + std::panic::resume_unwind(payload); + } + Err(error) => { + if let Err(cleanup_error) = cleanup_result { + eprintln!( + "failed to clean up shared E2E test after setup failure: {cleanup_error}" + ); + } + if let Err(teardown_error) = teardown_result { + eprintln!( + "failed to tear down shared E2E group after setup failure: {teardown_error}" + ); + } + panic!("prepare shared E2E test: {error}"); + } + } +} + +pub async fn with_dedicated_e2e_context(category: &str, snapshot_name: &str, test: F) where F: for<'a> FnOnce(&'a mut E2eContext) -> TestFuture<'a>, { @@ -56,11 +325,56 @@ where ); } -/// Like [`with_e2e_context`] but starts the CapiProxy without loading a +pub async fn with_dedicated_group_e2e_context( + _group: &'static SharedE2eGroup, + category: &str, + snapshot_name: &str, + test: F, +) where + F: for<'a> FnOnce(&'a mut E2eContext) -> TestFuture<'a>, +{ + with_dedicated_e2e_context(category, snapshot_name, test).await; +} + +pub async fn skip_shared_e2e_inprocess(group: &'static SharedE2eGroup, reason: &str) -> bool { + if !skip_inprocess(reason) { + return false; + } + + let mut state = group.state.lock().await; + let _permit = E2E_CONCURRENCY + .acquire() + .await + .expect("E2E concurrency semaphore should stay open"); + let completed = group.completed_invocations.fetch_add(1, Ordering::Relaxed) + 1; + if completed == group.expected_invocations + && let Some(state) = state.take() + { + state + .shutdown_bounded(false) + .await + .unwrap_or_else(|error| panic!("tear down shared E2E group after skip: {error}")); + } + true +} + +/// Run a dedicated one-client E2E test. +/// +/// New tests should call [`with_dedicated_e2e_context`] to make the lifecycle +/// choice visible at the call site. This name remains for existing dedicated +/// tests while they are migrated group by group. +pub async fn with_e2e_context(category: &str, snapshot_name: &str, test: F) +where + F: for<'a> FnOnce(&'a mut E2eContext) -> TestFuture<'a>, +{ + with_dedicated_e2e_context(category, snapshot_name, test).await; +} + +/// Like [`with_dedicated_e2e_context`] but starts the CapiProxy without loading a /// recorded snapshot. Used by the LLM inference callback tests, whose /// registered provider fabricates every model-layer response so no CAPI /// replay is needed — only the auth/user endpoints are served by the proxy. -pub async fn with_e2e_context_no_snapshot(test: F) +pub async fn with_dedicated_e2e_context_no_snapshot(test: F) where F: for<'a> FnOnce(&'a mut E2eContext) -> TestFuture<'a>, { @@ -89,6 +403,15 @@ where ); } +/// Dedicated no-snapshot compatibility helper. See +/// [`with_dedicated_e2e_context_no_snapshot`]. +pub async fn with_e2e_context_no_snapshot(test: F) +where + F: for<'a> FnOnce(&'a mut E2eContext) -> TestFuture<'a>, +{ + with_dedicated_e2e_context_no_snapshot(test).await; +} + pub struct E2eContext { repo_root: PathBuf, cli_path: PathBuf, @@ -193,6 +516,7 @@ impl E2eContext { /// `node --embedded-host` argv itself and loads the sibling /// runtime cdylib), so a `.js` entrypoint is not split into node + /// prefix_args here. + #[cfg_attr(not(feature = "bundled-in-process"), allow(dead_code))] pub async fn start_inprocess_client(&self) -> Client { let options = ClientOptions::new().with_transport(Transport::InProcess); Client::start(options) @@ -330,6 +654,9 @@ impl E2eContext { .as_os_str() .to_owned(), ), + ]); + env.extend(isolated_cache_environment(self.home_dir.path())); + env.extend([ ("COPILOT_MCP_APPS".into(), "true".into()), ("MCP_APPS".into(), "true".into()), ("GH_TOKEN".into(), DEFAULT_TEST_TOKEN.into()), @@ -347,6 +674,130 @@ impl E2eContext { } } +impl SharedE2eState { + async fn prepare_test(&mut self, category: &str, snapshot_name: &str) -> std::io::Result<()> { + self.cleanup_sessions().await?; + clear_directory_contents(self.context.work_dir())?; + self.context.configure(category, snapshot_name)?; + self.context.set_default_copilot_user(); + Ok(()) + } + + async fn cleanup_after_test(&mut self) -> std::io::Result<()> { + self.cleanup_sessions().await?; + clear_directory_contents(self.context.work_dir()) + } + + async fn cleanup_sessions(&self) -> std::io::Result<()> { + self.client + .cleanup_sessions_for_test() + .await + .map_err(|err| { + std::io::Error::other(format!("clean up shared E2E sessions failed: {err}")) + }) + } + + async fn shutdown_bounded(mut self, skip_writing_cache: bool) -> std::io::Result<()> { + let client_result = + match tokio::time::timeout(SHARED_E2E_CLEANUP_TIMEOUT, self.client.stop()).await { + Ok(result) => result.map_err(|err| { + std::io::Error::other(format!("stop shared E2E client failed: {err}")) + }), + Err(_) => { + self.client.force_stop(); + Err(std::io::Error::other(format!( + "timed out after {SHARED_E2E_CLEANUP_TIMEOUT:?} stopping shared E2E client" + ))) + } + }; + let proxy_result = self.context.cleanup(skip_writing_cache).await; + + match (client_result, proxy_result) { + (Ok(()), Ok(())) => Ok(()), + (Err(error), Ok(())) | (Ok(()), Err(error)) => Err(error), + (Err(client_error), Err(proxy_error)) => Err(std::io::Error::other(format!( + "{client_error}; stop shared E2E proxy failed: {proxy_error}" + ))), + } + } +} + +fn wait_for_child_exit(child: &mut Child) -> std::io::Result<()> { + let deadline = Instant::now() + SHARED_E2E_CLEANUP_TIMEOUT; + loop { + if child.try_wait()?.is_some() { + return Ok(()); + } + if Instant::now() >= deadline { + kill_and_wait_child(child); + return Err(std::io::Error::other(format!( + "timed out after {SHARED_E2E_CLEANUP_TIMEOUT:?} waiting for child process" + ))); + } + std::thread::sleep(Duration::from_millis(25)); + } +} + +fn kill_and_wait_child(child: &mut Child) { + if let Err(error) = child.kill() { + eprintln!("failed to kill E2E child process: {error}"); + } + let deadline = Instant::now() + SHARED_E2E_CLEANUP_TIMEOUT; + loop { + match child.try_wait() { + Ok(Some(_)) => return, + Ok(None) => {} + Err(error) => { + eprintln!("failed to inspect E2E child process after kill: {error}"); + return; + } + } + if Instant::now() >= deadline { + eprintln!( + "timed out after {SHARED_E2E_CLEANUP_TIMEOUT:?} waiting for killed E2E child process" + ); + return; + } + std::thread::sleep(Duration::from_millis(25)); + } +} + +fn connect_with_timeout(host: &str, port: u16) -> std::io::Result { + let mut last_error = None; + for address in (host, port).to_socket_addrs()? { + match TcpStream::connect_timeout(&address, SHARED_E2E_CLEANUP_TIMEOUT) { + Ok(stream) => { + stream.set_read_timeout(Some(SHARED_E2E_CLEANUP_TIMEOUT))?; + stream.set_write_timeout(Some(SHARED_E2E_CLEANUP_TIMEOUT))?; + return Ok(stream); + } + Err(error) => last_error = Some(error), + } + } + Err(last_error.unwrap_or_else(|| { + std::io::Error::other(format!("no socket addresses resolved for {host}:{port}")) + })) +} + +fn is_filtered_test_run() -> bool { + std::env::args().skip(1).any(|arg| { + !arg.starts_with('-') || matches!(arg.as_str(), "--ignored" | "--include-ignored") + }) +} + +fn clear_directory_contents(directory: &Path) -> std::io::Result<()> { + for entry in std::fs::read_dir(directory)? { + let entry = entry?; + let path = entry.path(); + if entry.file_type()?.is_dir() { + std::fs::remove_dir_all(path)?; + } else { + std::fs::remove_file(path)?; + } + } + Ok(()) +} + impl Drop for E2eContext { fn drop(&mut self) { if let Some(mut proxy) = self.proxy.take() { @@ -783,6 +1234,20 @@ fn canonical_temp_path(path: &Path) -> PathBuf { std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf()) } +fn isolated_cache_environment(path: &Path) -> [(OsString, OsString); 2] { + let home_dir = canonical_temp_path(path); + let cache_dir = home_dir.join(".cache"); + // COPILOT_HOME does not redirect platform cache paths, so isolate the cache + // to prevent concurrent CLI processes from sharing mutable startup state. + [ + ( + "COPILOT_CACHE_HOME".into(), + cache_dir.join("copilot").into_os_string(), + ), + ("XDG_CACHE_HOME".into(), cache_dir.into_os_string()), + ] +} + struct CapiProxy { child: Option, proxy_url: String, @@ -802,38 +1267,85 @@ impl CapiProxy { .spawn()?; let stdout = child.stdout.take().expect("proxy stdout"); - let reader = BufReader::new(stdout); + let (line_tx, line_rx) = std::sync::mpsc::channel(); + std::thread::spawn(move || { + for line in BufReader::new(stdout).lines() { + let failed = line.is_err(); + if line_tx.send(line).is_err() || failed { + break; + } + } + }); let re = regex::Regex::new(r"Listening: (http://[^\s]+)\s+(\{.*\})$").unwrap(); - for line in reader.lines() { - let line = line?; + let deadline = Instant::now() + SHARED_E2E_CLEANUP_TIMEOUT; + while let Some(remaining) = deadline.checked_duration_since(Instant::now()) { + let line = match line_rx.recv_timeout(remaining) { + Ok(Ok(line)) => line, + Ok(Err(error)) => { + kill_and_wait_child(&mut child); + return Err(error); + } + Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => { + kill_and_wait_child(&mut child); + return Err(std::io::Error::other("proxy exited before startup")); + } + Err(std::sync::mpsc::RecvTimeoutError::Timeout) => break, + }; if let Some(captures) = re.captures(&line) { - let metadata: serde_json::Value = - serde_json::from_str(captures.get(2).unwrap().as_str())?; - let connect_proxy_url = metadata - .get("connectProxyUrl") - .and_then(|value| value.as_str()) - .expect("connectProxyUrl") - .to_string(); - let ca_file_path = metadata - .get("caFilePath") - .and_then(|value| value.as_str()) - .expect("caFilePath") - .to_string(); + let parsed = (|| { + let proxy_url = captures + .get(1) + .ok_or_else(|| { + std::io::Error::other("proxy startup line missing URL capture") + })? + .as_str() + .to_string(); + let metadata_text = captures.get(2).ok_or_else(|| { + std::io::Error::other("proxy startup line missing metadata capture") + })?; + let metadata: serde_json::Value = serde_json::from_str(metadata_text.as_str())?; + let connect_proxy_url = metadata + .get("connectProxyUrl") + .and_then(|value| value.as_str()) + .ok_or_else(|| { + std::io::Error::other("proxy startup metadata missing connectProxyUrl") + })? + .to_string(); + let ca_file_path = metadata + .get("caFilePath") + .and_then(|value| value.as_str()) + .ok_or_else(|| { + std::io::Error::other("proxy startup metadata missing caFilePath") + })? + .to_string(); + Ok::<_, std::io::Error>((proxy_url, connect_proxy_url, ca_file_path)) + })(); + let (proxy_url, connect_proxy_url, ca_file_path) = match parsed { + Ok(metadata) => metadata, + Err(error) => { + kill_and_wait_child(&mut child); + return Err(error); + } + }; return Ok(Self { child: Some(child), - proxy_url: captures.get(1).unwrap().as_str().to_string(), + proxy_url, connect_proxy_url, ca_file_path, }); } if line.contains("Listening: ") { + kill_and_wait_child(&mut child); return Err(std::io::Error::other(format!( "proxy startup line missing metadata: {line}" ))); } } - Err(std::io::Error::other("proxy exited before startup")) + kill_and_wait_child(&mut child); + Err(std::io::Error::other(format!( + "timed out after {SHARED_E2E_CLEANUP_TIMEOUT:?} waiting for proxy startup" + ))) } fn url(&self) -> &str { @@ -874,7 +1386,7 @@ impl CapiProxy { }; let result = self.post_json(path, ""); if let Some(mut child) = self.child.take() { - let _ = child.wait(); + wait_for_child_exit(&mut child)?; } result } @@ -926,7 +1438,7 @@ impl CapiProxy { fn request(&self, method: &str, path: &str, body: &str) -> std::io::Result { let (host, port) = parse_http_url(&self.proxy_url)?; - let mut stream = TcpStream::connect((host.as_str(), port))?; + let mut stream = connect_with_timeout(&host, port)?; write!( stream, "{method} {path} HTTP/1.1\r\nHost: {host}:{port}\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}", @@ -1003,3 +1515,25 @@ fn node_program() -> &'static str { fn npx_program() -> &'static str { if cfg!(windows) { "npx.cmd" } else { "npx" } } + +#[test] +fn e2e_context_isolates_copilot_cache() { + let home_dir = tempfile::tempdir().expect("create test home"); + let home_dir = canonical_temp_path(home_dir.path()); + let cache_dir = home_dir.join(".cache"); + let expected = [ + ("COPILOT_CACHE_HOME", cache_dir.join("copilot")), + ("XDG_CACHE_HOME", cache_dir), + ]; + + let environment = isolated_cache_environment(&home_dir); + + for (key, value) in expected { + assert!( + environment.iter().any(|(actual_key, actual_value)| { + actual_key == key && actual_value == value.as_os_str() + }), + "{key} should use the isolated test home" + ); + } +} diff --git a/rust/tests/e2e/system_message_sections.rs b/rust/tests/e2e/system_message_sections.rs index e582d3846..f13336752 100644 --- a/rust/tests/e2e/system_message_sections.rs +++ b/rust/tests/e2e/system_message_sections.rs @@ -2,11 +2,12 @@ use std::collections::HashMap; use github_copilot_sdk::{SectionOverride, SystemMessageConfig}; -use super::support::{assistant_message_content, with_e2e_context}; +use super::support::assistant_message_content; #[tokio::test] async fn should_use_replaced_identity_section_in_response() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "system_message_sections", "should_use_replaced_identity_section_in_response", |ctx| { @@ -60,7 +61,8 @@ async fn should_use_replaced_identity_section_in_response() { #[tokio::test] async fn should_use_replaced_preamble_section_in_response() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "system_message_sections", "should_use_replaced_preamble_section_in_response", |ctx| { @@ -111,3 +113,5 @@ async fn should_use_replaced_preamble_section_in_response() { ) .await; } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("system_message_sections", 2); diff --git a/rust/tests/e2e/tool_results.rs b/rust/tests/e2e/tool_results.rs index e6e62643f..c46cacbf3 100644 --- a/rust/tests/e2e/tool_results.rs +++ b/rust/tests/e2e/tool_results.rs @@ -10,11 +10,12 @@ use github_copilot_sdk::{ use serde_json::json; use tokio::sync::mpsc; -use super::support::{assistant_message_content, collect_until_idle, with_e2e_context}; +use super::support::{assistant_message_content, collect_until_idle}; #[tokio::test] async fn should_handle_structured_toolresultobject_from_custom_tool() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "tool_results", "should_handle_structured_toolresultobject_from_custom_tool", |ctx| { @@ -41,7 +42,7 @@ async fn should_handle_structured_toolresultobject_from_custom_tool() { #[tokio::test] async fn should_handle_tool_result_with_failure_resulttype() { - with_e2e_context( + super::support::with_shared_e2e_context(&E2E, "tool_results", "should_handle_tool_result_with_failure_resulttype", |ctx| { @@ -69,7 +70,8 @@ async fn should_handle_tool_result_with_failure_resulttype() { #[tokio::test] async fn should_preserve_tooltelemetry_and_not_stringify_structured_results_for_llm() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "tool_results", "should_preserve_tooltelemetry_and_not_stringify_structured_results_for_llm", |ctx| { @@ -116,7 +118,7 @@ async fn should_preserve_tooltelemetry_and_not_stringify_structured_results_for_ #[tokio::test] async fn should_handle_tool_result_with_rejected_resulttype() { - with_e2e_context( + super::support::with_shared_e2e_context(&E2E, "tool_results", "should_handle_tool_result_with_rejected_resulttype", |ctx| { @@ -153,7 +155,7 @@ async fn should_handle_tool_result_with_rejected_resulttype() { #[tokio::test] async fn should_handle_tool_result_with_denied_resulttype() { - with_e2e_context( + super::support::with_shared_e2e_context(&E2E, "tool_results", "should_handle_tool_result_with_denied_resulttype", |ctx| { @@ -356,3 +358,5 @@ fn string_tool( "required": [parameter], })) } +static E2E: super::support::SharedE2eGroup = + super::support::SharedE2eGroup::standard("tool_results", 5); diff --git a/rust/tests/e2e/tools.rs b/rust/tests/e2e/tools.rs index 2c474bca1..586a31d3a 100644 --- a/rust/tests/e2e/tools.rs +++ b/rust/tests/e2e/tools.rs @@ -9,11 +9,11 @@ use github_copilot_sdk::{ use serde_json::json; use tokio::sync::{Mutex, mpsc}; -use super::support::{assistant_message_content, recv_with_timeout, with_e2e_context}; +use super::support::{assistant_message_content, recv_with_timeout}; #[tokio::test] async fn invokes_built_in_tools() { - with_e2e_context("tools", "invokes_built_in_tools", |ctx| { + super::support::with_shared_e2e_context(&E2E, "tools", "invokes_built_in_tools", |ctx| { Box::pin(async move { ctx.set_default_copilot_user(); std::fs::write( @@ -43,7 +43,7 @@ async fn invokes_built_in_tools() { #[tokio::test] async fn invokes_custom_tool() { - with_e2e_context("tools", "invokes_custom_tool", |ctx| { + super::support::with_shared_e2e_context(&E2E, "tools", "invokes_custom_tool", |ctx| { Box::pin(async move { ctx.set_default_copilot_user(); let client = ctx.start_client().await; @@ -75,7 +75,7 @@ async fn invokes_custom_tool() { #[tokio::test] async fn low_level_tool_definition() { - with_e2e_context("tools", "low_level_tool_definition", |ctx| { + super::support::with_shared_e2e_context(&E2E, "tools", "low_level_tool_definition", |ctx| { Box::pin(async move { ctx.set_default_copilot_user(); let client = ctx.start_client().await; @@ -124,7 +124,7 @@ async fn low_level_tool_definition() { #[tokio::test] async fn handles_tool_calling_errors() { - with_e2e_context("tools", "handles_tool_calling_errors", |ctx| { + super::support::with_shared_e2e_context(&E2E, "tools", "handles_tool_calling_errors", |ctx| { Box::pin(async move { ctx.set_default_copilot_user(); let client = ctx.start_client().await; @@ -173,7 +173,7 @@ async fn handles_tool_calling_errors() { #[tokio::test] async fn can_receive_and_return_complex_types() { - with_e2e_context("tools", "can_receive_and_return_complex_types", |ctx| { + super::support::with_shared_e2e_context(&E2E, "tools", "can_receive_and_return_complex_types", |ctx| { Box::pin(async move { ctx.set_default_copilot_user(); let client = ctx.start_client().await; @@ -212,76 +212,89 @@ async fn can_receive_and_return_complex_types() { #[tokio::test] async fn overrides_built_in_tool_with_custom_tool() { - with_e2e_context("tools", "overrides_built_in_tool_with_custom_tool", |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let client = ctx.start_client().await; - let __perm = Arc::new(ApproveAllHandler); - let tools = vec![custom_grep_tool()]; - let session = client - .create_session( - SessionConfig::default() - .with_github_token(super::support::DEFAULT_TEST_TOKEN) - .with_permission_handler(__perm) - .with_tools(tools), - ) - .await - .expect("create session"); + super::support::with_shared_e2e_context( + &E2E, + "tools", + "overrides_built_in_tool_with_custom_tool", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let client = ctx.start_client().await; + let __perm = Arc::new(ApproveAllHandler); + let tools = vec![custom_grep_tool()]; + let session = client + .create_session( + SessionConfig::default() + .with_github_token(super::support::DEFAULT_TEST_TOKEN) + .with_permission_handler(__perm) + .with_tools(tools), + ) + .await + .expect("create session"); - let answer = session - .send_and_wait("Use grep to search for the word 'hello'") - .await - .expect("send") - .expect("assistant message"); - assert!(assistant_message_content(&answer).contains("CUSTOM_GREP_RESULT")); + let answer = session + .send_and_wait("Use grep to search for the word 'hello'") + .await + .expect("send") + .expect("assistant message"); + assert!(assistant_message_content(&answer).contains("CUSTOM_GREP_RESULT")); - session.disconnect().await.expect("disconnect session"); - client.stop().await.expect("stop client"); - }) - }) + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) .await; } #[tokio::test] async fn skippermission_sent_in_tool_definition() { - with_e2e_context("tools", "skippermission_sent_in_tool_definition", |ctx| { - Box::pin(async move { - ctx.set_default_copilot_user(); - let client = ctx.start_client().await; - let (permission_tx, mut permission_rx) = mpsc::unbounded_channel(); - let handler = Arc::new(RecordingPermissionHandler { - permission_tx, - decision: PermissionResult::reject(None), - }); - let __perm = handler; - let tools = vec![safe_lookup_tool()]; - let session = client - .create_session( - SessionConfig::default() - .with_github_token(super::support::DEFAULT_TEST_TOKEN) - .with_permission_handler(__perm) - .with_tools(tools), - ) - .await - .expect("create session"); + super::support::with_shared_e2e_context( + &E2E, + "tools", + "skippermission_sent_in_tool_definition", + |ctx| { + Box::pin(async move { + ctx.set_default_copilot_user(); + let client = ctx.start_client().await; + let (permission_tx, mut permission_requests) = mpsc::unbounded_channel(); + let handler = Arc::new(RecordingPermissionHandler { + permission_tx, + decision: PermissionResult::reject(None), + }); + let __perm = handler; + let tools = vec![safe_lookup_tool()]; + let session = client + .create_session( + SessionConfig::default() + .with_github_token(super::support::DEFAULT_TEST_TOKEN) + .with_permission_handler(__perm) + .with_tools(tools), + ) + .await + .expect("create session"); - let answer = session - .send_and_wait("Use safe_lookup to look up 'test123'") - .await - .expect("send") - .expect("assistant message"); - assert!(assistant_message_content(&answer).contains("RESULT")); - assert!( - tokio::time::timeout(std::time::Duration::from_millis(100), permission_rx.recv()) + let answer = session + .send_and_wait("Use safe_lookup to look up 'test123'") + .await + .expect("send") + .expect("assistant message"); + assert!(assistant_message_content(&answer).contains("RESULT")); + assert!( + tokio::time::timeout( + std::time::Duration::from_millis(100), + permission_requests.recv() + ) .await .is_err(), - "skip_permission tool should not request permission" - ); + "skip_permission tool should not request permission" + ); - session.disconnect().await.expect("disconnect session"); - client.stop().await.expect("stop client"); - }) - }) + session.disconnect().await.expect("disconnect session"); + client.stop().await.expect("stop client"); + }) + }, + ) .await; } @@ -291,7 +304,8 @@ async fn can_return_binary_result() {} #[tokio::test] async fn invokes_custom_tool_with_permission_handler() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "tools", "invokes_custom_tool_with_permission_handler", |ctx| { @@ -334,7 +348,8 @@ async fn invokes_custom_tool_with_permission_handler() { #[tokio::test] async fn denies_custom_tool_when_permission_denied() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "tools", "denies_custom_tool_when_permission_denied", |ctx| { @@ -380,7 +395,7 @@ async fn denies_custom_tool_when_permission_denied() { #[tokio::test] async fn should_execute_multiple_custom_tools_in_parallel_single_turn() { - with_e2e_context( + super::support::with_shared_e2e_context(&E2E, "tools", "should_execute_multiple_custom_tools_in_parallel_single_turn", |ctx| { @@ -428,7 +443,8 @@ async fn should_execute_multiple_custom_tools_in_parallel_single_turn() { #[tokio::test] async fn should_respect_availabletools_and_excludedtools_combined() { - with_e2e_context( + super::support::with_shared_e2e_context( + &E2E, "tools", "should_respect_availabletools_and_excludedtools_combined", |ctx| { @@ -864,3 +880,4 @@ impl ToolHandler for DbQueryTool { )) } } +static E2E: super::support::SharedE2eGroup = super::support::SharedE2eGroup::standard("tools", 11); diff --git a/rust/tests/jsonrpc_test.rs b/rust/tests/jsonrpc_test.rs index 7f7d43213..1735067c3 100644 --- a/rust/tests/jsonrpc_test.rs +++ b/rust/tests/jsonrpc_test.rs @@ -2,7 +2,7 @@ #![allow(clippy::unwrap_used)] use github_copilot_sdk::test_support::{JsonRpcClient, JsonRpcNotification, JsonRpcRequest}; -use tokio::io::{AsyncWrite, AsyncWriteExt, duplex}; +use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, duplex}; use tokio::sync::{broadcast, mpsc}; /// Write a Content-Length framed JSON-RPC message to a writer. @@ -13,6 +13,28 @@ async fn write_framed(writer: &mut (impl AsyncWrite + Unpin), body: &[u8]) { writer.flush().await.unwrap(); } +async fn read_framed(reader: &mut (impl AsyncRead + Unpin)) -> Vec { + let mut header = String::new(); + loop { + let mut byte = [0u8; 1]; + reader.read_exact(&mut byte).await.unwrap(); + header.push(byte[0] as char); + if header.ends_with("\r\n\r\n") { + break; + } + } + + let length = header + .trim() + .strip_prefix("Content-Length: ") + .unwrap() + .parse() + .unwrap(); + let mut body = vec![0u8; length]; + reader.read_exact(&mut body).await.unwrap(); + body +} + #[tokio::test] async fn request_response_round_trip() { // duplex: client_write → server_read, server_write → client_read @@ -410,3 +432,116 @@ async fn send_request_cancellation_does_not_leak_pending() { assert_eq!(response.result.unwrap()["ok"], true); server_task.await.unwrap(); } + +#[test] +fn lone_surrogate_yields_unexpected_end_of_hex_escape() { + let error = serde_json::from_slice::(br#""\ud83d""#).unwrap_err(); + + assert_eq!( + error.to_string(), + "unexpected end of hex escape at line 1 column 8" + ); +} + +#[tokio::test] +async fn lone_surrogate_frame_is_recovered_without_closing_connection() { + let (client_write, mut server_read) = duplex(4096); + let (mut server_write, client_read) = duplex(4096); + let (notification_tx, _) = broadcast::channel(16); + let (request_tx, _) = mpsc::unbounded_channel(); + let client = JsonRpcClient::new(client_write, client_read, notification_tx, request_tx); + + let server_task = tokio::spawn(async move { + let request: JsonRpcRequest = + serde_json::from_slice(&read_framed(&mut server_read).await).unwrap(); + let response = format!( + r#"{{"jsonrpc":"2.0","id":{},"result":{{"name":"invalid \ud83d value"}}}}"#, + request.id + ); + write_framed(&mut server_write, response.as_bytes()).await; + + let request: JsonRpcRequest = + serde_json::from_slice(&read_framed(&mut server_read).await).unwrap(); + let response = serde_json::json!({ + "jsonrpc": "2.0", + "id": request.id, + "result": {"name": "still connected"} + }); + write_framed(&mut server_write, &serde_json::to_vec(&response).unwrap()).await; + }); + + let response = client.send_request("models.list", None).await.unwrap(); + assert_eq!( + response.result.unwrap()["name"], + serde_json::json!("invalid \u{FFFD} value") + ); + + let response = client.send_request("account.getQuota", None).await.unwrap(); + assert_eq!( + response.result.unwrap()["name"], + serde_json::json!("still connected") + ); + server_task.await.unwrap(); +} + +#[tokio::test] +async fn unrepairable_frame_remains_fatal() { + let (client_write, mut server_read) = duplex(4096); + let (mut server_write, client_read) = duplex(4096); + let (notification_tx, _) = broadcast::channel(16); + let (request_tx, _) = mpsc::unbounded_channel(); + let client = JsonRpcClient::new(client_write, client_read, notification_tx, request_tx); + + let server_task = tokio::spawn(async move { + let request: JsonRpcRequest = + serde_json::from_slice(&read_framed(&mut server_read).await).unwrap(); + let response = format!( + r#"{{"jsonrpc":"2.0","id":{},"result":{{"surrogate":"\ud83d","escape":"\q"}}}}"#, + request.id + ); + write_framed(&mut server_write, response.as_bytes()).await; + }); + + let error = tokio::time::timeout( + std::time::Duration::from_secs(2), + client.send_request("models.list", None), + ) + .await + .expect("unrepairable frame did not terminate the pending request") + .unwrap_err(); + + assert_eq!(error.to_string(), "request cancelled"); + assert!(error.is_transport_failure()); + server_task.await.unwrap(); +} + +#[tokio::test] +async fn valid_pairs_and_escaped_backslashes_are_untouched() { + let (client_write, mut server_read) = duplex(4096); + let (mut server_write, client_read) = duplex(4096); + let (notification_tx, _) = broadcast::channel(16); + let (request_tx, _) = mpsc::unbounded_channel(); + let client = JsonRpcClient::new(client_write, client_read, notification_tx, request_tx); + + let server_task = tokio::spawn(async move { + let request: JsonRpcRequest = + serde_json::from_slice(&read_framed(&mut server_read).await).unwrap(); + let response = format!( + r#"{{"jsonrpc":"2.0","id":{},"result":{{"emoji":"\ud83d\ude00","path":"C:\\ud83d","invalid":"\ud83d"}}}}"#, + request.id + ); + write_framed(&mut server_write, response.as_bytes()).await; + }); + + let result = client + .send_request("models.list", None) + .await + .unwrap() + .result + .unwrap(); + + assert_eq!(result["emoji"], serde_json::json!("😀")); + assert_eq!(result["path"], serde_json::json!(r"C:\ud83d")); + assert_eq!(result["invalid"], serde_json::json!("\u{FFFD}")); + server_task.await.unwrap(); +} diff --git a/rust/tests/session_test.rs b/rust/tests/session_test.rs index 122ec475d..231a8f91e 100644 --- a/rust/tests/session_test.rs +++ b/rust/tests/session_test.rs @@ -10,20 +10,23 @@ use github_copilot_sdk::canvas::{CanvasDeclaration, CanvasHandler, CanvasResult} use github_copilot_sdk::handler::{ ApproveAllHandler, AutoModeSwitchHandler, AutoModeSwitchResponse, ElicitationHandler, ExitPlanModeHandler, ExitPlanModeResult, McpAuthHandler, McpAuthRequest, McpAuthResult, - UserInputHandler, UserInputResponse, + PermissionHandler, PermissionResult, UserInputHandler, UserInputResponse, }; use github_copilot_sdk::rpc::{ CanvasProviderInvokeActionRequest, CanvasProviderOpenRequest, CanvasProviderOpenResult, OpenCanvasInstance, }; use github_copilot_sdk::session_events::{ - McpOauthRequiredData, ReasoningSummary, SessionLimitsConfig, + ManagedSettingsResolvedSource, McpOauthRequiredData, ReasoningSummary, SessionLimitsConfig, + SessionManagedSettingsResolvedData, }; use github_copilot_sdk::types::{ CanvasProviderIdentity, CloudSessionOptions, CloudSessionRepository, CommandContext, - CommandDefinition, CommandHandler, DeliveryMode, ElicitationRequest, ElicitationResult, - ExitPlanModeData, ExtensionInfo, MessageOptions, RequestId, SessionConfig, SessionId, - SetModelOptions, Tool, ToolInvocation, ToolResult, + CommandDefinition, CommandHandler, DeliveryMode, DisableBypassPermissionsMode, + ElicitationRequest, ElicitationResult, ExitPlanModeData, ExtensionInfo, ManagedSettings, + ManagedSettingsPermissions, MessageOptions, PermissionDecisionContext, + PermissionDecisionOutcome, PermissionDecisionSource, PermissionDecisionSurface, RequestId, + SessionConfig, SessionId, SetModelOptions, Tool, ToolInvocation, ToolResult, }; use github_copilot_sdk::{Client, ContextTier, ErrorKind, ProtocolErrorKind, tool}; use serde_json::Value; @@ -36,6 +39,24 @@ struct TestCanvasHandler; struct CancelMcpAuthHandler; +struct ContextualApproveHandler; + +#[async_trait] +impl PermissionHandler for ContextualApproveHandler { + async fn handle( + &self, + _session_id: SessionId, + _request_id: RequestId, + _data: github_copilot_sdk::PermissionRequestData, + ) -> PermissionResult { + PermissionResult::approve_once().with_context(PermissionDecisionContext { + outcome: PermissionDecisionOutcome::PromptedUser, + source: PermissionDecisionSource::HumanResponse, + surface: PermissionDecisionSurface::CopilotApp, + }) + } +} + #[async_trait] impl McpAuthHandler for CancelMcpAuthHandler { async fn handle( @@ -639,6 +660,7 @@ async fn create_session_sends_new_session_options() { SessionConfig::default() .with_excluded_builtin_agents(["explore"]) .with_enable_citations(true) + .with_enable_file_change_tracking(true) .with_session_limits(SessionLimitsConfig { max_ai_credits: Some(30.0), }), @@ -655,6 +677,7 @@ async fn create_session_sends_new_session_options() { serde_json::json!(["explore"]) ); assert_eq!(request["params"]["enableCitations"], true); + assert_eq!(request["params"]["enableFileChangeTracking"], true); assert_eq!(request["params"]["sessionLimits"]["maxAiCredits"], 30.0); let id = request["id"].as_u64().unwrap(); @@ -683,6 +706,7 @@ async fn resume_session_sends_new_session_options() { ResumeSessionConfig::new(SessionId::from("session-options")) .with_excluded_builtin_agents(["task"]) .with_enable_citations(false) + .with_enable_file_change_tracking(false) .with_session_limits(SessionLimitsConfig { max_ai_credits: Some(15.0), }), @@ -700,6 +724,7 @@ async fn resume_session_sends_new_session_options() { serde_json::json!(["task"]) ); assert_eq!(request["params"]["enableCitations"], false); + assert_eq!(request["params"]["enableFileChangeTracking"], false); assert_eq!(request["params"]["sessionLimits"]["maxAiCredits"], 15.0); server_respond_create(&mut server_write, &request, "session-options").await; @@ -763,6 +788,135 @@ async fn create_session_sends_canvas_wire_fields() { timeout(TIMEOUT, create_handle).await.unwrap().unwrap(); } +#[tokio::test] +async fn create_and_resume_send_managed_settings_permissions() { + use github_copilot_sdk::types::ResumeSessionConfig; + + let (client, mut server_read, mut server_write) = make_client(); + + let managed = ManagedSettings::default().with_permissions( + ManagedSettingsPermissions::default() + .with_disable_bypass_permissions_mode(DisableBypassPermissionsMode::Disable) + .with_deny(vec!["shell(rm*)".to_string()]) + .with_ask(vec!["write".to_string()]) + .with_allow(vec![]), + ); + + let create_handle = tokio::spawn({ + let client = client.clone(); + let managed = managed.clone(); + async move { + client + .create_session( + SessionConfig::default() + .with_enable_managed_settings(true) + .with_managed_settings(managed), + ) + .await + .unwrap() + } + }); + + let request = read_framed(&mut server_read).await; + assert_eq!(request["method"], "session.create"); + assert_eq!(request["params"]["enableManagedSettings"], true); + let perms = &request["params"]["managedSettings"]["permissions"]; + assert_eq!(perms["disableBypassPermissionsMode"], "disable"); + assert_eq!(perms["deny"][0], "shell(rm*)"); + assert_eq!(perms["ask"][0], "write"); + assert_eq!(perms["allow"], serde_json::json!([])); + + let id = request["id"].as_u64().unwrap(); + let session_id = requested_session_id(&request).to_string(); + let response = serde_json::json!({ + "jsonrpc": "2.0", + "id": id, + "result": { "sessionId": session_id.clone() }, + }); + write_framed(&mut server_write, &serde_json::to_vec(&response).unwrap()).await; + timeout(TIMEOUT, create_handle).await.unwrap().unwrap(); + + let resume_handle = tokio::spawn({ + let client = client.clone(); + let session_id = session_id.clone(); + async move { + client + .resume_session( + ResumeSessionConfig::new(SessionId::from(session_id)) + .with_managed_settings(managed), + ) + .await + .unwrap() + } + }); + + let request = read_framed(&mut server_read).await; + assert_eq!(request["method"], "session.resume"); + assert_eq!( + request["params"]["managedSettings"]["permissions"]["deny"][0], + "shell(rm*)" + ); + + let id = request["id"].as_u64().unwrap(); + let response = serde_json::json!({ + "jsonrpc": "2.0", + "id": id, + "result": { "sessionId": session_id }, + }); + write_framed(&mut server_write, &serde_json::to_vec(&response).unwrap()).await; + + let reload = read_framed(&mut server_read).await; + assert_eq!(reload["method"], "session.skills.reload"); + let id = reload["id"].as_u64().unwrap(); + let response = serde_json::json!({ "jsonrpc": "2.0", "id": id, "result": {} }); + write_framed(&mut server_write, &serde_json::to_vec(&response).unwrap()).await; + + timeout(TIMEOUT, resume_handle).await.unwrap().unwrap(); +} + +#[test] +fn managed_settings_resolved_event_preserves_client_provenance() { + let sources = [ + (ManagedSettingsResolvedSource::Server, "server"), + (ManagedSettingsResolvedSource::Device, "device"), + (ManagedSettingsResolvedSource::Client, "client"), + (ManagedSettingsResolvedSource::Mixed, "mixed"), + (ManagedSettingsResolvedSource::None, "none"), + ]; + for (source, wire_value) in sources { + assert_eq!( + serde_json::to_value(source).unwrap(), + serde_json::json!(wire_value) + ); + } + + let with_client = SessionManagedSettingsResolvedData { + bypass_permissions_disabled: true, + client_managed: Some(true), + managed_keys: vec!["permissions".to_string()], + source: ManagedSettingsResolvedSource::Client, + ..Default::default() + }; + let serialized = serde_json::to_value(&with_client).unwrap(); + assert_eq!(serialized["source"], "client"); + assert_eq!(serialized["clientManaged"], true); + + let round_tripped: SessionManagedSettingsResolvedData = + serde_json::from_value(serialized).unwrap(); + assert_eq!(round_tripped.source, ManagedSettingsResolvedSource::Client); + assert_eq!(round_tripped.client_managed, Some(true)); + + let without_client = SessionManagedSettingsResolvedData { + bypass_permissions_disabled: true, + managed_keys: vec!["permissions".to_string()], + source: ManagedSettingsResolvedSource::Mixed, + ..Default::default() + }; + let serialized = serde_json::to_value(&without_client).unwrap(); + assert_eq!(serialized["source"], "mixed"); + assert!(serialized.get("clientManaged").is_none()); +} + fn make_client_with_telemetry( callback: github_copilot_sdk::github_telemetry::GitHubTelemetryCallback, ) -> (Client, tokio::io::DuplexStream, tokio::io::DuplexStream) { @@ -2360,6 +2514,45 @@ async fn approve_all_handler_approves_permission() { assert_eq!(request["params"]["result"]["kind"], "approve-once"); } +#[tokio::test] +async fn permission_result_forwards_context_beside_result() { + let (_session, mut server) = create_session_pair_with_config(|cfg| { + cfg.with_permission_handler(Arc::new(ContextualApproveHandler)) + }) + .await; + + server + .send_event( + "permission.requested", + serde_json::json!({ + "requestId": "perm-attributed", + "sessionId": server.session_id, + "permissionRequest": { "kind": "shell" }, + }), + ) + .await; + + let request = timeout(TIMEOUT, server.read_request()).await.unwrap(); + assert_eq!( + request["method"], + "session.permissions.handlePendingPermissionRequest" + ); + assert_eq!( + request["params"], + serde_json::json!({ + "sessionId": server.session_id, + "requestId": "perm-attributed", + "result": { "kind": "approve-once" }, + "decisionContext": { + "outcome": "prompted_user", + "source": "human_response", + "surface": "copilot_app", + }, + }) + ); + assert!(request["params"]["result"].get("decisionContext").is_none()); +} + #[tokio::test] async fn session_event_notification_reaches_handler() { let (session, mut server) = create_session_pair().await; @@ -4309,7 +4502,7 @@ async fn command_execute_handler_error_propagates_to_ack() { use github_copilot_sdk::session_fs::{ DirEntry, DirEntryKind, FileInfo, FsError, FsErrorKind, SessionFsConventions, SessionFsProvider, SessionFsSqliteProvider, SessionFsSqliteQueryResult, - SessionFsSqliteQueryType, + SessionFsSqliteQueryType, SessionFsSqliteTransactionError, SessionFsSqliteTransactionStatement, }; struct RecordingFsProvider { @@ -4431,6 +4624,24 @@ impl SessionFsSqliteProvider for RecordingFsProvider { })) } + async fn sqlite_transaction( + &self, + statements: &[SessionFsSqliteTransactionStatement], + ) -> Result, SessionFsSqliteTransactionError> { + let mut results = Vec::with_capacity(statements.len()); + for statement in statements { + let result = self + .sqlite_query( + statement.query_type.clone(), + &statement.query, + statement.params.as_ref(), + ) + .await?; + results.push(result.unwrap_or_default()); + } + Ok(results) + } + async fn sqlite_exists(&self) -> Result { Ok(true) } @@ -4619,6 +4830,13 @@ async fn session_fs_maps_sqlite_errors_to_results() { )) } + async fn sqlite_transaction( + &self, + _statements: &[SessionFsSqliteTransactionStatement], + ) -> Result, SessionFsSqliteTransactionError> { + Err(SessionFsSqliteTransactionError::fatal("sqlite unavailable")) + } + async fn sqlite_exists(&self) -> Result { Err(FsError::with_message( FsErrorKind::Other, diff --git a/scripts/codegen/csharp.ts b/scripts/codegen/csharp.ts index 24ec217f5..46f50daf5 100644 --- a/scripts/codegen/csharp.ts +++ b/scripts/codegen/csharp.ts @@ -40,6 +40,7 @@ import { isSchemaExperimental, isSchemaInternal, isOpaqueJson, + isOpaqueInProcess, isObjectSchema, isVoidSchema, getNullableInner, @@ -66,6 +67,55 @@ const TYPE_RENAMES: Record = { PermissionRequestedDataPermissionRequest: "PermissionRequest", }; +const POLYMORPHIC_BASE_PROPERTIES: Record = { + PermissionRequest: ["managedApprovalRequired"], +}; + +/** + * Public type names declared by hand-written C# sources under `dotnet/src` + * (excluding `dotnet/src/Generated`). Generated session-event types share the + * `GitHub.Copilot` namespace with those sources, so a schema definition whose + * name collides with a hand-written declaration must reuse it — emitting a + * second class of the same name fails the build (CS0260/CS0102). + * + * Populated by {@link collectHandWrittenCSharpTypeNames} before generation. + */ +let handWrittenCSharpTypeNames = new Set(); + +/** + * Scan hand-written `.cs` files under `dotnet/src` for top-level public type + * declarations. The `Generated` directory is skipped so this scanner never + * reads (or depends on the output of) its own emit. + */ +async function collectHandWrittenCSharpTypeNames(): Promise> { + const names = new Set(); + const srcDir = path.join(REPO_ROOT, "dotnet", "src"); + const declaration = /^\s*(?:public|internal)\s+(?:(?:abstract|sealed|static|partial|readonly|ref)\s+)*(?:class|record|struct|interface|enum)\s+([A-Za-z_]\w*)/gm; + + const walk = async (dir: string): Promise => { + let entries; + try { + entries = await fs.readdir(dir, { withFileTypes: true }); + } catch { + return; + } + for (const entry of entries) { + const entryPath = path.join(dir, entry.name); + if (entry.isDirectory()) { + if (entry.name === "Generated" || entry.name === "bin" || entry.name === "obj") continue; + await walk(entryPath); + continue; + } + if (!entry.name.endsWith(".cs")) continue; + const content = await fs.readFile(entryPath, "utf-8"); + for (const match of content.matchAll(declaration)) names.add(match[1]); + } + }; + + await walk(srcDir); + return names; +} + /** Apply rename to a generated class name, checking both exact match and prefix replacement for derived types. */ function applyTypeRename(className: string): string { if (TYPE_RENAMES[className]) return TYPE_RENAMES[className]; @@ -306,6 +356,41 @@ function failUnmappable(context: string, schema: JSONSchema7): never { ); } +function omitUnrepresentableInternalProperties(value: unknown): void { + if (!value || typeof value !== "object") return; + if (Array.isArray(value)) { + value.forEach(omitUnrepresentableInternalProperties); + return; + } + + const node = value as Record; + const properties = node.properties; + if (properties && typeof properties === "object" && !Array.isArray(properties)) { + for (const [name, property] of Object.entries(properties)) { + if (!property || typeof property !== "object" || Array.isArray(property)) continue; + const schema = property as JSONSchema7; + const hasType = + schema.type !== undefined || + schema.$ref !== undefined || + schema.anyOf !== undefined || + schema.oneOf !== undefined || + schema.allOf !== undefined || + schema.enum !== undefined || + schema.const !== undefined || + isOpaqueJson(schema); + if (isSchemaInternal(schema) && (!hasType || isOpaqueInProcess(schema))) { + delete (properties as Record)[name]; + } else { + omitUnrepresentableInternalProperties(property); + } + } + } + + for (const [name, child] of Object.entries(node)) { + if (name !== "properties") omitUnrepresentableInternalProperties(child); + } +} + function requiresArgumentNullCheck(typeName: string, isRequired: boolean): boolean { return isRequired && !typeName.endsWith("?") && !isNonNullableCSharpValueType(typeName); } @@ -827,6 +912,7 @@ function generatePolymorphicClasses( const lines: string[] = []; const discriminatorInfo = findDiscriminator(variants)!; const renamedBase = applyTypeRename(baseClassName); + const baseProperties = new Set(POLYMORPHIC_BASE_PROPERTIES[renamedBase] ?? []); lines.push(...xmlDocCommentWithFallback(description, `Polymorphic base type discriminated by ${escapeXml(discriminatorProperty)}.`, "")); if (experimental) pushExperimentalAttribute(lines); @@ -845,13 +931,52 @@ function generatePolymorphicClasses( lines.push(` /// The type discriminator.`); lines.push(` [JsonPropertyName("${discriminatorProperty}")]`); lines.push(` public virtual string ${toPascalCase(discriminatorProperty)} { get; set; } = string.Empty;`); + for (const propName of baseProperties) { + const propSchema = variants + .map((variant) => variant.properties?.[propName]) + .find((property): property is JSONSchema7 => typeof property === "object"); + if (!propSchema) continue; + + const csharpName = toCSharpPropertyName(propName, propSchema); + const csharpType = resolver( + propSchema, + renamedBase, + csharpName, + false, + knownTypes, + nestedClasses, + enumOutput + ); + lines.push(""); + lines.push(...xmlDocPropertyComment(propSchema.description, propName, " ")); + lines.push(...emitDataAnnotations(propSchema, " ", csharpType)); + if (isSchemaDeprecated(propSchema)) pushObsoleteAttributes(lines, " "); + if (isSchemaExperimental(propSchema)) pushExperimentalAttribute(lines, " "); + lines.push(` [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]`); + const propVisibility = pushCSharpInternalAttribute(lines, propSchema); + lines.push(` [JsonPropertyName("${propName}")]`); + lines.push(` ${propVisibility} virtual ${csharpType} ${csharpName} { get; set; }`); + } lines.push(`}`); lines.push(""); for (const { value, schema } of discriminatorInfo.mapping.values()) { const constValue = String(value); const derivedClassName = applyTypeRename(`${baseClassName}${toPascalCase(constValue)}`); - const derivedCode = generateDerivedClass(derivedClassName, renamedBase, discriminatorProperty, constValue, schema, knownTypes, nestedClasses, enumOutput, resolver, experimental, options); + const derivedCode = generateDerivedClass( + derivedClassName, + renamedBase, + discriminatorProperty, + constValue, + schema, + knownTypes, + nestedClasses, + enumOutput, + resolver, + experimental, + options, + baseProperties + ); nestedClasses.set(derivedClassName, derivedCode); } @@ -872,7 +997,8 @@ function generateDerivedClass( enumOutput: string[], propertyResolver: PropertyTypeResolver, experimental = false, - options: DiscriminatedUnionGenerationOptions = {} + options: DiscriminatedUnionGenerationOptions = {}, + baseProperties: ReadonlySet = new Set() ): string { const lines: string[] = []; const required = new Set(schema.required || []); @@ -897,6 +1023,18 @@ function generateDerivedClass( const csharpName = toCSharpPropertyName(propName, prop); const csharpType = propertyResolver(prop, className, csharpName, isReq, knownTypes, nestedClasses, enumOutput); + if (baseProperties.has(propName)) { + lines.push(` /// `); + if (!isReq) lines.push(` [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]`); + lines.push(` [JsonPropertyName("${propName}")]`); + lines.push(` public override ${csharpType} ${csharpName}`); + lines.push(` {`); + lines.push(` get => base.${csharpName};`); + lines.push(` set => base.${csharpName} = value;`); + lines.push(` }`, ""); + continue; + } + lines.push(...xmlDocPropertyComment(prop.description, propName, " ")); lines.push(...emitDataAnnotations(prop, " ", csharpType)); if (isSchemaDeprecated(prop)) pushObsoleteAttributes(lines, " "); @@ -1399,8 +1537,13 @@ namespace GitHub.Copilot; lines.push(generateDataClass(variant, knownTypes, nestedClasses, enumOutput), ""); } - // Nested classes - for (const [, code] of nestedClasses) lines.push(code, ""); + // Nested classes. A name already declared by a hand-written source is skipped: + // that declaration is the one the namespace keeps, and the generated property + // simply binds to it. + for (const [name, code] of nestedClasses) { + if (handWrittenCSharpTypeNames.has(name)) continue; + lines.push(code, ""); + } // Enums for (const code of enumOutput) lines.push(code); @@ -1420,6 +1563,7 @@ export async function generateSessionEvents(schemaPath?: string): Promise const resolvedPath = schemaPath ?? (await getSessionEventsSchemaPath()); const schema = cloneSchemaForCodegen((await loadSchemaJson(resolvedPath)) as JSONSchema7); const processed = propagateInternalVisibility(postProcessSchema(schema)); + handWrittenCSharpTypeNames = await collectHandWrittenCSharpTypeNames(); const code = generateSessionEventsCode(processed); const outPath = await writeGeneratedFile("dotnet/src/Generated/SessionEvents.cs", code); console.log(` ✓ ${outPath}`); @@ -1624,7 +1768,8 @@ function emitRpcClass( className: string, schema: JSONSchema7, visibility: "public" | "internal", - extraClasses: string[] + extraClasses: string[], + inlineTypeParentName: string = className ): string { const effectiveSchema = resolveObjectSchema(schema, rpcDefinitions) ?? @@ -1671,7 +1816,7 @@ function emitRpcClass( const prop = propSchema as JSONSchema7; const isReq = requiredSet.has(propName); const csharpName = toCSharpPropertyName(propName, prop); - const csharpType = resolveRpcType(prop, isReq, className, csharpName, extraClasses); + const csharpType = resolveRpcType(prop, isReq, inlineTypeParentName, csharpName, extraClasses); lines.push(...xmlDocPropertyComment(prop.description, propName, " ")); lines.push(...emitDataAnnotations(prop, " ", csharpType)); @@ -2037,7 +2182,15 @@ function emitSessionMethod(key: string, method: RpcMethod, lines: string[], clas }; const publicReqClass = emitRpcClass(requestClassName, publicParams, methodVisibility, classes); if (publicReqClass) classes.push(publicReqClass); - const wireReqClass = emitRpcClass(wireRequestClassName, effectiveParams, "internal", classes); + // The wire wrapper carries the same properties as the public request + // type plus `sessionId`, so both must reuse the same inline types. + const wireReqClass = emitRpcClass( + wireRequestClassName, + effectiveParams, + "internal", + classes, + requestClassName + ); if (wireReqClass) classes.push(wireReqClass); } else { const reqClass = emitRpcClass(requestClassName, effectiveParams, "internal", classes); @@ -2451,6 +2604,8 @@ function generateRpcCode( externalJsonSerializableRefs: Map> = new Map(), externalValueTypes: Set = new Set() ): string { + schema = cloneSchemaForCodegen(schema); + omitUnrepresentableInternalProperties(schema); emittedRpcClassSchemas.clear(); emittedRpcEnumResultTypes.clear(); experimentalRpcTypes.clear(); @@ -2563,6 +2718,7 @@ namespace GitHub.Copilot.Rpc; export async function generateRpc(schemaPath?: string, sessionEventsSchema?: JSONSchema7): Promise { console.log("C#: generating RPC types..."); const resolvedPath = schemaPath ?? (await getApiSchemaPath()); + handWrittenCSharpTypeNames = await collectHandWrittenCSharpTypeNames(); let schema = fixNullableRequiredRefsInApiSchema(cloneSchemaForCodegen((await loadSchemaJson(resolvedPath)) as ApiSchema)); if (sessionEventsSchema) { const sharedDefinitions = findSharedSchemaDefinitions( @@ -2592,7 +2748,9 @@ export async function generateRpc(schemaPath?: string, sessionEventsSchema?: JSO for (const name of reachableDefinitions) { const typeName = typeToClassName(name); const declarationPattern = new RegExp(`\\bpublic\\s+(?:(?:sealed|abstract|partial|readonly)\\s+)*(?:class|struct)\\s+${typeName}\\b`); - if (declarationPattern.test(sessionEventsCode)) { + // A hand-written declaration also lives in `GitHub.Copilot`, so the + // reference resolves even though the generated file skipped it. + if (declarationPattern.test(sessionEventsCode) || handWrittenCSharpTypeNames.has(typeName)) { emittedDefinitions.add(name); } const valueTypeDeclarationPattern = new RegExp(`\\bpublic\\s+(?:(?:readonly)\\s+)?struct\\s+${typeName}\\b`); diff --git a/scripts/codegen/go.ts b/scripts/codegen/go.ts index b1d7474b9..d6eda7f99 100644 --- a/scripts/codegen/go.ts +++ b/scripts/codegen/go.ts @@ -14,6 +14,7 @@ import { fileURLToPath } from "url"; import { promisify } from "util"; import wordwrap from "wordwrap"; import { + addManagedApprovalRequiredToPermissionRequests, cloneSchemaForCodegen, collectDefinitionCollections, collectExperimentalOnlyRpcReferencedDefinitionNames, @@ -1812,6 +1813,9 @@ function emitGoFlatDiscriminatedUnion( lines.push(`type ${typeName} interface {`); lines.push(`\t${markerName}()`); lines.push(`\t${discriminatorMethodName}() ${discGoType}`); + if (typeName === "PermissionRequest") { + lines.push(`\tRequiresManagedApproval() bool`); + } lines.push(`}`); lines.push(``); @@ -3679,7 +3683,9 @@ async function generateSessionEvents(schemaPath?: string, apiSchema?: ApiSchema) console.log("Go: generating session-events..."); const resolvedPath = schemaPath ?? (await getSessionEventsSchemaPath()); - const schema = cloneSchemaForCodegen((await loadSchemaJson(resolvedPath)) as JSONSchema7); + const schema = addManagedApprovalRequiredToPermissionRequests( + (await loadSchemaJson(resolvedPath)) as JSONSchema7 + ); const processed = propagateInternalVisibility(postProcessSchema(schema)); const processedApiSchema = apiSchema ? propagateInternalVisibility(postProcessSchema(cloneSchemaForCodegen(apiSchema as JSONSchema7)) as JSONSchema7) diff --git a/scripts/codegen/python.ts b/scripts/codegen/python.ts index 5b343122b..978021a98 100644 --- a/scripts/codegen/python.ts +++ b/scripts/codegen/python.ts @@ -11,6 +11,7 @@ import path from "path"; import type { JSONSchema7, JSONSchema7Definition } from "json-schema"; import { fileURLToPath } from "url"; import { + addManagedApprovalRequiredToPermissionRequests, cloneSchemaForCodegen, filterNodeByVisibility, fixNullableRequiredRefsInApiSchema, @@ -272,6 +273,39 @@ function postProcessExternalUnionAliasesForPython(code: string, aliases: Map; + dispatch: Array<{ value: PyDiscriminatorValue; typeName: string }>; } function postProcessRefBasedDiscriminatedUnionsForPython( code: string, @@ -304,7 +338,7 @@ function postProcessRefBasedDiscriminatedUnionsForPython( aliasName: string; variantNames: string[]; discriminatorProp: string; - dispatch: Array<{ value: string; typeName: string }>; + dispatch: Array<{ value: PyDiscriminatorValue; typeName: string }>; description: string | undefined; } const unions: UnionInfo[] = []; @@ -334,7 +368,7 @@ function postProcessRefBasedDiscriminatedUnionsForPython( discriminator.property ]; return { - value: String(discProp.const), + value: pyDiscriminatorValue(discProp.const), typeName: toPascalCase(variantRefNames[i]), }; }); @@ -387,7 +421,7 @@ function postProcessRefBasedDiscriminatedUnionsForPython( for (const union of unions) { const actualAliasName = resolveActualName(union.aliasName); const actualVariantNames: string[] = []; - const actualDispatch: Array<{ value: string; typeName: string }> = []; + const actualDispatch: Array<{ value: PyDiscriminatorValue; typeName: string }> = []; let allResolved = true; for (let i = 0; i < union.variantNames.length; i++) { const actual = resolveActualName(union.variantNames[i]); @@ -450,7 +484,7 @@ function postProcessRefBasedDiscriminatedUnionsForPython( dispatcherLines.push(` kind = obj.get(${JSON.stringify(union.discriminatorProp)})`); dispatcherLines.push(` match kind:`); for (const m of actualDispatch) { - dispatcherLines.push(` case ${JSON.stringify(m.value)}: return ${m.typeName}.from_dict(obj)`); + dispatcherLines.push(` case ${pyDiscriminatorValueExpr(m.value)}: return ${m.typeName}.from_dict(obj)`); } dispatcherLines.push( ` case _: raise ValueError(f"Unknown ${actualAliasName} ${union.discriminatorProp}: {kind!r}")` @@ -500,7 +534,7 @@ function postProcessDiscriminatorDefaultsForPython( unions: ResolvedRefBasedUnion[] ): string { // Build variant lookup: variant class name → { prop, value }. - const variantInfo = new Map(); + const variantInfo = new Map(); for (const union of unions) { for (const d of union.dispatch) { // First-wins; multiple unions referencing the same variant share a @@ -571,9 +605,9 @@ function postProcessDiscriminatorDefaultsForPython( continue; } const fieldIndent = (block[fieldIdx].match(/^(\s+)/) ?? ["", ""])[1]; - const literal = JSON.stringify(info.value); + const literal = pyDiscriminatorValueExpr(info.value); // Replace the field with a class-level constant. - block[fieldIdx] = `${fieldIndent}${info.prop}: ClassVar[str] = ${literal}`; + block[fieldIdx] = `${fieldIndent}${info.prop}: ClassVar[${pyDiscriminatorValueType(info.value)}] = ${literal}`; usedClassVar = true; // Drop any field-trailing docstring lines that immediately followed the @@ -895,6 +929,95 @@ function collapsePlaceholderPythonDataclasses(code: string, knownDefinitionNames return code.replace(/\n{3,}/g, "\n\n"); } +function removeUnusedSyntheticPythonDataclasses(code: string, knownDefinitionNames: Set): string { + interface DataclassBlock { + name: string; + text: string; + start: number; + end: number; + synthetic: boolean; + } + + const classBlockRe = + /((?:^# (?:Experimental|Deprecated|Internal):[^\n]*\r?\n)*@dataclass(?:\([^\r\n]*\))?\r?\nclass\s+(\w+):[\s\S]*?)(?=^(?:# (?:Experimental|Deprecated|Internal):[^\n]*\r?\n)*@dataclass(?:\([^\r\n]*\))?\r?\nclass\s+\w|^class\s+\w|^def\s+\w|^[A-Z]\w+\s*=|\Z)/gm; + const blocks: DataclassBlock[] = [...code.matchAll(classBlockRe)].map((match) => ({ + name: match[2], + text: match[1], + start: match.index ?? 0, + end: (match.index ?? 0) + match[1].length, + synthetic: !knownDefinitionNames.has(match[2].toLowerCase()), + })); + const syntheticBlocks = blocks.filter((block) => block.synthetic); + if (syntheticBlocks.length === 0) return code; + + let outsideSyntheticBlocks = ""; + let cursor = 0; + for (const block of syntheticBlocks) { + outsideSyntheticBlocks += code.slice(cursor, block.start); + cursor = block.end; + } + outsideSyntheticBlocks += code.slice(cursor); + + const syntheticNames = new Set(syntheticBlocks.map((block) => block.name)); + const dependencies = new Map>(); + const live = new Set(); + + for (const block of syntheticBlocks) { + const referenceRe = new RegExp(`\\b${escapeRegExp(block.name)}\\b`); + if (referenceRe.test(outsideSyntheticBlocks)) { + live.add(block.name); + } + + const blockDependencies = new Set(); + for (const dependency of syntheticNames) { + if (dependency === block.name) continue; + const dependencyRe = new RegExp(`\\b${escapeRegExp(dependency)}\\b`); + if (dependencyRe.test(block.text)) { + blockDependencies.add(dependency); + } + } + dependencies.set(block.name, blockDependencies); + } + + const worklist = [...live]; + while (worklist.length > 0) { + const name = worklist.pop()!; + for (const dependency of dependencies.get(name) ?? []) { + if (live.has(dependency)) continue; + live.add(dependency); + worklist.push(dependency); + } + } + + const blocksToRemove = new Set(syntheticBlocks.filter((block) => !live.has(block.name)).map((block) => block.name)); + if (blocksToRemove.size === 0) return code; + + const appendSegment = (parts: string[], segment: string): void => { + if (parts.length === 0 || segment.length === 0) { + parts.push(segment); + return; + } + const previous = parts[parts.length - 1]; + const trailingNewlines = previous.match(/\n+$/)?.[0].length ?? 0; + const leadingNewlines = segment.match(/^\n+/)?.[0].length ?? 0; + if (trailingNewlines + leadingNewlines > 2) { + segment = "\n".repeat(Math.max(0, 2 - trailingNewlines)) + segment.slice(leadingNewlines); + } + parts.push(segment); + }; + + const parts: string[] = []; + cursor = 0; + for (const block of blocks) { + if (!blocksToRemove.has(block.name)) continue; + appendSegment(parts, code.slice(cursor, block.start)); + cursor = block.end; + } + appendSegment(parts, code.slice(cursor)); + + return parts.join(""); +} + /** * Reorder Python class/enum definitions so forward references are resolved. * Quicktype may emit classes in an order where a class references another @@ -1590,7 +1713,7 @@ function tryEmitPyRefBasedDiscriminatedUnion( if (!discriminator) return undefined; const variantTypeNames: string[] = []; - const dispatch: Array<{ value: string; typeName: string }> = []; + const dispatch: Array<{ value: PyDiscriminatorValue; typeName: string }> = []; for (let i = 0; i < variants.length; i++) { const variantTypeName = toPascalCase(variantRefNames[i]); const variantSchema = resolveObjectSchema(variants[i], ctx.definitions); @@ -1599,7 +1722,7 @@ function tryEmitPyRefBasedDiscriminatedUnion( } variantTypeNames.push(variantTypeName); const discProp = resolvedVariants[i].properties?.[discriminator.property] as JSONSchema7; - dispatch.push({ value: String(discProp.const), typeName: variantTypeName }); + dispatch.push({ value: pyDiscriminatorValue(discProp.const), typeName: variantTypeName }); } if (!ctx.aliasesByName.has(aliasName)) { @@ -1627,7 +1750,7 @@ function tryEmitPyRefBasedDiscriminatedUnion( lines.push(` match kind:`); for (const m of dispatch) { lines.push( - ` case ${JSON.stringify(m.value)}: return ${m.typeName}.from_dict(obj)` + ` case ${pyDiscriminatorValueExpr(m.value)}: return ${m.typeName}.from_dict(obj)` ); } lines.push( @@ -2219,9 +2342,19 @@ function emitPyClass( const fieldEntries = Object.entries(schema.properties || {}).filter( ([, value]) => typeof value === "object" ) as Array<[string, JSONSchema7]>; + const optionalFieldEntries = fieldEntries + .filter(([name]) => !required.has(name)) + .sort(([left, leftSchema], [right, rightSchema]) => { + const leftAppendOnly = + (leftSchema as Record)["x-copilot-sdk-append-last"] === true; + const rightAppendOnly = + (rightSchema as Record)["x-copilot-sdk-append-last"] === true; + if (leftAppendOnly !== rightAppendOnly) return leftAppendOnly ? 1 : -1; + return left.localeCompare(right); + }); const orderedFieldEntries = [ ...fieldEntries.filter(([name]) => required.has(name)).sort(([a], [b]) => a.localeCompare(b)), - ...fieldEntries.filter(([name]) => !required.has(name)).sort(([a], [b]) => a.localeCompare(b)), + ...optionalFieldEntries, ]; const fieldInfos = orderedFieldEntries.map(([propName, propSchema]) => { @@ -2808,7 +2941,9 @@ async function generateSessionEvents(schemaPath?: string): Promise { console.log("Python: generating session-events..."); const resolvedPath = schemaPath ?? (await getSessionEventsSchemaPath()); - const schema = (await loadSchemaJson(resolvedPath)) as JSONSchema7; + const schema = addManagedApprovalRequiredToPermissionRequests( + (await loadSchemaJson(resolvedPath)) as JSONSchema7 + ); const processed = propagateInternalVisibility(postProcessSchema(schema)); let code = generatePythonSessionEventsCode(processed); const { typeNames } = collectInternalSymbols(processed); @@ -3228,6 +3363,10 @@ def _patch_model_capabilities(data: dict) -> dict: finalCode = applyUnionRewritesToPython(finalCode, refBasedUnions); finalCode = postProcessDiscriminatorDefaultsForPython(finalCode, refBasedUnions); finalCode = unwrapRedundantPythonLambdas(finalCode); + finalCode = removeUnusedSyntheticPythonDataclasses( + finalCode, + new Set(Object.keys(allDefinitions).map((name) => name.toLowerCase())) + ); // Apply `_`-prefix to type names of internal RPC types so the leading-underscore // Python convention signals "internal, no stability guarantees" to consumers. diff --git a/scripts/codegen/rust.ts b/scripts/codegen/rust.ts index a04f5a21c..4090318f0 100644 --- a/scripts/codegen/rust.ts +++ b/scripts/codegen/rust.ts @@ -17,6 +17,7 @@ import { fileURLToPath } from "url"; import { promisify } from "util"; import type { JSONSchema7, JSONSchema7Definition } from "json-schema"; import { + addManagedApprovalRequiredToPermissionRequests, type ApiSchema, type DefinitionCollections, EXCLUDED_EVENT_TYPES, @@ -575,6 +576,45 @@ function emitRustMapAlias( ); } +/** + * Map a primitive JSON Schema type to its Rust equivalent, or `undefined` when + * the schema is not a plain scalar. Mirrors the primitive branches of + * {@link resolveRustType}. + */ +function rustScalarType(schema: JSONSchema7): string | undefined { + if (schema.enum || schema.const !== undefined) return undefined; + switch (schema.type) { + case "string": + return "String"; + case "number": + return "f64"; + case "integer": + return isIntegerSchemaBoundedToInt32(schema) ? "i32" : "i64"; + case "boolean": + return "bool"; + default: + return undefined; + } +} + +/** + * Emit a type alias for a named schema that resolves to a primitive scalar + * (e.g. an RPC result declared as `{ "type": "integer" }`). Without this the + * generated RPC surface would reference a `*Result` type that was never + * defined. + */ +function emitRustScalarAlias( + typeName: string, + schema: JSONSchema7, + ctx: RustCodegenCtx, + description?: string, +): void { + if (ctx.generatedNames.has(typeName)) return; + const scalarType = rustScalarType(schema); + if (!scalarType) return; + emitRustTypeAlias(typeName, schema, scalarType, ctx, description); +} + function rustRpcResultDescription( method: RpcMethod, resultSchema: JSONSchema7 | undefined, @@ -1527,6 +1567,8 @@ function generateApiTypesCode( } else { tryEmitRustUnion(schema, name, "", ctx); } + } else { + emitRustScalarAlias(name, schema, ctx, schema.description); } } @@ -1576,6 +1618,8 @@ function generateApiTypesCode( emitRustMapAlias(resultName, resolved, ctx, resolved.description); } else if (isObjectSchema(resolved)) { emitRustStruct(resultName, resolved, ctx, resolved.description); + } else { + emitRustScalarAlias(resultName, resolved, ctx, resolved.description); } } } @@ -2168,7 +2212,9 @@ async function generate(): Promise { const sessionEventsSchema = propagateInternalVisibility( postProcessSchema( - stripBooleanLiterals(sessionEventsRaw) as JSONSchema7, + stripBooleanLiterals( + addManagedApprovalRequiredToPermissionRequests(sessionEventsRaw as JSONSchema7), + ) as JSONSchema7, ), ); const apiSchema = propagateInternalVisibility( diff --git a/scripts/codegen/typescript.ts b/scripts/codegen/typescript.ts index f82e67abe..4984816d8 100644 --- a/scripts/codegen/typescript.ts +++ b/scripts/codegen/typescript.ts @@ -40,7 +40,9 @@ import { isSchemaInternal, appendPropertyMarkerTagsToDescriptions, getEnumValueDescriptions, - stripOpaqueJsonMarker, + isBareSchemaNode, + isOpaqueInProcess, + isOpaqueJson, loadSchemaJson, fixBrandCasing, type ApiSchema, @@ -52,11 +54,115 @@ const TS_EXPERIMENTAL_JSDOC = "/** @experimental */"; const EXTERNAL_SCHEMA_TS_IMPORT: Record = { "session-events.schema.json": "./session-events.js", }; +type OpaqueTypeAlias = "JsonValue" | "OpaqueInProcessValue"; + +function opaqueTypeAliasBlock(aliases: ReadonlySet): string { + const declarations: string[] = []; + if (aliases.has("JsonValue")) { + declarations.push( + `/** A value that can be represented losslessly on the SDK JSON wire. */ +export type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue };` + ); + } + if (aliases.has("OpaqueInProcessValue")) { + declarations.push( + `/** + * A value that lives only in this process and never crosses the JSON-RPC + * boundary, such as a callback or a host object handle. + * @internal + */ +export type OpaqueInProcessValue = unknown;` + ); + } + return declarations.join("\n\n"); +} + +function restoreOpaqueTypeAliasFormatting(code: string): string { + return code.replace( + "export type JsonValue = null | boolean | number | string | JsonValue[] | {[key: string]: JsonValue};", + "export type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue };" + ); +} function tsExperimentalJSDoc(indent = ""): string { return `${indent}${TS_EXPERIMENTAL_JSDOC}`; } +/** + * Validates that no public declaration in the generated TypeScript references an internal type. + * + * If the schema is valid (enforced by the runtime's `assert_no_public_internal_references` lint), + * this should never trigger. A failure here means the codegen itself produced a public reference + * to an internal type — which is a codegen bug that must be fixed, not silently worked around. + */ +export function assertNoPublicInternalReferences(generatedTs: string, internalTypes: Set): void { + if (internalTypes.size === 0) return; + + // Identify declarations tagged @internal anywhere in their JSDoc (multi-line or single-line). + const internalDeclarations = new Set(); + for (const m of generatedTs.matchAll( + /\/\*\*(?:[^*]|\*(?!\/))*@internal(?:[^*]|\*(?!\/))*\*\/\s*\nexport (?:interface|type|function|const) (\w+)\b/g + )) { + internalDeclarations.add(m[1]); + } + + // Split on export interface/type/function/const boundaries for attribution. + const declarationRe = /^export (interface|type|function|const) (\w+)\b/gm; + const starts: Array<{ index: number; kind: string; name: string }> = []; + for (let m = declarationRe.exec(generatedTs); m !== null; m = declarationRe.exec(generatedTs)) { + starts.push({ index: m.index, kind: m[1], name: m[2] }); + } + const blocks = starts.map((start, i) => ({ + kind: start.kind, + name: start.name, + text: generatedTs.slice(start.index, i + 1 < starts.length ? starts[i + 1].index : generatedTs.length), + })); + + const violations: string[] = []; + for (const intType of internalTypes) { + for (const block of blocks) { + if (block.name === intType) continue; + if (internalDeclarations.has(block.name)) continue; + + // Strip content that does not appear in the emitted .d.ts: + // 1. All JSDoc/block comments — prevents doc-comment text that happens to name a + // type (e.g. "via the definition X") from registering as a code reference. + // 2. Function bodies — declaration emit drops bodies, so a reference inside a + // function implementation is not a public type reference. + // 3. @internal-tagged member sections — TypeScript's stripInternal removes them + // from the .d.ts along with any types they reference. + let publicText = block.text + // Remove @internal-tagged member declarations before stripping comments so + // member-level internal references do not count as part of the public surface. + // Handles both simple members (`foo?: Hidden;`) and inline object-shaped members + // (`foo?: { ... };`) used by generated TypeScript interfaces. + .replace( + /^[ \t]*\/\*\*[\s\S]*?@internal[\s\S]*?\*\/\s*\n(?:[ \t]*[^\n{;]+;\n?|[ \t]*[^\n{]+\{\n[\s\S]*?^[ \t]*\};\n?)/gm, + "" + ) + // Remove all remaining block comments (JSDoc and otherwise). + .replace(/\/\*[\s\S]*?\*\//g, ""); + + if (block.kind === "function") { + // Remove function bodies (from the opening { to matching closing }). + publicText = publicText.replace(/\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}/g, "{}"); + } + + if (new RegExp(`\\b${intType}\\b`).test(publicText)) { + violations.push(` ${block.name} (public) references internal type ${intType}`); + } + } + } + + if (violations.length > 0) { + throw new Error( + `Codegen produced public declarations that reference internal types.\n` + + `This is a codegen bug — fix the generator so internal types are not referenced by public output:\n` + + violations.join("\n") + ); + } +} + function sanitizeJsDocText(text: string): string { return text.trim().replace(/\*\//g, "* /"); } @@ -252,7 +358,10 @@ function collectRpcMethods(node: Record): RpcMethod[] { return results; } -export function normalizeSchemaForTypeScript(schema: JSONSchema7): JSONSchema7 { +export function normalizeSchemaForTypeScript( + schema: JSONSchema7, + opaqueTypeAliases?: Set +): JSONSchema7 { const root = structuredClone(schema) as JSONSchema7 & { definitions?: Record; $defs?: Record; @@ -274,24 +383,52 @@ export function normalizeSchemaForTypeScript(schema: JSONSchema7): JSONSchema7 { root.definitions = definitions; delete root.$defs; - const rewrite = (value: unknown): unknown => { + const internalDefinitionNames = new Set( + Object.entries(definitions) + .filter(([, definition]) => typeof definition === "object" && definition !== null && isSchemaInternal(definition as JSONSchema7)) + .map(([name]) => name) + ); + const isInternalUnionVariant = (value: unknown): boolean => { + if (!value || typeof value !== "object") return false; + const variant = value as JSONSchema7; + if (isSchemaInternal(variant)) return true; + const match = variant.$ref?.match(/^#\/(?:definitions|\$defs)\/([^/]+)$/); + return match ? internalDefinitionNames.has(match[1]) : false; + }; + + const rewrite = (value: unknown, withinInternalDefinition = false): unknown => { if (Array.isArray(value)) { - return value.map(rewrite); + return value.map((item) => rewrite(item, withinInternalDefinition)); } if (!value || typeof value !== "object") { return value; } + const source = value as Record; + const isInternal = withinInternalDefinition || isSchemaInternal(source as JSONSchema7); const rewritten = Object.fromEntries( - Object.entries(value as Record).map(([key, child]) => [key, rewrite(child)]) + Object.entries(source).map(([key, child]) => { + const publicChild = + !isInternal && + (key === "anyOf" || key === "oneOf") && + Array.isArray(child) + ? child.filter((variant) => !isInternalUnionVariant(variant)) + : child; + return [key, rewrite(publicChild, isInternal)]; + }) ) as Record; - // The TypeScript codegen doesn't distinguish opaque JSON from any - // other unconstrained value, so drop the marker before feeding the - // schema to json-schema-to-typescript. C# codegen reads the marker - // from its own (un-normalized) view of the schema and emits - // `JsonElement` instead. - stripOpaqueJsonMarker(rewritten); + if (isBareSchemaNode(rewritten as JSONSchema7)) { + if (isOpaqueJson(rewritten as JSONSchema7)) { + rewritten.tsType = "JsonValue"; + opaqueTypeAliases?.add("JsonValue"); + } else if (isOpaqueInProcess(rewritten as JSONSchema7)) { + rewritten.tsType = "OpaqueInProcessValue"; + opaqueTypeAliases?.add("OpaqueInProcessValue"); + } + } + delete rewritten["x-opaque-json"]; + delete rewritten["x-opaque-in-process"]; const enumValueDescriptions = getEnumValueDescriptions(rewritten as JSONSchema7); if (enumValueDescriptions && Array.isArray(rewritten.enum) && rewritten.enum.every((entry) => typeof entry === "string")) { @@ -338,21 +475,37 @@ export function normalizeSchemaForTypeScript(schema: JSONSchema7): JSONSchema7 { // ── Session Events ────────────────────────────────────────────────────────── -async function generateSessionEvents(schemaPath?: string): Promise { - console.log("TypeScript: generating session-events..."); - - const resolvedPath = schemaPath ?? (await getSessionEventsSchemaPath()); - const schema = (await loadSchemaJson(resolvedPath)) as JSONSchema7; - const processed = propagateInternalVisibility(postProcessSchema(schema)); - const definitionCollections = collectDefinitionCollections(processed as Record); - const sessionEvent = - resolveSchema({ $ref: "#/definitions/SessionEvent" }, definitionCollections) ?? - resolveSchema({ $ref: "#/$defs/SessionEvent" }, definitionCollections) ?? - processed; +/** + * Filters a `SessionEvent` union schema to exclude internal arms. + * + * The schema marks internal union members with `visibility: "internal"` on the arm object itself + * AND on the resolved definition. An arm is excluded when either level is internal, or when the + * arm's resolved `data` property is internal (legacy pattern for event types that carry their + * payload in a `data` field). + * + * Returns the filtered arms and the set of definition names to exclude from compilation. + */ +export function filterPublicSessionEventVariants( + variants: JSONSchema7[], + definitionCollections: DefinitionCollections +): { publicVariants: JSONSchema7[]; excludedDefinitionNames: Set } { const excludedDefinitionNames = new Set(); - const publicVariants = (sessionEvent.anyOf ?? []).filter((variant) => { + const publicVariants = variants.filter((variant) => { const variantSchema = variant as JSONSchema7; const resolvedVariant = resolveSchema(variantSchema, definitionCollections) ?? variantSchema; + + // Exclude the arm if the arm object itself or its resolved definition is internal. + // The schema marks internal union members at both levels; checking only the resolved + // definition's `data` sub-property (the original logic) missed cases where the event + // type itself carries `visibility: "internal"`. + if (isSchemaInternal(variantSchema) || isSchemaInternal(resolvedVariant)) { + for (const ref of [variantSchema.$ref]) { + const match = ref?.match(/^#\/(?:definitions|\$defs)\/([^/]+)$/); + if (match) excludedDefinitionNames.add(match[1]); + } + return false; + } + const dataSchema = resolvedVariant.properties?.data as JSONSchema7 | undefined; const resolvedData = dataSchema ? resolveSchema(dataSchema, definitionCollections) ?? dataSchema : undefined; if (!isSchemaInternal(resolvedData)) { @@ -365,6 +518,24 @@ async function generateSessionEvents(schemaPath?: string): Promise { } return false; }); + return { publicVariants, excludedDefinitionNames }; +} + +async function generateSessionEvents(schemaPath?: string): Promise { + console.log("TypeScript: generating session-events..."); + + const resolvedPath = schemaPath ?? (await getSessionEventsSchemaPath()); + const schema = (await loadSchemaJson(resolvedPath)) as JSONSchema7; + const processed = propagateInternalVisibility(postProcessSchema(schema)); + const definitionCollections = collectDefinitionCollections(processed as Record); + const sessionEvent = + resolveSchema({ $ref: "#/definitions/SessionEvent" }, definitionCollections) ?? + resolveSchema({ $ref: "#/$defs/SessionEvent" }, definitionCollections) ?? + processed; + const { publicVariants, excludedDefinitionNames } = filterPublicSessionEventVariants( + sessionEvent.anyOf ?? [], + definitionCollections + ); const publicDefinitions = Object.fromEntries( Object.entries(definitionCollections.definitions).filter(([name]) => !excludedDefinitionNames.has(name)) ); @@ -384,20 +555,31 @@ async function generateSessionEvents(schemaPath?: string): Promise { ); appendPropertyMarkerTagsToDescriptions(schemaForCompile); - const ts = await compile(normalizeSchemaForTypeScript(schemaForCompile), "SessionEvent", { - bannerComment: `/** + const opaqueTypeAliases = new Set(); + const ts = restoreOpaqueTypeAliasFormatting( + await compile(normalizeSchemaForTypeScript(schemaForCompile, opaqueTypeAliases), "SessionEvent", { + bannerComment: [ + `/** * AUTO-GENERATED FILE - DO NOT EDIT * Generated from: session-events.schema.json */`, - style: { semi: true, singleQuote: false, trailingComma: "all" }, - additionalProperties: false, - strictIndexSignatures: true, - }); + opaqueTypeAliasBlock(opaqueTypeAliases), + ] + .filter(Boolean) + .join("\n\n"), + style: { semi: true, singleQuote: false, trailingComma: "all" }, + additionalProperties: false, + strictIndexSignatures: true, + }) + ); let annotatedTs = annotateTypeScriptTypes(ts, experimentalDefinitionNames(definitionCollections), TS_EXPERIMENTAL_JSDOC); // Add @internal JSDoc annotations for session-event types marked // `visibility: "internal"` in the schema. The tag drives `stripInternal` // so the whole type is dropped from the published .d.ts. + // Because internal union arms are excluded from the compiled output by the + // publicVariants filter above, no public declaration should reference these + // types; assertNoPublicInternalReferences enforces that invariant hard. const sessionInternalTypes = new Set(); for (const [name, def] of Object.entries(definitionCollections.definitions ?? {})) { if (def && typeof def === "object" && (def as Record).visibility === "internal") { @@ -415,6 +597,7 @@ async function generateSessionEvents(schemaPath?: string): Promise { `$1/** @internal */\n$2` ); } + assertNoPublicInternalReferences(annotatedTs, sessionInternalTypes); const outPath = await writeGeneratedFile("nodejs/src/generated/session-events.ts", annotatedTs); console.log(` ✓ ${outPath}`); } @@ -546,6 +729,7 @@ import type { MessageConnection } from "vscode-jsonrpc/node.js"; if (externalSchemaRefs.size > 0) { lines.push(""); } + const aliasInsertIndex = lines.length; const allMethods = [...collectRpcMethods(schema.server || {}), ...collectRpcMethods(schema.session || {})]; const clientSessionMethods = collectRpcMethods(schema.clientSession || {}); @@ -648,12 +832,17 @@ import type { MessageConnection } from "vscode-jsonrpc/node.js"; const schemaForCompile = combinedSchema; appendPropertyMarkerTagsToDescriptions(schemaForCompile); - const compiled = await compile(normalizeSchemaForTypeScript(schemaForCompile), "_RpcSchemaRoot", { + const opaqueTypeAliases = new Set(); + const compiled = await compile(normalizeSchemaForTypeScript(schemaForCompile, opaqueTypeAliases), "_RpcSchemaRoot", { bannerComment: "", additionalProperties: false, strictIndexSignatures: true, unreachableDefinitions: true, }); + const aliases = opaqueTypeAliasBlock(opaqueTypeAliases); + if (aliases) { + lines.splice(aliasInsertIndex, 0, aliases, ""); + } // Strip the placeholder root type and keep only the definition-generated types const strippedTs = compiled @@ -675,13 +864,9 @@ import type { MessageConnection } from "vscode-jsonrpc/node.js"; `$1/** @deprecated */\n$2` ); } - // Add @internal JSDoc annotations for types from internal methods - for (const intType of internalTypes) { - annotatedTs = annotatedTs.replace( - new RegExp(`(^|\\n)(export (?:interface|type) ${intType}\\b)`, "m"), - `$1/** @internal */\n$2` - ); - } + // @internal tagging happens in a final pass over the assembled file: the client/server + // method signatures that reference these types are emitted later, so a per-chunk check + // would not see them and would strip a type the public API still names. lines.push(annotatedTs); lines.push(""); } @@ -758,7 +943,20 @@ function hasInternalMethods(node: Record): boolean { lines.push(...emitClientGlobalApiRegistration(schema.clientGlobal)); } - const outPath = await writeGeneratedFile("nodejs/src/generated/rpc.ts", lines.join("\n")); + // Apply @internal to RPC types in a final pass over the assembled file. + // The client/server method signatures that reference these types are emitted + // after the per-schema type chunks, so the tagging must happen here rather + // than per-chunk. assertNoPublicInternalReferences then enforces hard that no + // public declaration slipped through referencing a type the schema marked internal. + let rpcTs = lines.join("\n"); + for (const intType of internalTypes) { + rpcTs = rpcTs.replace( + new RegExp(`(^|\\n)(export (?:interface|type) ${intType}\\b)`, "m"), + `$1/** @internal */\n$2` + ); + } + assertNoPublicInternalReferences(rpcTs, internalTypes); + const outPath = await writeGeneratedFile("nodejs/src/generated/rpc.ts", rpcTs); console.log(` ✓ ${outPath}`); } @@ -983,6 +1181,9 @@ function emitClientGlobalApiRegistration(clientSchema: Record): for (const [groupName, methods] of groups) { const interfaceName = toPascalCase(groupName) + "Handler"; + const publicMethods = methods.filter((m) => m.visibility !== "internal"); + // Skip groups that have no public methods — they are handled internally by the SDK. + if (publicMethods.length === 0) continue; const groupDeprecated = isNodeFullyDeprecated(clientSchema[groupName] as Record); const groupExperimental = isNodeFullyExperimental(clientSchema[groupName] as Record); if (groupDeprecated) { @@ -994,7 +1195,7 @@ function emitClientGlobalApiRegistration(clientSchema: Record): lines.push(`/** Handler for \`${groupName}\` client global API methods. */`); } lines.push(`export interface ${interfaceName} {`); - for (const method of methods) { + for (const method of publicMethods) { const name = handlerMethodName(method.rpcMethod); const hasParams = hasSchemaPayload(getMethodParamsSchema(method)); const pType = hasParams ? paramsTypeName(method) : ""; @@ -1019,7 +1220,9 @@ function emitClientGlobalApiRegistration(clientSchema: Record): lines.push(`/** All client global API handler groups. */`); lines.push(`export interface ClientGlobalApiHandlers {`); - for (const [groupName] of groups) { + for (const [groupName, methods] of groups) { + const publicMethods = methods.filter((m) => m.visibility !== "internal"); + if (publicMethods.length === 0) continue; const interfaceName = toPascalCase(groupName) + "Handler"; lines.push(` ${groupName}?: ${interfaceName};`); } @@ -1039,7 +1242,10 @@ function emitClientGlobalApiRegistration(clientSchema: Record): lines.push(`): void {`); for (const [groupName, methods] of groups) { - for (const method of methods) { + // Only wire up public methods; internal methods are handled directly by the SDK. + const publicMethods = methods.filter((m) => m.visibility !== "internal"); + if (publicMethods.length === 0) continue; + for (const method of publicMethods) { const name = handlerMethodName(method.rpcMethod); const pType = paramsTypeName(method); const hasParams = hasSchemaPayload(getMethodParamsSchema(method)); diff --git a/scripts/codegen/utils.ts b/scripts/codegen/utils.ts index 9ab335b05..1804990ae 100644 --- a/scripts/codegen/utils.ts +++ b/scripts/codegen/utils.ts @@ -453,6 +453,49 @@ export function cloneSchemaForCodegen(value: T): T { return value; } +const PERMISSION_REQUEST_DEFINITION_NAMES = [ + "PermissionRequestCustomTool", + "PermissionRequestExtensionManagement", + "PermissionRequestExtensionPermissionAccess", + "PermissionRequestFactory", + "PermissionRequestHook", + "PermissionRequestMcp", + "PermissionRequestMemory", + "PermissionRequestRead", + "PermissionRequestShell", + "PermissionRequestUrl", + "PermissionRequestWrite", +] as const; + +/** + * Add managed approval metadata until the pinned CLI schema includes the field. + */ +export function addManagedApprovalRequiredToPermissionRequests(schema: T): T { + const cloned = cloneSchemaForCodegen(schema); + const property: JSONSchema7 = { + description: + "When true, managed policy requires an explicit user decision and automatic approval must be bypassed.", + type: ["boolean", "null"], + }; + (property as Record)["x-copilot-sdk-append-last"] = true; + + for (const definitions of [cloned.definitions, cloned.$defs]) { + if (!definitions) continue; + for (const name of PERMISSION_REQUEST_DEFINITION_NAMES) { + const definition = definitions[name]; + if (!definition || typeof definition !== "object") continue; + const objectDefinition = definition as JSONSchema7; + objectDefinition.properties = { + ...objectDefinition.properties, + managedApprovalRequired: + objectDefinition.properties?.managedApprovalRequired ?? cloneSchemaForCodegen(property), + }; + } + } + + return cloned; +} + export function getEnumValueDescriptions(schema: JSONSchema7 | null | undefined): EnumValueDescriptions | undefined { if (!schema || typeof schema !== "object") return undefined; @@ -900,6 +943,34 @@ export function isOpaqueJson(schema: JSONSchema7 | null | undefined): boolean { return typeof schema === "object" && schema !== null && (schema as Record)["x-opaque-json"] === true; } +/** Returns true when a JSON Schema node is marked `x-opaque-in-process: true`. */ +export function isOpaqueInProcess(schema: JSONSchema7 | null | undefined): boolean { + return typeof schema === "object" && schema !== null && (schema as Record)["x-opaque-in-process"] === true; +} + +/** + * Returns true when a schema node has no structural constraints that describe a + * more precise TypeScript type than an opaque marker. + */ +export function isBareSchemaNode(schema: JSONSchema7 | null | undefined): boolean { + if (typeof schema !== "object" || schema === null) return false; + const node = schema as Record; + return ![ + "type", + "anyOf", + "oneOf", + "allOf", + "$ref", + "properties", + "items", + "enum", + "const", + "additionalProperties", + "not", + "patternProperties", + ].some((key) => key in node); +} + /** * Removes the `x-opaque-json` marker from a schema node in place. Useful for * codegens (e.g. TypeScript) that don't distinguish opaque JSON from any other diff --git a/scripts/corrections/package-lock.json b/scripts/corrections/package-lock.json index 60559d62d..a975812af 100644 --- a/scripts/corrections/package-lock.json +++ b/scripts/corrections/package-lock.json @@ -1107,9 +1107,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "version": "3.3.17", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.17.tgz", + "integrity": "sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==", "dev": true, "funding": [ { @@ -1164,9 +1164,9 @@ } }, "node_modules/postcss": { - "version": "8.5.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", - "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "version": "8.5.25", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz", + "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==", "dev": true, "funding": [ { @@ -1184,7 +1184,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.12", + "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -1334,9 +1334,9 @@ } }, "node_modules/undici": { - "version": "6.27.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz", - "integrity": "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==", + "version": "6.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.28.0.tgz", + "integrity": "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==", "dev": true, "license": "MIT", "engines": { diff --git a/scripts/docs-validation/package-lock.json b/scripts/docs-validation/package-lock.json index 7b7ca5a65..0c2751fa7 100644 --- a/scripts/docs-validation/package-lock.json +++ b/scripts/docs-validation/package-lock.json @@ -480,15 +480,15 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" } }, "node_modules/color-convert": { diff --git a/scripts/docs-validation/validate.ts b/scripts/docs-validation/validate.ts index b609ef859..6ce615eab 100644 --- a/scripts/docs-validation/validate.ts +++ b/scripts/docs-validation/validate.ts @@ -385,13 +385,16 @@ async function validateJava(): Promise { fs.copyFileSync(path.join(javaDir, file), path.join(srcDir, file)); } - // Read the SDK version from java/pom.xml - const sdkPomPath = path.join(ROOT_DIR, "java", "pom.xml"); + // Read the inherited SDK version from java/sdk/pom.xml + const sdkPomPath = path.join(ROOT_DIR, "java", "sdk", "pom.xml"); const sdkPomContent = fs.readFileSync(sdkPomPath, "utf-8"); const versionMatch = sdkPomContent.match( - /copilot-sdk-java<\/artifactId>\s*([^<]+)<\/version>/, + /[\s\S]*?([^<]+)<\/version>[\s\S]*?<\/parent>/, ); - const sdkVersion = versionMatch ? versionMatch[1] : "1.0.0-SNAPSHOT"; + if (!versionMatch) { + throw new Error(`Could not read the Java SDK version from ${sdkPomPath}`); + } + const sdkVersion = versionMatch[1]; // Create pom.xml that references the local SDK const pomXml = ` diff --git a/test/harness/package-lock.json b/test/harness/package-lock.json index ffd36162c..17a839f0d 100644 --- a/test/harness/package-lock.json +++ b/test/harness/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "devDependencies": { - "@github/copilot": "^1.0.73", + "@github/copilot": "^1.0.79", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "@types/node-forge": "^1.3.14", @@ -501,9 +501,9 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.73", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.73.tgz", - "integrity": "sha512-8I2Ejg2CX/PQA3c2H8W1zuqhniCeR1q1/bD8CrV53/ZLw8GF7DAV0xQpwa8ELYvFgjXb6AADojafCKwdbVef+A==", + "version": "1.0.79", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.79.tgz", + "integrity": "sha512-uHBm2BYbKJgyfiKp1WokX7QUNHGvzEX0zaGeb3qM3CybP06rsJrX3JgQe95qwwma6vQz0ah9gV68ERW2JqaKRA==", "dev": true, "license": "SEE LICENSE IN LICENSE.md", "dependencies": { @@ -513,20 +513,20 @@ "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.73", - "@github/copilot-darwin-x64": "1.0.73", - "@github/copilot-linux-arm64": "1.0.73", - "@github/copilot-linux-x64": "1.0.73", - "@github/copilot-linuxmusl-arm64": "1.0.73", - "@github/copilot-linuxmusl-x64": "1.0.73", - "@github/copilot-win32-arm64": "1.0.73", - "@github/copilot-win32-x64": "1.0.73" + "@github/copilot-darwin-arm64": "1.0.79", + "@github/copilot-darwin-x64": "1.0.79", + "@github/copilot-linux-arm64": "1.0.79", + "@github/copilot-linux-x64": "1.0.79", + "@github/copilot-linuxmusl-arm64": "1.0.79", + "@github/copilot-linuxmusl-x64": "1.0.79", + "@github/copilot-win32-arm64": "1.0.79", + "@github/copilot-win32-x64": "1.0.79" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.73", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.73.tgz", - "integrity": "sha512-5jv7t2sw35/zI0cPze38hG6239NT5/q/Emjx6gLibYkolDqMDJjpm17Ps7tc8oafUEOiMQMb+ar7+qi6rSiGJA==", + "version": "1.0.79", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.79.tgz", + "integrity": "sha512-rsw7JoMvlcxXb0yx08oIeEc0x2hUEwKSfhX9ESKfdMVt0Ckrzm4OEvNUyzOpOnLJ9+l3h/aI+u1w5g2ZU2K7UA==", "cpu": [ "arm64" ], @@ -541,9 +541,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.73", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.73.tgz", - "integrity": "sha512-l794k6Ahb11AG2FQT/P4TEWxWblzM1h8aQQCzG8jBWp8dfwjhyYjJ+d+0CWQzM3Fc1ddNUZRjKXCUsfvFjiZhQ==", + "version": "1.0.79", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.79.tgz", + "integrity": "sha512-D983e2lXYnq+KhjA8mTZXonY1+LGfJN9BM195J73shUvx49nRJmibDHWLvVtGeYc+43evGUOAQrOqOspAhhWPQ==", "cpu": [ "x64" ], @@ -558,9 +558,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.73", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.73.tgz", - "integrity": "sha512-Zu0W5nupJjNeem0brqU/pG+VY0IWr6EWr/FsC90g5SEDiaM4VhVNVWcz8t0E3DQCSYetV6IBaNMtjs/3uIIiDQ==", + "version": "1.0.79", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.79.tgz", + "integrity": "sha512-qqaNkvi92Wg+4OZk/kTWC2nUG72G0vV6eRAo5+PnKaPmjdX1GsI0a+lPxXPEbzX0zYLi/8yrUyANwyyNEsGgXA==", "cpu": [ "arm64" ], @@ -575,9 +575,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.73", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.73.tgz", - "integrity": "sha512-k33XIr6/PVp+K+5F/zv3No4PPaNImvHz73mcbIw63oxh5iiacXjgr0WqbBIS5s/rkhOWjNPIkbof/TTPZ7mQjA==", + "version": "1.0.79", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.79.tgz", + "integrity": "sha512-wzotZfvHkItutciLFMXZT2k9Qiii4Ta8tsVDCMQ7CP8hPxV91FyJ1yf3+FFSSfPvWrfYM6BOAiqIuX+LjgRuiw==", "cpu": [ "x64" ], @@ -592,9 +592,9 @@ } }, "node_modules/@github/copilot-linuxmusl-arm64": { - "version": "1.0.73", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.73.tgz", - "integrity": "sha512-HJWzhfD3oaiIgfRAHkNWzp17fELtshqM9HVN5n+lFEmSO2EETCEh0P1lhJc4m+FYfXSJnL0raAqVuyaNMuPoPw==", + "version": "1.0.79", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.79.tgz", + "integrity": "sha512-INtRSARl7DdNm2MXnn4GJuK+Y7QD24ANox02uH8htNQwRlNvdvg+YGS1V/mYgLDXFepeUjMjzTNC+i70+kh5uw==", "cpu": [ "arm64" ], @@ -609,9 +609,9 @@ } }, "node_modules/@github/copilot-linuxmusl-x64": { - "version": "1.0.73", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.73.tgz", - "integrity": "sha512-/BpOXSb16wHEu8I1SaKiLszQ4Kvu4+Z4uCn7W0bv4xI4fPZwTEG0u3zgaI2W9Ao3+aBl0XRpPmpWzE9ziYEq+w==", + "version": "1.0.79", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.79.tgz", + "integrity": "sha512-LxJAIfPP6Ok/9qpXGZuhnAft3W9JVcK9tbO3jWXcGDJT3v+2NtutyjmP/A7/cDXdTruXVQ4MybwAgacN8Gj/sg==", "cpu": [ "x64" ], @@ -626,9 +626,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.73", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.73.tgz", - "integrity": "sha512-DbPeXiYzQjpOy9oboaBvuCzjRwfcL987c3bG09cK1crdCDrKfkTJ7NXpcp1KWRPIRFO1FQm1qToNE89J+L3uvg==", + "version": "1.0.79", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.79.tgz", + "integrity": "sha512-5wg/ayCBTVy4g4FdO/9BJZRVARY0sgjAn9rBkw5BSJMv4u7Mvxg5Sftlift+V5UWxTyCSHAELZ5IHKvox4Yi8w==", "cpu": [ "arm64" ], @@ -643,9 +643,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.73", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.73.tgz", - "integrity": "sha512-8D3E1l5i+N5Eq8HIOQpx+Zbcb3MXdFxszksM2gqq175Z1S7Zna67oY4GoR3psxlbIpSyHKiLEBWYiaps6ayHWw==", + "version": "1.0.79", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.79.tgz", + "integrity": "sha512-FTpThWwwCDYnLdE0pfdo5zpAQLLVg36kmC2IKyVMuCYv9iPe7rE1mz7ng/UITN9M3TAMBrwHSvCV3pITvw4W8Q==", "cpu": [ "x64" ], @@ -1691,9 +1691,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", "dev": true, "funding": [ { @@ -1871,9 +1871,9 @@ } }, "node_modules/hono": { - "version": "4.12.23", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.23.tgz", - "integrity": "sha512-eIaZ9qDgu7XV0pxOCrg7/WhnQ6Ivm22UcxhXx/A3dcbqbbYgBEkc6e/J/s7j2tS96zoB0S9VBdLwQNCWwUo4LA==", + "version": "4.12.32", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.32.tgz", + "integrity": "sha512-XcuyW9qE2kJn07PkecMOBd5Vq/hMy7mmGw+idz1yblbg9N17ijJODrvPkn7/dwL3Kulj8LcRJ69DLOWf91dRUg==", "dev": true, "license": "MIT", "engines": { @@ -1926,9 +1926,9 @@ "license": "ISC" }, "node_modules/ip-address": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", - "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.4.0.tgz", + "integrity": "sha512-oSK96Grm3aP6OrS263xVxbNDGVL7rzBtYdpGqlDG8iQdoenDoTs/nkki+DflYbAEE8Xl6o5YxhxlrKvI3nqKXQ==", "dev": true, "license": "MIT", "engines": { @@ -2322,9 +2322,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "version": "3.3.17", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.17.tgz", + "integrity": "sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==", "dev": true, "funding": [ { @@ -2508,9 +2508,9 @@ } }, "node_modules/postcss": { - "version": "8.5.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", - "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "version": "8.5.25", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz", + "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==", "dev": true, "funding": [ { @@ -2528,7 +2528,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.12", + "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, diff --git a/test/harness/package.json b/test/harness/package.json index c77a25f54..7903a8f4e 100644 --- a/test/harness/package.json +++ b/test/harness/package.json @@ -14,7 +14,7 @@ "node": "^20.19.0 || >=22.12.0" }, "devDependencies": { - "@github/copilot": "^1.0.73", + "@github/copilot": "^1.0.79", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "@types/node-forge": "^1.3.14", diff --git a/test/harness/replayingCapiProxy.ts b/test/harness/replayingCapiProxy.ts index 5e07449f7..4c1be59f2 100644 --- a/test/harness/replayingCapiProxy.ts +++ b/test/harness/replayingCapiProxy.ts @@ -19,6 +19,7 @@ import { CapturingHttpProxy, PerformRequestOptions, } from "./capturingHttpProxy"; +export type { CapturedRequest } from "./capturingHttpProxy"; import { anthropicMessagesEndpoint, anthropicMessagesRequestToChatCompletion, @@ -327,6 +328,20 @@ export class ReplayingCapiProxy extends CapturingHttpProxy { return; } + // Handle /requests endpoint for retrieving all captured outbound requests. + if ( + options.requestOptions.path === "/requests" && + options.requestOptions.method === "GET" + ) { + const requests = this.exchanges + .map((exchange) => exchange.request) + .filter((request) => request.url !== "/requests"); + options.onResponseStart(200, { "content-type": "application/json" }); + options.onData(Buffer.from(JSON.stringify(requests))); + options.onResponseEnd(); + return; + } + // Handle /copilot_internal/user endpoint for per-session auth. // This must run before the state guard below: the CLI authenticates and // calls /copilot_internal/user at startup, which can race ahead of the @@ -401,6 +416,51 @@ export class ReplayingCapiProxy extends CapturingHttpProxy { return; } + // Keep GitHub MCP tests hermetic while still capturing the request at + // the CAPI proxy. The tests only need a successful transport handshake; + // no fake tools are exposed. + if (options.requestOptions.path === "/mcp") { + if (options.requestOptions.method !== "POST") { + options.onResponseStart(200, commonResponseHeaders); + options.onResponseEnd(); + return; + } + + const request = JSON.parse(options.body ?? "{}") as { + id?: string | number; + method?: string; + params?: { protocolVersion?: string }; + }; + if (request.id === undefined) { + options.onResponseStart(202, commonResponseHeaders); + options.onResponseEnd(); + return; + } + + const result = + request.method === "initialize" + ? { + protocolVersion: + request.params?.protocolVersion ?? "2025-03-26", + capabilities: { tools: {} }, + serverInfo: { name: "e2e-github-mcp", version: "1.0.0" }, + } + : request.method === "tools/list" + ? { tools: [] } + : {}; + options.onResponseStart(200, { + "content-type": "application/json", + ...commonResponseHeaders, + }); + options.onData( + Buffer.from( + JSON.stringify({ jsonrpc: "2.0", id: request.id, result }), + ), + ); + options.onResponseEnd(); + return; + } + // Handle memory endpoints - return stub responses in tests // Matches: /agents/*/memory/*/enabled, /agents/*/memory/*/recent, etc. if (options.requestOptions.path?.match(/\/agents\/.*\/memory\//)) { @@ -1747,6 +1807,7 @@ export type ToolResultNormalizer = { export type CopilotUserResponse = { login: string; copilot_plan?: string; + token_based_billing?: boolean; is_mcp_enabled?: boolean; endpoints?: { api?: string; diff --git a/test/harness/test-mcp-server.mjs b/test/harness/test-mcp-server.mjs index b2b32606d..a3a84b42b 100644 --- a/test/harness/test-mcp-server.mjs +++ b/test/harness/test-mcp-server.mjs @@ -13,9 +13,17 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { appendFile } from "node:fs/promises"; import { z } from "zod"; -const server = new McpServer({ name: "env-echo", version: "1.0.0" }); +function getArgument(name) { + const index = process.argv.indexOf(name); + return index === -1 ? undefined : process.argv[index + 1]; +} + +const startupMarkerPath = getArgument("--startup-marker"); +const serverName = getArgument("--server-name") ?? "env-echo"; +const server = new McpServer({ name: serverName, version: "1.0.0" }); server.tool( "get_env", @@ -27,5 +35,7 @@ server.tool( ); const transport = new StdioServerTransport(); +if (startupMarkerPath) { + await appendFile(startupMarkerPath, `${serverName}\n`); +} await server.connect(transport); - diff --git a/test/snapshots/hooks_extended/should_invoke_agentstop_hook_and_apply_block_response.yaml b/test/snapshots/hooks_extended/should_invoke_agentstop_hook_and_apply_block_response.yaml new file mode 100644 index 000000000..6485670a1 --- /dev/null +++ b/test/snapshots/hooks_extended/should_invoke_agentstop_hook_and_apply_block_response.yaml @@ -0,0 +1,14 @@ +models: + - claude-sonnet-4.5 +conversations: + - messages: + - role: system + content: ${system} + - role: user + content: "Reply with exactly: AGENT_STOP_INITIAL" + - role: assistant + content: AGENT_STOP_INITIAL + - role: user + content: "Reply with exactly: AGENT_STOP_CONTINUED" + - role: assistant + content: AGENT_STOP_CONTINUED diff --git a/test/snapshots/hooks_extended/should_invoke_userprompttransformed_hook_and_modify_transformed_prompt.yaml b/test/snapshots/hooks_extended/should_invoke_userprompttransformed_hook_and_modify_transformed_prompt.yaml new file mode 100644 index 000000000..db2b02968 --- /dev/null +++ b/test/snapshots/hooks_extended/should_invoke_userprompttransformed_hook_and_modify_transformed_prompt.yaml @@ -0,0 +1,10 @@ +models: + - claude-sonnet-4.5 +conversations: + - messages: + - role: system + content: ${system} + - role: user + content: "Reply with exactly: HOOKED_TRANSFORMED_PROMPT" + - role: assistant + content: HOOKED_TRANSFORMED_PROMPT diff --git a/test/snapshots/permissions/should_honor_a_decision_annotated_with_decisioncontext.yaml b/test/snapshots/permissions/should_honor_a_decision_annotated_with_decisioncontext.yaml new file mode 100644 index 000000000..ef6f60dbe --- /dev/null +++ b/test/snapshots/permissions/should_honor_a_decision_annotated_with_decisioncontext.yaml @@ -0,0 +1,24 @@ +models: + - claude-sonnet-4.5 +conversations: + - messages: + - role: system + content: ${system} + - role: user + content: Edit protected.txt and replace 'protected' with 'hacked'. + - role: assistant + content: I'll view the file first, then make the edit. + - role: assistant + tool_calls: + - id: toolcall_0 + type: function + function: + name: report_intent + arguments: '{"intent":"Editing protected.txt file"}' + - role: assistant + tool_calls: + - id: toolcall_1 + type: function + function: + name: view + arguments: '{"path":"${workdir}/protected.txt"}' diff --git a/test/snapshots/rewind/should_restore_tracked_file_and_conversation.yaml b/test/snapshots/rewind/should_restore_tracked_file_and_conversation.yaml new file mode 100644 index 000000000..2ef3733e0 --- /dev/null +++ b/test/snapshots/rewind/should_restore_tracked_file_and_conversation.yaml @@ -0,0 +1,21 @@ +models: + - claude-sonnet-4.5 +conversations: + - messages: + - role: system + content: ${system} + - role: user + content: Use the create tool to create rewind-sdk.txt containing exactly SDK rewind content. After the tool succeeds, + reply with exactly SDK_REWIND_DONE. + - role: assistant + tool_calls: + - id: toolcall_0 + type: function + function: + name: create + arguments: '{"path":"${workdir}/rewind-sdk.txt","file_text":"SDK rewind content"}' + - role: tool + tool_call_id: toolcall_0 + content: Created file ${workdir}/rewind-sdk.txt with 18 characters + - role: assistant + content: SDK_REWIND_DONE diff --git a/test/snapshots/rpc_session_state/should_report_processing_and_context_metadata.yaml b/test/snapshots/rpc_session_state/should_report_processing_and_context_metadata.yaml new file mode 100644 index 000000000..6760888d7 --- /dev/null +++ b/test/snapshots/rpc_session_state/should_report_processing_and_context_metadata.yaml @@ -0,0 +1,10 @@ +models: + - claude-sonnet-4.5 +conversations: + - messages: + - role: system + content: ${system} + - role: user + content: "Reply with exactly: RUST_CONTEXT_INFO" + - role: assistant + content: RUST_CONTEXT_INFO diff --git a/test/snapshots/session/should_set_model_with_reasoningeffort.yaml b/test/snapshots/session/should_set_model_with_reasoningeffort.yaml index 0e019bdad..ccf204d2a 100644 --- a/test/snapshots/session/should_set_model_with_reasoningeffort.yaml +++ b/test/snapshots/session/should_set_model_with_reasoningeffort.yaml @@ -1,5 +1,6 @@ models: - claude-sonnet-4.5 + - gpt-5.4 conversations: - messages: - role: system diff --git a/test/snapshots/rpc_mcp_lifecycle/should_register_and_unregister_external_mcp_client.yaml b/test/snapshots/session_config/should_apply_github_mcp_tool_config_on_create.yaml similarity index 100% rename from test/snapshots/rpc_mcp_lifecycle/should_register_and_unregister_external_mcp_client.yaml rename to test/snapshots/session_config/should_apply_github_mcp_tool_config_on_create.yaml diff --git a/test/snapshots/streaming_fidelity/should_emit_streaming_deltas_with_reasoning_effort_configured.yaml b/test/snapshots/streaming_fidelity/should_emit_streaming_deltas_with_reasoning_effort_configured.yaml index fd825907f..e720fc34d 100644 --- a/test/snapshots/streaming_fidelity/should_emit_streaming_deltas_with_reasoning_effort_configured.yaml +++ b/test/snapshots/streaming_fidelity/should_emit_streaming_deltas_with_reasoning_effort_configured.yaml @@ -1,5 +1,5 @@ models: - - claude-sonnet-4.5 + - gpt-5.4 conversations: - messages: - role: system diff --git a/test/snapshots/tools/clears_context_from_a_terminal_tool_and_starts_the_seeded_turn.yaml b/test/snapshots/tools/clears_context_from_a_terminal_tool_and_starts_the_seeded_turn.yaml new file mode 100644 index 000000000..36d5adce4 --- /dev/null +++ b/test/snapshots/tools/clears_context_from_a_terminal_tool_and_starts_the_seeded_turn.yaml @@ -0,0 +1,22 @@ +models: + - claude-sonnet-4.5 +conversations: + - messages: + - role: system + content: ${system} + - role: user + content: Call clear_context with prompt "Reply with exactly FRESH_CONTEXT." now. + - role: assistant + tool_calls: + - id: toolcall_0 + type: function + function: + name: clear_context + arguments: '{"prompt":"Reply with exactly FRESH_CONTEXT."}' + - messages: + - role: system + content: ${system} + - role: user + content: Reply with exactly FRESH_CONTEXT. + - role: assistant + content: FRESH_CONTEXT